GDNative Cの例

はじめに

このチュートリアルでは、GDNativeモジュールの作成に最低限必要なものを紹介します。これがGDNativeの世界への出発点になるはずです。このチュートリアルの内容を理解することは、この後のすべてを理解するのに役立ちます。

始める前に、以下で説明するサンプルオブジェクトのソースコードをGDNative-demos repositoryからダウンロードできます。

このサンプルプロジェクトには、コンパイルを少し簡単にするSConstructファイルも含まれていますが、このチュートリアルでは、プロセスを理解するために手動で作業を行います。

GDNativeを使用すると、PluginScriptARVRInterfaceGDNativeなどのインターフェースを利用して、Godotに追加する複数のタイプの要素を作成できます。このチュートリアルでは、NativeScriptモジュールの作成について見ていきます。 NativeScriptを使用すると、GDScriptファイルを作成するのと同様の方法でCまたはC++でロジックを作成できます。このGDScriptに相当するCを作成します。

extends Reference

var data

func _ready():
    data = "World from GDScript!"

func get_data():
    return data

今後のチュートリアルでは、他のタイプのGDNativeモジュールに焦点を当て、各モジュールをいつどのように使用するかを説明します。

前提条件

始める前に、いくつかのものが必要です:

  1. ターゲットバージョンのGodot実行可能ファイル。
  2. Cコンパイラ。 Linuxでは、パッケージマネージャーから gcc または clang をインストールします。 macOSでは、Mac App StoreからXcodeをインストールできます。 Windowsでは、Visual Studio 2015以降またはMinGW-w64を使用できます。
  3. godot_headersリポジトリのGitクローン: これらはGDNative用に公開されているGodotのパブリックAPIのCヘッダーです。

次に行う作業については、このGDNativeサンプルプロジェクト専用のフォルダを作成し、そのフォルダ内でターミナルを開いて実行することをお勧めします。

git clone https://github.com/GodotNativeTools/godot_headers

これにより、必要なファイルがそのフォルダにダウンロードされます。

ちなみに

GDNativeプロジェクトにGitを使用する予定がある場合は、Gitサブモジュールとして godot_headers を追加することもできます。

注釈

godot_headers リポジトリにはさまざまなブランチがあります。 Godotが進化するにつれて、GDNativeも進化します。バージョン間の互換性を維持しようとしていますが、常にGodotの安定ブランチ(例: 3.1)と理想的な実際のリリース(例: 3.1.1-stable)に一致するヘッダーに対応させたGDNativeモジュールを構築する必要があります。古いバージョンのGodotヘッダーに対応して構築されたGDNativeモジュールは、新しいバージョンのエンジンで動作する可能性がありますが、その逆はできません。

godot_headers リポジトリの master ブランチはGodotの master ブランチと一致しているため、最新の開発ビルドで動作するGDNativeクラスと構造体の定義が含まれています。

Godotの安定バージョン用のGDNativeモジュールを作成する場合は、使用するエンジンバージョンに一致するGitタグ(git tags を含む)を確認してください。godot_headers リポジトリでは、そのようなタグの先頭に godot- が付いているため、 Godot 3.1.1で使用する godot-3.1.1-stable タグをチェックアウトします。クローンリポジトリでは、次のことができます。

git checkout godot-3.1.1-stable

何らかの理由で安定版リリースに一致するタグが欠落している場合は、一致する安定版ブランチ(3.1 など)にフォールバックできます。これは git checkout 3.1 でもチェックアウトできます。

GDNativeに影響する独自の変更を使用してソースからGodotをビルドしている場合、更新されたクラスと構造の定義は次の場所にあります。<godotsource>/modules/gdnative/include

私たちのCソース

メインコードを書くことから始めましょう。最終的には、下の図の各行と同様なファイル構造を作成します。

+ <your development folder>
  + godot_headers
    - <lots of files here>
  + simple
    + bin
      - libsimple.dll/so/dylib
      - libsimple.gdnlib
      - simple.gdns
    main.tscn
    project.godot
  + src
    - simple.c

Godotを開き、 godot_headers のGitクローンを含めた"simple" という新しいプロジェクトを作成します。これにより、 simple フォルダと project.godot ファイルが作成されます。次に、simple フォルダと src フォルダを手動で作成し、simple フォルダに bin サブフォルダを作成します。

