extensions.md 17 KB


title: Написание нативных расширений для Defold

brief: Это руководство объясняет, как написать нативное расширение для игрового движка Defold и как скомпилировать его с помощью облачного конструктора с нулевой настройкой.

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

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

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

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

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

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

Сервер сборки предоставляется бесплатно и без каких-либо ограничений на использование. Сервер размещён в Европе, а URL, на который отправляется нативный код, настраивается в настройках редактора или через опцию командной строки --build-server инструмента bob. Если вы хотите развернуть собственный сервер, пожалуйста, следуйте этим инструкциям.

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

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

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

ext.manifest : Папка расширения должна содержать файл ext.manifest. Этот файл представляет собой конфигурационный файл с флагами и директивами, используемыми при сборке одного расширения. Определение формата файла можно найти в руководстве по манифесту расширения. Минимальный файл манифеста должен содержать название расширения.

src : Эта папка должна содержать все файлы исходного кода.

include : Эта опциональная папка содержит все включаемые файлы.

lib : Эта опциональная папка содержит все скомпилированные библиотеки, от которых зависит расширение. Файлы библиотек должны быть помещены в подпапки с именем platform, или architecure-platform, в зависимости от того, какие архитектуры поддерживаются вашими библиотеками.

:platforms

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

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

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

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

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

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

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

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

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

Манифест

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

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

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

// 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, а также распаковывается в папку сборки вашего проекта.

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

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