エンジンを拡張する

ランタイム Stingray ゲーム エンジンに新機能を追加するプラグインを記述したり、Lua ゲームプレイ API の新しいオブジェクトや関数を公開したり、外部ミドルウェア SDK や他のライブラリを統合するなどの操作を行うことができます。C で記述することに慣れているユーザは、フローや Lua を使用しないで、C でゲームプレイ コード全体を記述することもできます。

このセクションでは、独自のプラグインでエンジンを拡張するためのヒントをいくつか示します。

その他の関連資料

エンジンとプラグインの相互作用

エンジンのプラグイン インタフェースは、エンジンとプラグイン間で相互作用のパターンに一貫性が維持されるよう構築されています。これらのすべての相互作用は、共通の API 定義セットに基づいています。双方がクエリーを送信して、相手がサポートする API を取得します。

次のイメージは、このワークフローの概要を示しています。

スタートアップ

次に、プラグインをランタイム エンジンと統合するための基本的な手順を示します。すべてのサンプル プラグインでこれらの基本操作を行います。説明用のコードを参照してください。

  1. plugin_api.h ファイルをインクルードします。

    #include "engine_plugin_api/plugin_api.h"
    

    上図のように、エンジンとプラグインが互いに呼び出しあう場合は、共通の ID セットを使用してこれらが要求している API を特定します。各 ID は常に plugin_api.h ファイルで定義された特定の構造体に対応しています。したがって、ID と API の定義を一致させるには、エンジンとプラグインがこのファイルをインクルードする必要があります。

  2. 次の署名を使用して関数を定義します。

    __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 内で関数名がめちゃくちゃにならなくなります。

  3. エンジンはプラグインの 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 など)、それらは将来のバージョンで追加される予定です。

  4. これまで、ライフスパン内の特定の時点でプラグインを呼び出すようにエンジンを設定してきました。ところが、通常は相互作用を双方向で行う必要があります。たとえば、プラグイン コードからエンジンを呼び出して、ワールドのロード、ユニットのスポーン、リソースのコンパイル、一部のデータのレンダリングなどの操作を行うようエンジンに要求する必要があります。

    一般的なパターンでは、エンジンが提供する 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);
    }
  5. プラグインを .dll ファイルにコンパイルします。

    C で開発することに慣れていない場合は、この作業が面倒になることがあります。独自の Visual Studio プロジェクトですべてが設定されているサンプル プラグインから作業を開始することをお勧めします。

    x64 プラットフォームをターゲットとする必要があります。また、動的にリンクされたプラグインは、現在、Windows でのみサポートされています。「アルファ SDK の制限事項」を参照してください。

  6. .dll をロードするためのエンジンを取得します。次の「プラグインの DLL をロードする」を参照してください。

エンジンとプラグイン間にこの基本的な統合レベルを設定したら、メインの PluginApi インタフェースから実装できる他の関数や、エンジンがプラグイン用に提供する他のサービス API を調べて、さらに詳細に設定することができます。「便利なエンジン プラグイン インタフェース」を参照してください。

プラグインの DLL をロードする

プラグインの .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 は、エンジンが自動的にロードできるようにこれらのフォルダのいずれかに格納されている必要があります。