まず、 simple.c ファイルに何が含まれているかを見てみましょう。ここで例では、説明を単純にするためにヘッダーを用意しない単一の C ソース ファイルを作成しています。大きなプロジェクトの作成を開始したら、プロジェクトを複数のファイルに分割することをお勧めします。ただし、それはこのチュートリアルの範囲外です。

ソースコードを少しずつ見ていきますので、以下のすべての部分を1つの大きなファイルにまとめて行く必要があります。各セクションの説明は、それを追加するときに行います。

#include <gdnative_api_struct.gen.h>

#include <string.h>

const godot_gdnative_core_api_struct *api = NULL;
const godot_gdnative_ext_nativescript_api_struct *nativescript_api = NULL;

上記のコードには、GDNative API構造体ヘッダーと、文字列操作のために使用する標準ヘッダーが含まれています。次に、2つの異なる構造体への2つのポインターを定義します。 GDNativeは、メインのGodot実行可能ファイルにコールバックするための機能の、大規模なコレクションをサポートしています。モジュールがこれらの関数にアクセスできるようにするため、GDNativeはこれらすべての関数へのポインターを含む構造体をアプリケーションに提供します。

この実装をモジュール化し、簡単に拡張できるようにするため、コア関数は「コア」API構造体から直接使用できますが、追加の関数には独自の「GDNative構造体」があり、拡張機能からアクセスできます。

この例では、これらの拡張機能の1つにアクセスして、NativeScriptに特に必要な関数にアクセスします。

NativeScriptは、Godotの他のスクリプトと同様に動作します。NativeScript APIはかなりレベルが低いため、ライブラリはGDScript などの他のスクリプトシステムよりも冗長に多くのことを指定する必要があります。NativeScriptインスタンスが作成されると、ライブラリ指定のコンストラクタが呼び出されます。そのインスタンスが破棄されると、指定されたデストラクタが実行されます。

void *simple_constructor(godot_object *p_instance, void *p_method_data);
void simple_destructor(godot_object *p_instance, void *p_method_data, void *p_user_data);
godot_variant simple_get_data(godot_object *p_instance, void *p_method_data,
        void *p_user_data, int p_num_args, godot_variant **p_args);

これらは、オブジェクトに実装する関数の前方宣言です。コンストラクタとデストラクタが必要です。さらに、オブジェクトには get_data と呼ばれる単一のメソッドがあります。

次は、動的ライブラリがロードされたときにGodotが呼び出す最初のエントリポイントです。これらのメソッドにはすべて godot_ というプレフィックスが付いており(後から変更できます)名前が後に続きます。 gdnative_init は動的ライブラリを初期化する関数です。 Godotは、API構造体へのポインターが、役に立つと思われるさまざまな情報を含む構造体へのポインターを提供します。

追加のAPI構造体については、拡張機能配列をループ処理し、拡張機能の種類を確認する必要があります。

void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *p_options) {
    api = p_options->api_struct;

    // Now find our extensions.
    for (int i = 0; i < api->num_extensions; i++) {
        switch (api->extensions[i]->type) {
            case GDNATIVE_EXT_NATIVESCRIPT: {
                nativescript_api = (godot_gdnative_ext_nativescript_api_struct *)api->extensions[i];
            }; break;
            default: break;
        }
    }
}

次は、ライブラリがアンロードされる前に呼び出される gdnative_terminate です。 Godotは、オブジェクトがライブラリを使用しなくなったときにライブラリをアンロードします。 ここで、必要なクリーンアップ処理を実行できます。 この例では、単にAPIポインタをクリアします。

void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *p_options) {
    api = NULL;
    nativescript_api = NULL;
}

Finally, we have nativescript_init which is the most important function we'll need today. This function will be called by Godot as part of loading a GDNative library and communicates back to the engine what objects we make available.

void GDN_EXPORT godot_nativescript_init(void *p_handle) {
    godot_instance_create_func create = { NULL, NULL, NULL };
    create.create_func = &simple_constructor;

    godot_instance_destroy_func destroy = { NULL, NULL, NULL };
    destroy.destroy_func = &simple_destructor;

    nativescript_api->godot_nativescript_register_class(p_handle, "Simple", "Reference",
            create, destroy);

    godot_instance_method get_data = { NULL, NULL, NULL };
    get_data.method = &simple_get_data;

    godot_method_attributes attributes = { GODOT_METHOD_RPC_MODE_DISABLED };

    nativescript_api->godot_nativescript_register_method(p_handle, "Simple", "get_data",
            attributes, get_data);
}

まず、 nativescript_register_class を呼び出すことで、どのクラスが実装されているかをエンジンに伝えます。ここでの最初のパラメーターは、エンジンから与えられたハンドルポインターです。 2番目はオブジェクトクラスの名前です。 3番目は、「継承」するGodotのオブジェクトのタイプです。これは真の継承ではありませんが、十分に近いものです。最後に、4番目と5番目のパラメーターは、コンストラクターとデストラクターの説明です。

次に、クラスの各メソッドに対して nativescript_register_method を呼び出して、メソッド (この場合は 1 つのメソッド) について Godot に伝えます。このチュートリアルでは、単に get_data だけです。最初のパラメータは、再びハンドルポインタです。2 つ目は、登録するオブジェクト クラスの名前です。3 つ目は、GDScript に認識される関数の名前です。4 つ目は属性設定です (可能な値については、 godot_headers / nativescript / godot_nativescript.hgodot_method_rpc_mode 列挙型を参照してください)。5 番目(最後)のパラメーターは、メソッドが呼び出されたときに呼び出す関数の説明です。

構造体 instance_method の記述には、関数自体への関数ポインターが最初のフィールドとして含まれています。これらの構造体の他の2つのフィールドは、メソッドごとのユーザーデータを指定するためのものです。 2つ目は method_data フィールドで、これはすべての関数呼び出しで p_method_data 引数として渡されます。これはおそらく、1つの関数を複数の異なるスクリプト クラスの異なるメソッドで再利用する場合に便利です。 method_data 値が解放する必要のあるメモリへのポインタである場合、3つ目の free_func フィールドにはそのメモリを解放する関数へのポインタを含めることができます。このフリー関数は、スクリプト自体(インスタンスではありません!)がアンロードされると呼び出されます(つまり、通常はライブラリのアンロード時に)。

それでは、オブジェクトの機能に関する作業を始めましょう。 まず、GDNativeクラスのインスタンスのメンバーデータを格納するために使用する構造体を定義します。

typedef struct user_data_struct {
    char data[256];
} user_data_struct;

次に、コンストラクタを定義します。コンストラクタで行うことは、構造体にメモリを割り当て、そのメモリにデータを書き込むことだけです。ここではGodotのメモリ関数を使ってメモリを追跡し、新しい構造体へのポインタを返すことに注意してください。このポインタは、複数のオブジェクトがインスタンス化された場合に、インスタンスIDとして機能します。

このポインタは、 p_user_data というパラメータとしてオブジェクトに関連する関数のいずれかに渡され、インスタンスの識別とメンバーデータへのアクセスの両方に使用できます。

void *simple_constructor(godot_object *p_instance, void *p_method_data) {
    user_data_struct *user_data = api->godot_alloc(sizeof(user_data_struct));
    strcpy(user_data->data, "World from GDNative!");

    return user_data;
}

オブジェクトに対してGodotが実行され、インスタンスのメンバデータが解放されると、デストラクタが呼び出されます。

void simple_destructor(godot_object *p_instance, void *p_method_data, void *p_user_data) {
    api->godot_free(p_user_data);
}

最後に、 get_data 関数を実装します。データは常にバリアント型(Variant)として送受信されるため、文字列のデータを返すには、まずCの文字列をGodotの文字列オブジェクトに変換し、次にその文字列オブジェクトを戻り値のバリアントにコピーする必要があります。

godot_variant simple_get_data(godot_object *p_instance, void *p_method_data,
        void *p_user_data, int p_num_args, godot_variant **p_args) {
    godot_string data;
    godot_variant ret;
    user_data_struct *user_data = (user_data_struct *)p_user_data;

    api->godot_string_new(&data);
    api->godot_string_parse_utf8(&data, user_data->data);
    api->godot_variant_new_string(&ret, &data);
    api->godot_string_destroy(&data);

    return ret;
}

文字列はGodotではヒープに割り当てられるため、そのメモリを解放するデストラクタがあります。 デストラクタには godot_TYPENAME_destroy という名前が付けられます。 バリアントが文字列で作成されると、その文字列を参照します。 つまり、元の文字列を「破棄」して参照カウントを減らすことができます。 そうしないと、参照カウントがゼロにならず、メモリの割り当てが解除されないため、文字列のメモリがリークします。 返されたバリアントは、Godotによって自動的に破棄されます。

注釈

より複雑な操作では、どの値の割り当てを解放するべきなのかそれとも保持するべきなのか、混乱しないように追跡する必要があります。一般的な規則として、C++で デストラクターが呼び出されるときには godot_TYPENAME_destroy を呼び出します。文字列デストラクターは、バリアント型 (Variant) の作成後に C++ で呼び出されるため、C でも同じことが必要です。

戻り値として返されるバリアントは、Godotによって自動的に破棄されます。

これがモジュールのソースコード全体です。

コンパイル

次に、ソースコードをコンパイルする必要があります。前述のとおり、GitHubのサンプルプロジェクトには、すべての面倒な作業を行う「SCons configuration」が含まれていますが、ここでのチュートリアルでは、コンパイラを直接呼び出します。

上記のフォルダ構造に揃えてあると仮定すると、 src フォルダでターミナルセッションを開き、そこからコマンドを実行するのが最善です。先に進む前に ``bin``フォルダを作成してください。

Linux:

gcc -std=c11 -fPIC -c -I../godot_headers simple.c -o simple.o
gcc -rdynamic -shared simple.o -o ../simple/bin/libsimple.so

macOS:

clang -std=c11 -fPIC -c -I../godot_headers simple.c -o simple.os
clang -dynamiclib simple.os -o ../simple/bin/libsimple.dylib

Windows:

cl /Fosimple.obj /c simple.c /nologo -EHsc -DNDEBUG /MD /I. /I..\godot_headers
link /nologo /dll /out:..\simple\bin\libsimple.dll /implib:..\simple\bin\libsimple.lib simple.obj

注釈

Windowsでのビルドでは、 libsimple.lib ライブラリも作成されます。これは、(一般的には)DLLにアクセスするためにプロジェクトのコンパイルで生成されるライブラリです。Godotにとってはそれは単に副産物なので必要ではありません。リリースのためにゲームをエクスポートするとき、このファイルは無視されエクスポートには含まれませんが心配はいりません。

GDNativeLibrary(.gdnlib)ファイルの作成

モジュールをコンパイルしたら、動的ライブラリに対応するGDNativeLibraryリソースを作成し、それを .gdnlib 拡張子を付けて一緒に配置する必要があります。このファイルは、どの動的ライブラリがモジュールの一部であり、プラットフォームごとにロードする必要があるかをGodotに伝えます。

Godotを使用してこのファイルを生成できるため、エディタでシンプルなプロジェクトを開きます。

まず、インスペクタで 「リソースの作成」 ボタンをクリックします:

../../../_images/new_resource.gif

そして、 GDNativeLibrary を選択します:

../../../_images/gdnativelibrary_resource.png

下部のパネルにコンテキストエディタが表示されます。右下にある[下部パネルを展開]ボタンを使用して、目いっぱいの高さにパネルを拡張します。

../../../_images/gdnativelibrary_editor.png

一般的なプロパティ

インスペクタには、ライブラリの読み込みを制御するためのさまざまなプロパティがあります。

Load Once が有効な場合、ライブラリは一度だけ読み込まれ、ライブラリを使用する個々のスクリプトは同じデータを使用します。グローバルに定義した変数は、作成されるオブジェクトのインスタンスからアクセスできます。Load Once を無効にすると、スクリプトがライブラリにアクセスするたびに、ライブラリの新しいコピーがメモリに読み込まれます。

Singleton が有効になっている場合はライブラリが自動的に読み込まれ、 godot_gdneative_singleton という関数が呼び出されます。これに関しては別のチュートリアルで説明を行ないます。

