GDNative C示例

简介

This tutorial will introduce you to the bare minimum required to create GDNative modules. This should be your starting point into the world of GDNative. Understanding the contents of this tutorial will help you in understanding all that is to come after this.

Before we begin, you can download the source code to the example object we describe below in the GDNative-demos repository.

This example project also contains a SConstruct file that makes compiling a little easier, but in this tutorial we'll be doing things by hand to understand the process.

GDNative 可用于创建多种类型的Godot附加功能,使用的接口有 PluginScriptARVRInterfaceGDNative 。在本教程中,我们将探讨如何创建一个 NativeScript 模块。NativeScript允许用C或C++写逻辑,就像写GDScript文件一样。我们将创建这个GDScript的C语言等效物:

extends Reference

var data

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

func get_data():
    return data

Future tutorials will focus on the other types of GDNative modules and explain when and how to use each of them.

先决条件

在我们开始之前,您需要一些东西:

  1. A Godot executable for your target version.

  2. 一个C语言编译器。在Linux上,从你的软件包管理器安装 gccclang 。在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.

For the latter, we suggest that you create a dedicated folder for this GDNative example project, open a terminal in that folder and execute:

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

这会将所需文件下载到该文件夹中。

小技巧

如果你打算在GDNative项目中使用Git,你也可以添加 godot_headers 作为Git子模块。

注解

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.1) and ideally actual release (e.g. 3.1.1-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.

如果你想为Godot的稳定版本写一个GDNative模块,请查看与你的引擎版本相匹配的Git标签(带``git标签``),在``godot_headers``库中,这种标签的前缀是``godot-,所以你可以查看``godot-`。在``godot_headers``版本库中,这些标签的前缀是``godot-``,所以你可以查看``godot-3.1.1-stable``标签,用于Godot 3.1.1。在你的克隆仓库中,你可以这样做:

git checkout godot-3.1.1-stable

如果由于任何原因缺少了与稳定版相匹配的标签,你可以回到与之相匹配的稳定分支(例如 3.1 ),你也可以用 git checkout 3.1 来检查。

如果您使用您自己的影响GDNative的更改从源代码构建Godot,您可以在``<godotsource> / modules / gdnative / include`中找到更新的类和结构定义

我们的C源

Let's start by writing our main code. Eventually, we want to end up with a file structure that looks along those lines:

+ <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.

We're going to start by having a look at what our simple.c file contains. Now, for our example here we're making a single C source file without a header to keep things simple. Once you start writing bigger projects it is advisable to break your project up into multiple files. That however falls outside of the scope of this tutorial.

We'll be looking at the source code bit by bit so all the parts below should all be put together into one big file. Each section will be explained as we add it.

#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;

The above code includes the GDNative API struct header and a standard header that we will use further down for string operations. It then defines two pointers to two different structs. GDNative supports a large collection of functions for calling back into the main Godot executable. In order for your module to have access to these functions, GDNative provides your application with a struct containing pointers to all these functions.

为了保持这种实现模块化和易于扩展,核心功能可直接通过``核心``API结构提供,但其他功能有自己的``GDNative结构``,可通过扩展访问。

在我们的示例中,我们访问其中一个扩展,以获取对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);

These are forward declarations for the functions we'll be implementing for our object. A constructor and destructor is needed. Additionally, the object will have a single method called get_data.

Next up is the first of the entry points Godot will call when our dynamic library is loaded. These methods are all prefixed with godot_ (you can change this later on) followed by their name. gdnative_init is a function that initializes our dynamic library. Godot will give it a pointer to a structure that contains various bits of information we may find useful among which the pointers to our API structures.

对于任何其他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);
}

We first tell the engine which classes are implemented by calling nativescript_register_class. The first parameter here is the handle pointer given to us. The second is the name of our object class. The third is the type of object in Godot that we 'inherit' from; this is not true inheritance but it's close enough. Finally, our fourth and fifth parameters are descriptions for our constructor and destructor.

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.

The description struct instance_method contains the function pointer to the function itself as first field. The other two fields in these structs are for specifying per-method userdata. The second is the method_data field which is passed on every function call as the p_method_data argument. This is useful to reuse one function for different methods on possibly multiple different script-classes. If the method_data value is a pointer to memory that needs to be freed, the third free_func field can contain a pointer to a function that will free that memory. That free function gets called when the script itself (not instance!) gets unloaded (so usually at library-unload time).

现在,是时候开始处理我们对象的功能了。 首先,我们定义一个结构,用于存储GDNative类实例的成员数据。

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

