Read this manual in English

Нативные расширения

Если вам необходимо пользовательское взаимодействие с внешним программным или аппаратным обеспечением на низком уровне, где Lua недостаточно, Defold SDK позволяет писать расширения для движка на C, C++, Objective C, Java или Javascript, в зависимости от целевой платформы. Типичными случаями использования нативных расширений являются:

  • Взаимодействие с конкретным оборудованием, например, с камерой на мобильных телефонах.
  • Взаимодействие с внешними API низкого уровня, например, API рекламной сети, которые не позволяют взаимодействовать через сетевые API, где можно было бы использовать Luasocket.
  • Высокопроизводительные вычисления и обработка данных.

Платформа для сборки

Defold предоставляет точку входа для нативных расширений с нулевой настройкой и облачным решением для сборки. Любое нативное расширение, разработанное и добавленное в игровой проект, либо напрямую, либо через Library Project, становится частью содержимого проекта. Нет необходимости создавать специальные версии движка и распространять их среди членов команды, это происходит автоматически - любой член команды, который собирает и запускает проект, получит исполняемый файл движка для конкретного проекта со всеми встроенными расширениями.

Облачная сборка

Планировка проекта

Чтобы создать новое расширение, создайте папку в корне проекта. Эта папка будет содержать все настройки, исходный код, библиотеки и ресурсы, связанные с расширением. Конструктор расширений распознает структуру папок и соберет все исходные файлы и библиотеки.

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

ext.manifest
Папка расширения должна содержать файл ext.manifest. Этот файл представляет собой файл формата YAML, который подхватывается конструктором расширений. Минимальный файл манифеста должен содержать название расширения.
src
Эта папка должна содержать все файлы исходного кода.
include
Эта опциональная папка содержит все включаемые файлы.
lib
Эта опциональная папка содержит все скомпилированные библиотеки, от которых зависит расширение. Файлы библиотек должны быть помещены в подпапки с именем platform, или architecure-platform, в зависимости от того, какие архитектуры поддерживаются вашими библиотеками.

Supported platforms are ios, android, osx, win32, linux, web.

Supported arc-platform pairs are arm64-ios, x86_64-ios, armv7-android, arm64-android, arm64-osx, x86_64-osx, x86-win32, x86_64-win32, x86_64-linux, js-web and wasm-web.

manifests
Эта опциональная папка содержит дополнительные файлы, используемые в процессе сборки или комплектации. Подробнее см. ниже.
res
Эта опциональная папка содержит любые дополнительные ресурсы, от которых зависит расширение. Файлы ресурсов должны быть помещены в подпапки, названные по platform, или architecure-platform, как и подпапки “lib”. Также допускается подпапка common, содержащая файлы ресурсов, общие для всех платформ.

Файлы манифеста

Опциональная папка manifests расширения содержит дополнительные файлы, используемые в процессе сборки и комплектации. Файлы должны быть помещены во вложенные папки, названные по платформе:

  • android - В эту папку помещается файл-заглушка манифеста, который будет объединен с основным приложением (как описано здесь). Папка также может содержать файл build.gradle с зависимостями, которые должны быть разрешены Gradle (пример). Наконец, папка может также содержать ноль или более файлов ProGuard (экспериментально).
  • ios - Эта папка принимает файл-заглушку манифеста, который должен быть объединен с основным приложением (как описано здесь).
  • osx - Эта папка принимает файл-заглушку манифеста, который должен быть объединен с основным приложением (как описано здесь).
  • web - Эта папка принимает файл-заглушку манифеста, который должен быть объединен с основным приложением (как описано здесь).

Совместное использование расширения

Расширения рассматриваются так же, как и любые другие ассеты в вашем проекте, и ими можно делиться таким же образом. Если папка нативного расширения добавлена в качестве папки библиотеки, она может быть совместно использована другими пользователями как зависимость. Для получения дополнительной информации обратитесь к руководству Library project manual.

Простой пример расширения

Давайте создадим очень простое расширение. Сначала создадим новую корневую папку myextension и добавим в нее файл ext.manifest, содержащий имя расширения “MyExtension”. Обратите внимание, что имя является символом C++ и должно совпадать с первым аргументом DM_DECLARE_EXTENSION (см. ниже).

Манифест

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

Расширение состоит из одного файла C++, myextension.cpp, который создается в папке “src”.

C++ файл

Исходный файл расширения содержит следующий код:

// myextension.cpp
// Определения расширения lib
#define LIB_NAME "MyExtension"
#define MODULE_NAME "myextension"

// включаем Defold SDK
#include <dmsdk/sdk.h>

static int Reverse(lua_State* L)
{
    // Количество ожидаемых элементов в стеке Lua после того, 
    // как эта структура выйдет из области видимости
    DM_LUA_STACK_CHECK(L, 1);

    // Проверка и получение строки параметра из стека
    char* str = (char*)luaL_checkstring(L, 1);

    // Развернем строку
    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;
    }

    // Поместим развернутую строку в стек
    lua_pushstring(L, str);

    // Вернем значение 1
    return 1;
}

