Manuals
Manuals




This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English

Natywne rozszerzenia

Jeśli potrzebujesz niestandardowej integracji z zewnętrznym oprogramowaniem lub sprzętem na niskim poziomie, gdzie Lua nie wystarcza, Defold SDK pozwala pisać rozszerzenia do silnika w C, C++, C#, Objective-C, Java lub JavaScript, zależnie od platformy docelowej. Typowe zastosowania natywnych rozszerzeń to:

  • Integracja z konkretnym sprzętem, na przykład z kamerą w telefonie.
  • Integracja z zewnętrznymi niskopoziomowymi API, na przykład z API sieci reklamowych, które nie pozwalają na komunikację sieciową w sposób, w jaki można by użyć Luasocket.
  • Obliczenia o wysokiej wydajności i przetwarzanie danych.

Obsługa C# jest eksperymentalna i przeznaczona dla natywnych rozszerzeń, a nie dla komponentów skryptowych Defold. Używa .NET 9 NativeAOT do utworzenia biblioteki statycznej; dodaj pliki źródłowe .cs do folderu src rozszerzenia, a usługa budowania wygeneruje plik projektu. Obsługiwane platformy zależą od bieżących możliwości NativeAOT i usługi budowania. Aktualny przepływ pracy i przetestowaną konfigurację przedstawia oficjalny przykład języków dla natywnych rozszerzeń.

Serwer budowania

Defold udostępnia gotowy punkt startowy do pracy z natywnymi rozszerzeniami dzięki chmurowemu rozwiązaniu do budowania. Każde natywne rozszerzenie, które zostanie dodane do projektu gry, bezpośrednio lub przez Library Project, staje się częścią zwykłej zawartości projektu. Nie ma potrzeby budowania specjalnych wersji silnika i rozsyłania ich do członków zespołu. Robi się to automatycznie - każdy członek zespołu, który zbuduje i uruchomi projekt, otrzyma plik wykonywalny silnika specyficzny dla projektu, z wszystkimi natywnymi rozszerzeniami wbudowanymi na stałe.

Budowanie w chmurze

Defold udostępnia serwer budowania w chmurze bezpłatnie i bez żadnych ograniczeń użycia. Serwer jest hostowany w Europie, a URL, pod który wysyłany jest kod natywny, konfiguruje się w oknie Editor Preferences albo przez opcję wiersza poleceń --build-server narzędzia bob. Jeśli chcesz uruchomić własny serwer, postępuj zgodnie z tymi instrukcjami.

Układ projektu

Aby utworzyć nowe rozszerzenie, utwórz folder w katalogu głównym projektu. Ten folder będzie zawierał wszystkie ustawienia, kod źródłowy, biblioteki i zasoby związane z rozszerzeniem. Serwer budowania rozpoznaje strukturę folderów i zbiera wszystkie pliki źródłowe oraz biblioteki.

 myextension/
 │
 ├── ext.manifest
 │
 ├── src/
 │
 ├── include/
 │
 ├── lib/
 │   └──[platforms]
 │
 ├── manifests/
 │   └──[platforms]
 │
 └── res/
     └──[platforms]

ext.manifest
Folder rozszerzenia musi zawierać plik ext.manifest. Jest to plik konfiguracyjny z flagami i definicjami używanymi podczas budowania pojedynczego rozszerzenia. Definicję formatu pliku znajdziesz w instrukcji o manifestach rozszerzenia.
src
Ten folder powinien zawierać wszystkie pliki z kodem źródłowym.
include
Ten opcjonalny folder zawiera pliki include.
lib
Ten opcjonalny folder zawiera wszystkie skompilowane biblioteki, od których zależy rozszerzenie. Pliki biblioteki należy umieszczać w podfolderach nazwanych według platform albo architecture-platform, zależnie od tego, jakie architektury są obsługiwane przez twoje biblioteki.

Obsługiwane platformy to ios, android, osx, win32, linux, web.

Obsługiwane pary arc-platform to arm64-ios, arm64_sim-ios, armv7-android, arm64-android, x86_64-android, arm64-osx, x86_64-osx, x86_64-win32, arm64-linux, x86_64-linux, wasm-web oraz wasm_pthread-web.

manifests
Ten opcjonalny folder zawiera dodatkowe pliki używane podczas procesu budowania lub pakowania. Szczegóły znajdziesz poniżej.
res
Ten opcjonalny folder zawiera dodatkowe zasoby, od których zależy rozszerzenie. Pliki zasobów należy umieszczać w podfolderach nazwanych według platform albo architecture-platform, tak samo jak podfoldery w lib. Dozwolony jest też podfolder common, zawierający pliki zasobów wspólne dla wszystkich platform.