然后,我们定义我们的构造函数。 我们在构造函数中所做的就是为结构分配内存并用一些数据填充它。 请注意,我们使用Godot的内存函数,以便跟踪内存,然后将指针返回到我们的新结构。 如果实例化多个对象,则此指针将充当我们的实例标识符。

该指针将作为名为 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);
}

And finally, we implement our get_data function. Data is always sent and returned as variants so in order to return our data, which is a string, we first need to convert our C string to a Godot string object, and then copy that string object into the variant we are returning.

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 。 使用String创建Variant时,它会引用String。 这意味着可以``销毁``原始字符串以减少引用计数。 如果没有发生这种情况,String内存将泄漏,因为ref-count永远不会为零,并且内存永远不会被释放。 返回的变体会被Godot自动销毁。

注解

在更复杂的操作中,跟踪哪个值需要重新分配以及哪个值不需要可能会造成混淆。 一般来说:当将调用C++析构函数时,请调用``godot_TYPENAME_destroy``。 创建Variant后,将调用C++中的String类析构函数,在C中也是如此。

我们返回的变体由Godot自动销毁。

这就是我们模块的完整源代码。

开始编译

We now need to compile our source code. As mentioned our example project on GitHub contains a SCons configuration that does all the hard work for you, but for our tutorial here we are going to call the compilers directly.

Assuming you are sticking to the folder structure suggested above, it is best to open a terminal session in the src folder and execute the commands from there. Make sure to create the bin folder before you proceed.

在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

注解

On the Windows build you also end up with a libsimple.lib library. This is a library that you can compile into a project to provide access to the DLL. We get it as a byproduct and we do not need it :) When exporting your game for release this file will be ignored.

创建GDNativeLibrary (.gdnlib)文件

模块编译完成后,需要创建一个相应的 GDNativeLibrary 资源,扩展名为 .gdnlib ,与动态库放在一起。这个文件告诉Godot哪些动态库是模块的一部分,需要在每个平台上加载。

我们可以使用Godot来生成这个文件,所以在编辑器中打开 "简单 "项目。

首先单击属性面板中的创建资源按钮:

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

And select GDNativeLibrary:

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

你应该看到一个上下文编辑器出现在底部面板中。使用右下角的 "展开底部面板 "按钮将其展开到全高:

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

General properties

In the Inspector, you have various properties to control loading the library.

If Load Once is enabled, our library is loaded only once and each individual script that uses our library will use the same data. Any variable you define globally will be accessible from any instance of your object you create. If Load Once is disabled, a new copy of the library is loaded into memory each time a script accesses the library.

If Singleton is enabled, our library is automatically loaded and a function called godot_gdnative_singleton is called. We'll leave that for another tutorial.

The Symbol Prefix is a prefix for our core functions, such as godot_ in godot_nativescript_init seen earlier. If you use multiple GDNative libraries that you wish to statically link, you will have to use different prefixes. This again is a subject to dive into deeper in a separate tutorial, it is only needed at this time for deployment to iOS as this platform does not like dynamic libraries.

*Reloadable*定义了当编辑器失去和获得焦点时,是否应该重新加载库,通常是为了从外部对库的任何变化中获取新的或修改的符号。

平台库

GDNativeLibrary编辑器插件可以让你为你所要支持的每个平台和架构配置两件事。

动态库 一栏(保存文件中的 entry 部分)告诉我们每个平台和特征组合需要加载哪个动态库。这也告知导出器在向特定平台导出时需要导出哪些文件。

*Dependencies*列(也称 依赖 部分)告诉Godot为了让库工作,每个平台还需要导出哪些文件。如果您的GDNative模块使用另一个DLL来实现第三方库的功能,这就是您列出该DLL的地方。

在我们的例子中,我们只构建了Linux、macOS和/或Windows的库,所以你可以通过点击文件夹按钮在相关字段中链接它们。如果你建立了所有三个库,你应该有这样的东西:

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

Saving the resource

然后,我们可以通过检查器中的保存按钮将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

注解

类名*必须与注册时在``godot_nativescript_init``中给出的拼写相同。

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

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

Now it's time to build our scene. Add a Control node to your scene as your root and call it main. Then add a Button and a Label as child nodes. Place them somewhere nice on screen and give your button a name.

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

Select the control node and attach a script to it:

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

Next link up the pressed signal on the button to your script:

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

Don't forget to save your scene, call it main.tscn.

Now we can implement our main.gd code:

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()

After all that, our project should work. The first time you run it Godot will ask you what your main scene is and you select your main.tscn file and presto:

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