ランタイム Stingray ゲーム エンジンに新機能を追加するプラグインを記述したり、Lua ゲームプレイ API の新しいオブジェクトや関数を公開したり、外部ミドルウェア SDK や他のライブラリを統合するなどの操作を行うことができます。C で記述することに慣れているユーザは、フローや Lua を使用しないで、C でゲームプレイ コード全体を記述することもできます。
このセクションでは、独自のプラグインでエンジンを拡張するためのヒントをいくつか示します。
エンジン プラグインの操作において最も重要なリソースは、サンプル プラグインのリポジトリです。これらのサンプルをまだ持っていない場合は、「サンプル プラグイン」を参照してください。
最初に samples/simple_plugin のサンプルを参照してください。これは、エンジンとプラグイン間の相互作用の基本的な骨組みを示す最小限のプラグインです(例については、次のセクションを参照)。
次に、samples/bigger_plugin に移動します。これらのサンプルでは新しいリソース タイプの作成方法を示し、このリソースをランタイム データにコンパイルする方法を定義し、ランタイムにこのリソース内のデータにアクセスするようプロジェクト Lua 関数を設定します。
プラグインは、stingray-plugin リポジトリをベースにすることを強くお勧めします。このリポジトリには、Visual Studio 2015 を使用して .dll にコードをコンパイルするために必要になるすべてのものが既に設定されているため、より簡単に開始することができます。
SDK ヘッダ ファイルが必要になります。これらのファイルはサンプル リポジトリの stingray_sdk 内、または Stingray インストール フォルダ内の plugin_sdk 内にあります。 (stingray-plugin リポジトリを使用する場合は、これらのヘッダが自動的に取得されるため、意識する必要はありません。)
リファレンス ドキュメントには、SDK ヘッダ ファイルで定義されている API の参照可能なガイドが含まれています。
エンジンのプラグイン インタフェースは、エンジンとプラグイン間で相互作用のパターンに一貫性が維持されるよう構築されています。これらのすべての相互作用は、共通の API 定義セットに基づいています。双方がクエリーを送信して、相手がサポートする API を取得します。
まずエンジンはあらかじめ設定された get_plugin_api 関数を呼び出して、プラグインにその API をクエリーします。プラグインは、それらの共有プラグインの API 定義から実装する、すべての関数へのポインタが含まれる構造体をエンジンに提供します。
エンジンはプラグインで定義されたこれらの関数を呼び出すときに、独自の get_engine_api 関数ポインタを渡します。プラグインはこの関数を呼び出してエンジンにクエリーを行い、プラグインにエンジンのサービスを公開する API を取得します。次にプラグインは、それらの返された API の関数を呼び出して、エンジンに必要なタスクを実行させることができます。
次のイメージは、このワークフローの概要を示しています。
次に、プラグインをランタイム エンジンと統合するための基本的な手順を示します。すべてのサンプル プラグインでこれらの基本操作を行います。説明用のコードを参照してください。
plugin_api.h ファイルをインクルードします。
#include "engine_plugin_api/plugin_api.h"
上図のように、エンジンとプラグインが互いに呼び出しあう場合は、共通の ID セットを使用してこれらが要求している API を特定します。各 ID は常に plugin_api.h ファイルで定義された特定の構造体に対応しています。したがって、ID と API の定義を一致させるには、エンジンとプラグインがこのファイルをインクルードする必要があります。
次の署名を使用して関数を定義します。
__declspec(dllexport) void *get_plugin_api(unsigned api_id)
プラグイン インタフェースは C++ でなく C を使用して、各バージョンの C++ と各コンパイラ間で ABI の非互換性が生じる問題を回避します。C++ を使用してプラグインをコンパイルする場合は、次のように get_plugin_api 関数を extern C ブロック内でラップすることができます。
extern "C" { __declspec(dllexport) void *get_plugin_api(unsigned api) { ... } }
こうすると、コンパイルされた .dll 内で関数名がめちゃくちゃにならなくなります。
エンジンはプラグインの get_plugin_api 関数を呼び出すたびに、プラグインが提供するインタフェースを識別する PluginApiId を渡します。プラグインが要求されたインタフェースをサポートするように設定する場合は、get_plugin_api の実装が応答して、この要求された API と一致する構造体の新しいインスタンスを作成する必要があります。プラグインがサポートするこのインタフェース内の関数ごとに、インスタンス内のこの関数のポインタを、プラグインで記述された関数に設定する必要があります。サポートするすべての関数を設定したら、構造体を返します。
処理するメインのプラグイン API は、PluginApi 構造体を識別する PLUGIN_API_ID によって識別されます。この例では、 PluginApi 構造体に setup_game() 関数のカスタム実装へのポインタを設定して、PLUGIN_API_ID を送信するエンジンに応答します。
__declspec(dllexport) void *get_plugin_api(unsigned api_id) { if (api_id == PLUGIN_API_ID) { static struct PluginApi api = {0}; api.setup_game = setup_game; return &api; } return 0; }
エンジンは返されたポインタを、対応する PluginApi 構造体のポインタに変換します。次に、この構造体を使用して定義済みの関数に応答します。
エンジンが要求する他のプラグイン API がそのほかにもいくつかありますが(RENDER_CALLBACKS_PLUGIN_API_ID など)、それらは将来のバージョンで追加される予定です。
これまで、ライフスパン内の特定の時点でプラグインを呼び出すようにエンジンを設定してきました。ところが、通常は相互作用を双方向で行う必要があります。たとえば、プラグイン コードからエンジンを呼び出して、ワールドのロード、ユニットのスポーン、リソースのコンパイル、一部のデータのレンダリングなどの操作を行うようエンジンに要求する必要があります。
一般的なパターンでは、エンジンが提供する get_engine_api 関数をプラグインが呼び出して、必要な任意のサービス API をエンジンに対して要求します。各呼び出し内で PluginApiId 列挙内の値を渡して、必要なインタフェースをエンジンに指定します。プラグインは返された構造体を変数にキャッシュし、いつでもこれを使用してエンジンの操作を行うことができます。
たとえば、単純なプラグインでは、setup_game 関数のこの実装を使用し、エンジンに対して LuaApi インタフェースを要求します。プラグインはこのインタフェース内で定義されたいずれかの関数を使用して、エンジンの Lua 環境と相互作用します。この場合は、プロジェクトの Lua スクリプトから呼び出すことができる新しい関数を stingray.SimplePlugin.test() として公開します。
struct LuaApi *_lua; static void setup_game(GetApiFunction get_engine_api) { _lua = get_engine_api(LUA_API_ID); _lua->add_module_function("SimplePlugin", "test", test); }
プラグインを .dll ファイルにコンパイルします。
C で開発することに慣れていない場合は、この作業が面倒になることがあります。独自の Visual Studio プロジェクトですべてが設定されているサンプル プラグインから作業を開始することをお勧めします。
x64 プラットフォームをターゲットとする必要があります。また、動的にリンクされたプラグインは、現在、Windows でのみサポートされています。「アルファ SDK の制限事項」を参照してください。
.dll をロードするためのエンジンを取得します。次の「プラグインの DLL をロードする」を参照してください。
エンジンとプラグイン間にこの基本的な統合レベルを設定したら、メインの PluginApi インタフェースから実装できる他の関数や、エンジンがプラグイン用に提供する他のサービス API を調べて、さらに詳細に設定することができます。「便利なエンジン プラグイン インタフェース」を参照してください。
プラグインの .dll をエンジンで自動的にロードするには、.stingray_plugin 記述ファイルに拡張機能 runtime_libraries を設定するのが最も簡単な方法です。エディタにプラグインがロードされると、エディタから起動したあらゆるエンジン インスタンス(テスト レベルまたは実行プロジェクトを使用)は、プラグインに対応する設定を自動的にロードします。
注: runtime_libraries 拡張機能は、.dll ファイルを Deployer パネルで作成する配置済みスタンドアロン ビルドにパッケージ化しません。配置するときに、.dll ファイルをビルド出力フォルダ内の plugins フォルダに手動でコピーするか、または配置中に自動的にコピーする必要のあるファイルを識別するマニフェスト ファイルにプラグインを追加する必要があります。詳細については、「プラグインの配布とインストール」を参照してください。
この拡張機能は、次のパラメータを受け入れます。
runtime_libraries = [ { name = "my_engine_plugin" paths = { win32 = { dev = "binaries/engine/win64/dev/my_engine_plugin_w64_dev.dll" debug = "binaries/engine/win64/debug/my_engine_plugin_w64_debug.dll" release = "binaries/engine/win64/release/my_engine_plugin_w64_release.dll" } } }
name
ライブラリのセットを識別できるような分かりやすい名前を付けます。この名前は、PluginApi::get_name() の実装でプラグインが返す名前と一致する必要があります。
paths
サポートされている各ターゲット プラットフォームおよび異なるそれぞれのエンジンのビルド環境設定のライブラリへのパスを指定します。エディタがこのリストのいずれかのキーと一致するターゲット プラットフォームのエンジンを起動し、このエンジンのビルド環境設定がそのプラットフォームのいずれかのキーと一致すると、エディタはその環境設定に対応する .dll ファイルをロードするようにエンジンを設定します。
何らかの理由でプラグインが複数の .dll ファイルをロードする必要がある場合は、runtime_libraries リストに複数のオブジェクトを含めることができます。
ダイナミック プラグインをサポートするプラットフォーム(現在は Windows のみ)で、エンジンは起動時に .dll ファイルをチェックするフォルダのリストを保持します。プラグインの .dll は、エンジンが自動的にロードできるようにこれらのフォルダのいずれかに格納されている必要があります。
既定では、プラグインのフォルダのリストには、エンジンの実行可能ファイルの下に plugins フォルダのみが含まれています。この場所に、プラグインの .dll ファイルをコピーすることができます。
エンジンの起動時に --plugin-dir <folder> コマンドライン パラメータを渡すことで、このリストにパスを追加することができます。「Stingray エンジンのコマンドライン リファレンス」を参照してください。
ランタイムで他のパスからプラグインを動的にロード(およびロード解除)することができます。
Lua では、PluginManager.load_plug n() や PluginManager.load_relative_plugin() などの、「stingray.PluginManager ネームスペース リファレンス」の API の関数を使用します。
C/C++ では、別のプラグインを動的にロードする必要があるプラグインがロードされている場合、PluginManagerApi::load_relative_plugin() を使用します。