Pliki manifestu

Opcjonalny folder manifests rozszerzenia zawiera dodatkowe pliki używane podczas procesu budowania i pakowania. Pliki należy umieszczać w podfolderach nazwanych według platform:

Reguły R8 dla Androida

Dodaj plik .keep do katalogu manifests/android rozszerzenia, obok build.gradle. Na przykład plik /myextension/manifests/android/myextension.keep może zachowywać klasy Java rozszerzenia za pomocą reguły:

-keep,allowoptimization class com.example.myextension.** { *; }

Zastąp com.example.myextension pakietem zawierającym klasy Java rozszerzenia. Ta reguła zachowuje klasy i ich składowe, pozwalając R8 optymalizować ich kod. Dodaj reguły dla innych klas używanych przez Java Native Interface (JNI) lub mechanizm refleksji, ponieważ R8 może nie wykryć takich odwołań automatycznie.

Jeśli rozszerzenie korzysta z adnotacji w czasie działania, dodaj również:

-keepattributes *Annotation*

Te reguły są łączone z wybranym plikiem reguł projektu po włączeniu R8.

Przy użyciu zgodnego serwera Extender R8 usuwa również nieużywane zasoby Androida. Jeśli rozszerzenie wyszukuje zasoby dynamicznie według nazwy, dodaj reguły zachowywania zasobów w formacie XML w katalogu res/android/res/raw w folderze rozszerzenia.

Zasoby niestandardowe

Rozszerzenie może dołączać dane do archiwum gry, deklarując zasoby niestandardowe w pliku ext.properties obok ext.manifest:

[project]
custom_resources.default = /myextension/data

Na przykład umieść plik JSON w /myextension/data/settings.json. Ścieżka jest względna wobec katalogu głównego projektu i zawiera folder rozszerzenia. Udostępniając rozszerzenie jako bibliotekę, dodaj myextension do ustawienia Include Dirs biblioteki, aby korzystające z niej projekty otrzymały rozszerzenie wraz z danymi.

Te ścieżki są łączone z project.custom_resources z pliku game.project oraz ze ścieżkami dostarczanymi przez inne rozszerzenia. Ustawienie zasobów niestandardowych w projekcie nie zastępuje zasobów pochodzących z rozszerzeń. Pliki są dołączane zarówno podczas budowania w edytorze, jak i do archiwów tworzonych przez Bob, a w czasie działania można je wczytać:

local data, err = sys.load_resource("/myextension/data/settings.json")
if data then
    local settings = json.decode(data)
    pprint(settings)
else
    print(err)
end

Różnice między zasobami niestandardowymi a zasobami pakietu opisano w instrukcji dostępu do plików.

Udostępnianie rozszerzenia

Rozszerzenia są traktowane tak samo jak inne zasoby w projekcie i można je udostępniać w ten sam sposób. Jeśli folder natywnego rozszerzenia zostanie dodany jako folder biblioteki, można go udostępniać i używać jako zależności projektu. Więcej informacji znajdziesz w instrukcji o bibliotekach.

Prosty przykład rozszerzenia

Zbudujmy bardzo proste rozszerzenie. Najpierw tworzymy nowy folder główny myextension i dodajemy plik ext.manifest zawierający nazwę rozszerzenia MyExtension. Zwróć uwagę, że ta nazwa jest symbolem C++ i musi odpowiadać pierwszemu argumentowi DM_DECLARE_EXTENSION (patrz niżej).

Manifest

# C++ symbol in your extension
name: "MyExtension"

Rozszerzenie składa się z jednego pliku C++, myextension.cpp, który tworzymy w folderze src.

Plik C++

Plik źródłowy rozszerzenia zawiera następujący kod:

// myextension.cpp
// Extension lib defines
#define LIB_NAME "MyExtension"
#define MODULE_NAME "myextension"

// include the Defold SDK
#include <dmsdk/sdk.h>

static int Reverse(lua_State* L)
{
    // The number of expected items to be on the Lua stack
    // once this struct goes out of scope
    DM_LUA_STACK_CHECK(L, 1);

    // Check and get parameter string from stack
    char* str = (char*)luaL_checkstring(L, 1);

    // Reverse the string
    int len = strlen(str);
    for(int i = 0; i < len / 2; i++) {
        const char a = str[i];
        const char b = str[len - i - 1];
        str[i] = b;
        str[len - i - 1] = a;
    }

    // Put the reverse string on the stack
    lua_pushstring(L, str);

    // Return 1 item
    return 1;
}

// Functions exposed to Lua
static const luaL_reg Module_methods[] =
{
    {"reverse", Reverse},
    {0, 0}
};

static void LuaInit(lua_State* L)
{
    int top = lua_gettop(L);

    // Register lua names
    luaL_register(L, MODULE_NAME, Module_methods);

    lua_pop(L, 1);
    assert(top == lua_gettop(L));
}

dmExtension::Result AppInitializeMyExtension(dmExtension::AppParams* params)
{
    return dmExtension::RESULT_OK;
}

dmExtension::Result InitializeMyExtension(dmExtension::Params* params)
{
    // Init Lua
    LuaInit(params->m_L);
    printf("Registered %s Extension\n", MODULE_NAME);
    return dmExtension::RESULT_OK;
}

dmExtension::Result AppFinalizeMyExtension(dmExtension::AppParams* params)
{
    return dmExtension::RESULT_OK;
}

dmExtension::Result FinalizeMyExtension(dmExtension::Params* params)
{
    return dmExtension::RESULT_OK;
}


// Defold SDK uses a macro for setting up extension entry points:
//
// DM_DECLARE_EXTENSION(symbol, name, app_init, app_final, init, update, on_event, final)

// MyExtension is the C++ symbol that holds all relevant extension data.
// It must match the name field in the `ext.manifest`
DM_DECLARE_EXTENSION(MyExtension, LIB_NAME, AppInitializeMyExtension, AppFinalizeMyExtension, InitializeMyExtension, 0, 0, FinalizeMyExtension)

Zwróć uwagę na makro DM_DECLARE_EXTENSION, które służy do deklarowania różnych punktów wejścia do kodu rozszerzenia. Pierwszy argument symbol musi odpowiadać nazwie podanej w ext.manifest. W tym prostym przykładzie nie ma potrzeby definiować żadnych punktów wejścia update ani on_event, więc w tych miejscach do makra przekazano 0.

Teraz wystarczy zbudować projekt (Project ▸ Build). Spowoduje to wysłanie rozszerzenia do serwera budowania, który wygeneruje własny silnik z nowym rozszerzeniem wbudowanym na stałe. Jeśli serwer budowania napotka jakiekolwiek błędy, pojawi się okno dialogowe z błędami budowania.

Aby przetestować rozszerzenie, utwórz obiekt gry i dodaj do niego komponent skryptu z krótkim kodem testowym:

local s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
local reverse_s = myextension.reverse(s)
print(reverse_s) --> ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba

I to wszystko! Utworzyliśmy w pełni działające natywne rozszerzenie.

Cykl życia rozszerzenia

Jak widzieliśmy wyżej, makro DM_DECLARE_EXTENSION służy do deklarowania różnych punktów wejścia do kodu rozszerzenia:

DM_DECLARE_EXTENSION(symbol, name, app_init, app_final, init, update, on_event, final)

Punkty wejścia pozwalają uruchamiać kod w różnych momentach cyklu życia rozszerzenia:

  • Start silnika
    • Uruchamiają się systemy silnika
    • Rozszerzenie app_init
    • Rozszerzenie init - wszystkie API Defold są już zainicjalizowane. To zalecany moment w cyklu życia rozszerzenia na utworzenie wiązań Lua do kodu rozszerzenia.
    • Inicjalizacja skryptów - wywoływana jest funkcja init() plików skryptów.
  • Pętla silnika
    • Aktualizacja silnika
      • Rozszerzenie update
      • Aktualizacja skryptów - wywoływana jest funkcja update() plików skryptów.
    • Zdarzenia silnika (minimalizacja/maksymalizacja okna itp.)
      • Rozszerzenie on_event
  • Zamykanie silnika (lub ponowne uruchomienie)
    • Finalizacja skryptów - wywoływana jest funkcja final() plików skryptów.
    • Rozszerzenie final
    • Rozszerzenie app_final

Zdefiniowane identyfikatory platform

Następujące identyfikatory są definiowane przez serwer budowania dla każdej odpowiedniej platformy:

  • DM_PLATFORM_WINDOWS
  • DM_PLATFORM_OSX
  • DM_PLATFORM_IOS
  • DM_PLATFORM_ANDROID
  • DM_PLATFORM_LINUX
  • DM_PLATFORM_HTML5

Dzienniki serwera budowania

Dzienniki serwera budowania są dostępne, gdy projekt korzysta z natywnych rozszerzeń. Dziennik serwera budowania (log.txt) jest pobierany razem z własnym silnikiem podczas budowania projektu, przechowywany w pliku .internal/%platform%/build.zip i rozpakowywany także do folderu budowania projektu.

Przykłady rozszerzeń

Portal zasobów Defold także zawiera kilka natywnych rozszerzeń.