mcpField
D
defold-native-extension-editing

defold-native-extension-editing

github.com/indiesoftby开发10448 次安装27 周前更新
代码仓库

关于 defold-native-extension-editing

这是一份 Defold 原生扩展开发参考技能,涵盖 C++、Objective-C 和 JavaScript。内容包括扩展目录与清单、dmSDK 用法、Lua 回调及引擎参考资料。

主要功能

  1. 01扩展目录与清单结构
  2. 02C++ 和 JavaScript 原生入口
  3. 03直接使用 dmSDK 的模式
  4. 04异步 Lua 回调生命周期
  5. 05引擎源码与 API 参考

适用场景

  1. 01搭建 Defold 原生扩展
  2. 02在 C++ 中访问游戏对象
  3. 03异步事件后调用 Lua 回调
  4. 04核对平台库目录布局

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/indiesoftby/defold-agent-config --skill defold-native-extension-editing

安装后在 Claude Code 中输入/defold-native-extension-editing确认已加载。

详细介绍

Defold 原生扩展结构

原生扩展是一个包含 ext.manifest 和原生代码的文件夹,用于扩展 Defold 引擎。

必需的结构

my_extension/
├── ext.manifest           # Extension manifest (YAML)
├── src/                   # Source code (C/C++/ObjC/JS)
│   ├── extension.cpp      # Main extension entry point
│   └── ...
├── include/               # Public headers (optional)
├── lib/                   # Platform libraries (optional)
│   ├── x86_64-win32/
│   ├── x86_64-linux/
│   ├── arm64-ios/
│   └── ...
├── api/                   # Script API definitions (optional)
│   └── my_extension.script_api
└── res/                   # Platform resources (optional)
    └── android/

ext.manifest 格式

name: "MyExtension"
platforms:
  x86_64-win32:
    context:
      defines: ["MY_DEFINE"]
      libs: ["user32"]
  arm64-ios:
    context:
      frameworks: ["UIKit"]

扩展入口点(C++)

#define EXTENSION_NAME MyExtension
#define LIB_NAME "MyExtension"
#define MODULE_NAME "myextension"

#include <dmsdk/sdk.h>

static int MyFunction(lua_State* L)
{
    // Implementation
    return 0;
}

static const luaL_reg Module_methods[] =
{
    {"my_function", MyFunction},
    {0, 0}
};

static void LuaInit(lua_State* L)
{
    int top = lua_gettop(L);
    luaL_register(L, MODULE_NAME, Module_methods);
    lua_pop(L, 1);
    assert(top == lua_gettop(L));
}

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

static dmExtension::Result InitializeMyExtension(dmExtension::Params* params)
{
    LuaInit(params->m_L);
    return dmExtension::RESULT_OK;
}

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

DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, AppInitializeMyExtension, 0, InitializeMyExtension, 0, 0, FinalizeMyExtension)

HTML5/JavaScript 扩展

对于 Web 平台,请在 src/ 中使用 .js 文件:

var MyExtension = {
    MyFunction: function() {
        // Implementation
    }
};
mergeInto(LibraryManager.library, MyExtension);

平台库路径(lib/)

  • x86_64-win32/ - Windows 64 位
  • x86-win32/ - Windows 32 位
  • x86_64-linux/ - Linux 64 位
  • arm64-osx/ - macOS ARM
  • x86_64-osx/ - macOS Intel
  • arm64-ios/ - iOS ARM64
  • armv7-android/、arm64-android/ - Android
  • js-web/、wasm-web/ - HTML5

优先使用 dmSDK,而非 Lua API 调用

始终使用直接的 C++ dmSDK 函数,而不是调用 Lua API 封装(例如 lua_getglobal(L, "go") + lua_call)。对于大多数 Lua 游戏对象操作,dmSDK 都提供了高效的 C++ 等效函数。

dmSDK 关键用法模式

从 Lua 获取游戏对象实例:

// Get the calling script's game object instance
dmGameObject::HInstance caller = dmScript::CheckGOInstance(L);

// Get the collection from any instance
dmGameObject::HCollection collection = dmGameObject::GetCollection(caller);

// Resolve a hash identifier (from Lua stack) to an instance
dmhash_t id = dmScript::CheckHash(L, index);
dmGameObject::HInstance target = dmGameObject::GetInstanceFromIdentifier(collection, id);

直接在 C++ 中操作游戏对象:

// Position (uses dmVMath::Point3)
dmVMath::Point3 pos = dmGameObject::GetPosition(instance);
dmGameObject::SetPosition(instance, dmVMath::Point3(x, y, z));

// Rotation (uses dmVMath::Quat)
dmVMath::Quat rot = dmGameObject::GetRotation(instance);
dmGameObject::SetRotation(instance, rot);

// Scale
float scale = dmGameObject::GetUniformScale(instance);
dmGameObject::SetScale(instance, scale);
dmGameObject::SetScale(instance, dmVMath::Vector3(sx, sy, sz));

数学类型——创建一次,在循环中重复使用:

dmVMath::Point3 pos(0.0f, 0.0f, 0.0f);
for (int i = 0; i < count; ++i)
{
    pos.setX(computed_x);
    pos.setY(computed_y);
    dmGameObject::SetPosition(instance, pos);
}

通过 dmScript 提取 Lua 类型:

dmVMath::Vector3* v = dmScript::CheckVector3(L, index);
dmVMath::Vector4* v4 = dmScript::CheckVector4(L, index);
dmVMath::Quat* q = dmScript::CheckQuat(L, index);
dmVMath::Matrix4* m = dmScript::CheckMatrix4(L, index);
dmhash_t hash = dmScript::CheckHash(L, index);
dmhash_t hash = dmScript::CheckHashOrString(L, index);
通过 dmScript 异步调用 Lua 回调

当原生扩展需要异步调用用户提供的 Lua 回调(例如,在平台事件、计时器或异步操作之后)时,请使用 dmScript::LuaCallbackInfo。参考:https://github.com/indiesoftby/defold-page-visibility/tree/main/page_visibility/src

从 Lua 存储回调:

static dmScript::LuaCallbackInfo* g_Callback = 0;

static int SetCallback(lua_State* L)
{
    DM_LUA_STACK_CHECK(L, 0);

    // Destroy previous callback to avoid leaks
    if (g_Callback)
    {
        dmScript::DestroyCallback(g_Callback);
        g_Callback = 0;
    }

    if (lua_isfunction(L, 1))
    {
        g_Callback = dmScript::CreateCallback(L, 1);
    }

    return 0;
}

稍后调用回调(来自任何原生事件):

static void OnAsyncEvent(int result_code)
{
    if (!g_Callback || !dmScript::IsCallbackValid(g_Callback))
        return;

    lua_State* L = dmScript::GetCallbackLuaContext(g_Callback);
    DM_LUA_STACK_CHECK(L, 0);

    if (!dmScript::SetupCallback(g_Callback))
    {
        dmScript::DestroyCallback(g_Callback);
        g_Callback = 0;
        return;
    }

    // Push arguments after self (self is already on stack from SetupCallback)
    lua_pushnumber(L, result_code);

    dmScript::PCall(L, 2, 0); // 2 = self + 1 user argument

    dmScript::TeardownCallback(g_Callback);
}

在 Finalize 中清理(防止扩展卸载时发生泄漏):

static dmExtension::Result FinalizeMyExtension(dmExtension::Params* params)
{
    if (g_Callback)
    {
        dmScript::DestroyCallback(g_Callback);
        g_Callback = 0;
    }
    return dmExtension::RESULT_OK;
}

生命周期: CreateCallback → SetupCallback → PCall → TeardownCallback → DestroyCallback(完成后)。

对于一次性回调,请在 TeardownCallback 之后立即调用 DestroyCallback。对于持久监听器,请保留回调,仅在替换回调或 finalize 时销毁。

何时可以接受 Lua API 调用

仅在以下情况下,才退回使用 lua_getglobal/lua_call 模式:

  • 没有 dmSDK C++ 等效函数(例如 go.animate、向任意 URL 执行 msg.post)
  • 需要在不使用 dmScript::LuaCallbackInfo 模式的情况下调用用户定义的 Lua 回调(少见)

将 Defold 引擎源代码作为参考

Defold 引擎源代码位于 https://github.com/defold/defold/tree/dev/engine,其中包含大量 dmSDK 用法示例。实现原生扩展时,请主动研究引擎源代码,以查找正确的 API 用法模式,尤其是:

  • engine/gameobject/ — 游戏对象操作(dmGameObject::*)
  • engine/gamesys/ — 组件系统(精灵、碰撞、工厂等)
  • engine/script/ — 脚本桥接实用工具(dmScript::*)
  • engine/dlib/ — 数学、哈希、日志记录、缓冲区(dmVMath::*、dmLog*、dmBuffer::*)
  • engine/render/ — 渲染管线(dmRender::*)

此外,Spine 扩展 https://github.com/defold/extension-spine/tree/main/defold-spine 是 dmSDK 用法的绝佳实际参考——它展示了组件注册、游戏对象操作、渲染、资源管理和脚本绑定。

使用 Librarian 工具或 defold-api-fetch skill 获取特定 API 文档。如果 API 文档不够详尽,请在 GitHub 上浏览引擎源代码和 extension-spine,了解实际用法。

代码格式

使用原生扩展时,请确保项目根目录中存在 .clang-format,以保持 C/C++ 格式一致。如果缺少,请从 Defold 仓库获取:

https://raw.githubusercontent.com/defold/defold/refs/heads/dev/.clang-format

重要:此 .clang-format 来自 Defold 官方仓库,可确保代码风格与引擎保持一致。

API 参考

对于 C++ SDK 文档,请使用 defold-api-fetch skill 中的 C++ Native Extension APIs 部分(dmExtension、dmScript、dmBuffer 等)。

评论

支持 Markdown