Если вам необходимо пользовательское взаимодействие с внешним программным или аппаратным обеспечением на низком уровне, где Lua недостаточно, Defold SDK позволяет писать расширения для движка на C, C++, Objective C, Java или Javascript, в зависимости от целевой платформы. Типичными случаями использования нативных расширений являются:
Defold предоставляет точку входа для нативных расширений с нулевой настройкой и облачным решением для сборки. Любое нативное расширение, разработанное и добавленное в игровой проект, либо напрямую, либо через Library Project, становится частью содержимого проекта. Нет необходимости создавать специальные версии движка и распространять их среди членов команды, это происходит автоматически - любой член команды, который собирает и запускает проект, получит исполняемый файл движка для конкретного проекта со всеми встроенными расширениями.
Чтобы создать новое расширение, создайте папку в корне проекта. Эта папка будет содержать все настройки, исходный код, библиотеки и ресурсы, связанные с расширением. Конструктор расширений распознает структуру папок и соберет все исходные файлы и библиотеки.
myextension/
│
├── ext.manifest
│
├── src/
│
├── include/
│
├── lib/
│ └──[platforms]
│
├── manifests/
│ └──[platforms]
│
└── res/
└──[platforms]
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
.
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”.
Исходный файл расширения содержит следующий код:
// 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()
файлов скриптов.on_event
final()
файлов скриптов.final
app_final
Следующие идентификаторы определяются разработчиком на каждой соответствующей платформе:
Журналы сервера сборки доступны, если проект использует нативные расширения. Журнал сервера сборки (log.txt
) загружается вместе с движком при сборке проекта и хранится в файле .internal/%platform%/build.zip
, а также распаковывается в папку сборки вашего проекта.
Помимо названия расширения, файл манифеста может содержать флаги компиляции для конкретной платформы, флаги компоновки, библиотеки и фреймворки. Если файл 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
- Регулярные выражения (или точные названия) пакетов для исключения (AndroidaaptExcludeResourceDirs
- Регулярные выражения (или точные названия) каталоги ресурсов для исключения (Android)Портал ассетов в Defold, также содержит много нативных расширений.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB