defold-native-extension-editing について
Defold のネイティブ拡張を C++、Objective-C、JavaScript で開発するためのスキルです。拡張の構成、dmSDK の利用、Lua コールバック、エンジンの参考資料を扱います。
主な機能
- 01拡張フォルダーとマニフェストの構成
- 02C++ と JavaScript のエントリーポイント
- 03dmSDK の直接利用パターン
- 04非同期 Lua コールバックのライフサイクル
- 05エンジンソースと API 参照
活用シーン
- 01Defold ネイティブ拡張のひな形作成
- 02C++ からゲームオブジェクトへアクセス
- 03非同期イベント後の Lua コールバック
- 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 ARMx86_64-osx/- macOS Intelarm64-ios/- iOS ARM64armv7-android/、arm64-android/- Androidjs-web/、wasm-web/- HTML5
Lua API 呼び出しより dmSDK を優先する
Lua API のラッパーを呼び出す代わりに、常に dmSDK の C++ 関数を直接使用してください(例:lua_getglobal(L, "go") + lua_call)。dmSDK は、ほとんどの Lua ゲームオブジェクト操作に対して効率的な 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 エンジンのソースを参考にする
https://github.com/defold/defold/tree/dev/engine にある Defold エンジンのソースには、dmSDK の使用例が多数含まれています。ネイティブ拡張を実装する際は、特に次の箇所を参照して、正しい API の使用パターンを積極的に調べてください。
engine/gameobject/— ゲームオブジェクトの操作(dmGameObject::*)engine/gamesys/— コンポーネントシステム(スプライト、コリジョン、ファクトリーなど)engine/script/— スクリプトブリッジのユーティリティ(dmScript::*)engine/dlib/— 数学、ハッシュ、ロギング、バッファー(dmVMath::*、dmLog*、dmBuffer::*)engine/render/— レンダリングパイプライン(dmRender::*)
さらに、https://github.com/defold/extension-spine/tree/main/defold-spine にある Spine 拡張は、dmSDK の使い方を示す優れた実例です。コンポーネントの登録、ゲームオブジェクトの操作、レンダリング、リソース管理、スクリプトバインディングについて説明しています。
特定の API ドキュメントを取得するには、Librarian ツールまたは defold-api-fetch スキルを使用してください。API ドキュメントだけでは不十分な場合は、GitHub でエンジンのソースと extension-spine を確認し、実際の使用例を参照してください。
コードのフォーマット
ネイティブ拡張を扱う際は、一貫した C/C++ のフォーマットを適用するため、プロジェクトのルートに .clang-format があることを確認してください。存在しない場合は、Defold リポジトリから取得してください。
https://raw.githubusercontent.com/defold/defold/refs/heads/dev/.clang-format
重要:この .clang-format は Defold 公式リポジトリのもので、エンジンとのコードスタイルの一貫性を保ちます。
API リファレンス
C++ SDK のドキュメントには、C++ Native Extension APIs セクション(dmExtension、dmScript、dmBuffer など)を含む defold-api-fetch スキルを使用してください。