GDNative Cの例

はじめに

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

Before we begin, you can download the source code to the example object we describe below in the 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. A Git clone of the godot-headers repository: these are the C headers for Godot's public API exposed to GDNative.

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

git clone https://github.com/godotengine/godot-headers.git --branch=3.4

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

ちなみに

If you plan to use Git for your GDNative project, you can also add godot-headers as a Git submodule.

注釈

The godot-headers repository has different branches. As Godot evolves, so does GDNative. While we try to preserve compatibility between version, you should always build your GDNative module against headers matching the Godot stable branch (e.g. 3.4) and ideally actual release (e.g. 3.4.4-stable) that you use. GDNative modules built against older versions of the Godot headers may work with newer versions of the engine, but not the other way around.

The master branch of the godot-headers repository is kept in line with the master branch of Godot and thus contains the GDNative class and structure definitions that will work with the latest development builds.

If you want to write a GDNative module for a stable version of Godot, look at the available Git tags (with git tags) for the one matching your engine version. In the godot-headers repository, such tags are prefixed with godot-, so you can e.g. checkout the godot-3.4.4-stable tag for use with Godot 3.4.4. In your cloned repository, you can do:

git checkout godot-3.4.4-stable

If a tag matching your stable release is missing for any reason, you can fall back to the matching stable branch (e.g. 3.4), which you would also check out with git checkout 3.4.

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

Open up Godot and create a new project called "simple" alongside your godot-headers Git clone. This will create the simple folder and project.godot file. Then manually create a src folder alongside the simple folder, and a bin subfolder in the simple folder.

まず、 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番目のパラメーターは、コンストラクターとデストラクターの説明です。

We then tell Godot about our methods (well our one method in this case), by calling nativescript_register_method for each method of our class. In our case, that is just get_data. Our first parameter is yet again our handle pointer. The second is again the name of the object class we're registering. The third is the name of our function as it will be known to GDScript. The fourth is our attributes setting (see godot_method_rpc_mode enum in godot-headers/nativescript/godot_nativescript.h for possible values). The fifth and final parameter is a description of which function to call when the method gets called.

構造体 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)ファイルの作成

With our .gdnlib file we've told Godot how to load our library, now we need to tell it about our "SIMPLE" object class. We do this by creating a NativeScript resource file with .gdns extension.

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

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

The inspector will show a few properties that we need to fill. As Class Name we enter "SIMPLE" which is the object class name that we declared in our C source when calling godot_nativescript_register_class. We also need to select our .gdnlib file by clicking on Library and selecting Load:

../../../_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