// Функции, открытые для Lua
static const luaL_reg Module_methods[] =
{
    {"reverse", Reverse},
    {0, 0}
};

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

    // Регистрация имен Lua
    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)
{
    // Инициализация 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 использует макрос для установки точек входа расширения:
//
// DM_DECLARE_EXTENSION(symbol, name, app_init, app_final, init, update, on_event, final)

// MyExtension это символ C++, который содержит все соответствующие данные расширения.
// Он должно совпадать с полем имени в `ext.manifest`
DM_DECLARE_EXTENSION(MyExtension, LIB_NAME, AppInitializeMyExtension, AppFinalizeMyExtension, InitializeMyExtension, 0, 0, FinalizeMyExtension)

Обратите внимание на макрос DM_DECLARE_EXTENSION, который используется для объявления различных точек входа в код расширения. Первый аргумент символ должен соответствовать имени, указанному в ext.manifest. Для этого простого примера нет необходимости в точках входа “update” или “on_event”, поэтому в этих местах макросу присваивается 0.

Теперь осталось только создать проект (Project ▸ Build). Это загрузит расширение в конструктор расширений, который запустит движок с включенным в него новым расширением. Если сборщик столкнется с какими-либо ошибками, появится диалог с ошибками сборки.

Чтобы протестировать расширение, создайте игровой объект и добавьте компонент скрипта с некоторым тестовым кодом:

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

Вот и все! Мы создали полностью работающее нативное расширение.

Жизненный цикл расширения

Как мы видели выше, макрос DM_DECLARE_EXTENSION используется для объявления различных точек входа в код расширения:

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

Точки входа позволят вам запускать код в различных точках жизненного цикла расширения:

  • Запуск движка
    • Запуск систем движка
    • Расширение app_init
    • Расширение init - Все API Defold были инициализированы. Это рекомендуемый момент жизненного цикла расширения, когда создаются привязки Lua к коду расширения.
    • Запуск скрипта - Вызываются функции init() файлов скриптов.
  • Цикл движка
    • Обновление движка
      • Расширение update
      • Обновление скрипта - Вызывается функция update() файлов скриптов.
    • События движка (window minimize/maximize etc)
      • Расширение on_event
  • Выключение движка (или перезагрузка)
    • Завершения скрипта - Вызывается функция final() файлов скриптов.
    • Расширение final
    • Расширение app_final

Определенные идентификаторы платформы

Следующие идентификаторы определяются разработчиком на каждой соответствующей платформе:

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

Построение журналов сервера

Журналы сервера сборки доступны, если проект использует нативные расширения. Журнал сервера сборки (log.txt) загружается вместе с движком при сборке проекта и хранится в файле .internal/%platform%/build.zip, а также распаковывается в папку сборки вашего проекта.

Файл ext.manifest

Помимо названия расширения, файл манифеста может содержать флаги компиляции для конкретной платформы, флаги компоновки, библиотеки и фреймворки. Если файл ext.manifest не содержит сегмента “platforms”, или платформа отсутствует в списке, платформа, для которой вы собираете пакет, будет собрана, но без дополнительных флагов.

Вот пример:

name: "AdExtension"

platforms:
    arm64-ios:
        context:
            frameworks: ["CoreGraphics", "CFNetwork", "GLKit", "CoreMotion", "MessageUI", "MediaPlayer", "StoreKit", "MobileCoreServices", "AdSupport", "AudioToolbox", "AVFoundation", "CoreGraphics", "CoreMedia", "CoreMotion", "CoreTelephony", "CoreVideo", "Foundation", "GLKit", "JavaScriptCore", "MediaPlayer", "MessageUI", "MobileCoreServices", "OpenGLES", "SafariServices", "StoreKit", "SystemConfiguration", "UIKit", "WebKit"]
            flags:      ["-stdlib=libc++"]
            linkFlags:  ["-ObjC"]
            libs:       ["z", "c++", "sqlite3"]
            defines:    ["MY_DEFINE"]

    armv7-ios:
        context:
            frameworks: ["CoreGraphics", "CFNetwork", "GLKit", "CoreMotion", "MessageUI", "MediaPlayer", "StoreKit", "MobileCoreServices", "AdSupport", "AudioToolbox", "AVFoundation", "CoreGraphics", "CoreMedia", "CoreMotion", "CoreTelephony", "CoreVideo", "Foundation", "GLKit", "JavaScriptCore", "MediaPlayer", "MessageUI", "MobileCoreServices", "OpenGLES", "SafariServices", "StoreKit", "SystemConfiguration", "UIKit", "WebKit"]
            flags:      ["-stdlib=libc++"]
            linkFlags:  ["-ObjC"]
            libs:       ["z", "c++", "sqlite3"]
            defines:    ["MY_DEFINE"]

Разрешенные ключи

Допустимыми ключами для компиляционных флагов, специфичных для конкретной платформы, являются:

  • frameworks - Фреймворки Apple, которые необходимые для сборки (iOS и OSX)
  • flags - Флаги, которые должны быть переданы компилятору
  • linkFlags - Флаги, которые должны быть переданы компоновщику
  • libs - Библиотеки, необходимые включения при компоновке
  • defines - Определения установки при сборке
  • aaptExtraPackages - Отдельное имя пакета, которое должно быть сгенерировано (Android)
  • aaptExcludePackages - Регулярные выражения (или точные названия) пакетов для исключения (Android
  • aaptExcludeResourceDirs - Регулярные выражения (или точные названия) каталоги ресурсов для исключения (Android)

Примеры расширений

Портал ассетов в Defold, также содержит много нативных расширений.