Symbol Prefix は、これまでに見た godot_nativescript_initgodot_ のようなコア関数用のプレフィックスです。静的にリンクする複数のGDNativeライブラリを使用する場合は、異なるプレフィックスを使用する必要があります。これもまた別のチュートリアルでさらに掘り下げる対象です。iOSは動的ライブラリを好まないため、現時点ではこのプラットフォーム(iOS)への展開にのみ必要です。

Reloadable は、エディタがフォーカスを失ったときにライブラリをリロードする必要があるかどうかを定義します。通常は、外部で変更を加えられたライブラリから、新しいシンボルまたは変更されたシンボルを取得します。

プラットフォームライブラリ

GDNativeLibraryエディタプラグインを使用すると、サポートするプラットフォームとアーキテクチャごとに2つの設定を行うことができます。

ダイナミック ライブラリ列(保存されたファイルの entry セクション)は、プラットフォームと機能の組み合わせごとに、どのダイナミックライブラリをロードする必要があるかを示しています。これは、特定のプラットフォームにエクスポートするときに、どのファイルをエクスポートする必要があるかをエクスポーターに通知します。

依存関係列(dependencies セクション)は、ライブラリが機能するために各プラットフォームにエクスポートする必要がある他のファイルをGodotに伝えます。 例えばGDNativeモジュールが別のDLLを使用してサードパーティライブラリの機能を実装する場合には、そのDLLをここにリストアップします。

この例では、Linux、macOS、Windows用のライブラリのみを構築しました。フォルダボタンをクリックして、関連するフィールドでそれらをリンクできます。 3つのライブラリをすべて作成した場合、次のようなものが必要です:

../../../_images/gdnativelibrary_editor_complete.png

リソースの保存

次に、インスペクタの[保存]ボタンを使用して、GDNativeLibraryリソースを bin/libsimple.gdnlib として保存できます:

../../../_images/gdnativelibrary_save.png

ファイルはテキストベースの形式で保存され、次のような内容が含まれている必要があります:

[general]

singleton=false
load_once=true
symbol_prefix="godot_"
reloadable=true

[entry]

OSX.64="res://bin/libsimple.dylib"
OSX.32="res://bin/libsimple.dylib"
Windows.64="res://bin/libsimple.dll"
X11.64="res://bin/libsimple.so"

[dependencies]

OSX.64=[  ]
OSX.32=[  ]
Windows.64=[  ]
X11.64=[  ]

NativeScript( .gdns)ファイルの作成

`` .gdnlib`` ファイルを使用して、ライブラリを読み込む方法をGodotに指示しました。次に、「単純な」オブジェクトクラスについて通知する必要があります。これを行うには、拡張子が .gdnsNativeScriptリソースファイルを作成します。

GDNativeLibraryリソースの場合と同様に、インスペクタで新しいリソースを作成するボタンをクリックし、 NativeScript を選択します:

../../../_images/nativescript_resource.png

インスペクタは、入力する必要があるいくつかのプロパティを表示します。Class Name として、"Simple" を入力します。これは、godot_nativescript_register_class を呼び出すときにCソースで宣言したオブジェクトクラス名です。また、Libraryをクリックして読込みを選択し、.gdnlib ファイルを選択する必要があります:

../../../_images/nativescript_library.png

注釈

The Class Name must have the same spelling as the one given in godot_nativescript_init when registering the class.

Finally, click on the save icon and save this as bin/simple.gdns:

../../../_images/save_gdns.gif

次に、シーンを構築します。ルートとしてシーンにコントロールノードを追加し、 main と名付けます。次に、ボタンとラベルを子ノードとして追加します。画面上のどこかに適当な場所にボタンを配置して、名前を付けます。

../../../_images/c_main_scene_layout.png

コントロール ノードを選択し、スクリプトをアタッチします:

../../../_images/add_main_script.gif

次に、ボタンの pressed シグナルをスクリプトにリンクします:

../../../_images/connect_button_signal.gif

main.tscn と名付けてシーンを保存することを忘れないでください。

main.gd コードを実装できます:

extends Control

# load the Simple library
onready var data = preload("res://bin/simple.gdns").new()

func _on_Button_pressed():
    $Label.text = "Data = " + data.get_data()

これで、プロジェクトは機能するはずです。 Godotを初めて実行すると、メインシーンが何であるかを尋ねられるので、 main.tscn ファイルを選択して実行します。

../../../_images/c_sample_result.png