Примеры GDNative C¶
Введение¶
Этот туториал познакомит вас с минимально необходимым для создания модулей GDNative. Это должно стать вашей отправной точкой в мир GDNative. Понимание этого урока поможет вам понять все, что будет после него.
Прежде чем мы начнем, вы можете загрузить исходный код примера, который мы описываем ниже, из репозитория GDNative-demos.
Этот пример проекта также содержит файл SConstruct, который немного облегчает компиляцию, но в этом туториале мы будем делать все вручную, чтобы понять процесс.
GDNative можно использовать для создания нескольких типов дополнений к Godot, используя такие интерфейсы, как PluginScript или ARVRInterfaceGDNative. В этом руководстве мы рассмотрим создание модуля NativeScript. NativeScript позволяет вам писать логику на C или C++ аналогично тому, как вы бы написали файл GDScript. Мы создадим C-эквивалент этого GDScript:
extends Reference
var data
func _ready():
data = "World from GDScript!"
func get_data():
return data
В последующих уроках будут рассмотрены другие типы модулей GDNative и объяснено, когда и как использовать каждый из них.
Требования¶
Прежде чем мы начнем, вам понадобится несколько вещей:
Исполняемый файл Godot для вашей выбранной версии.
Компилятор языка Си. В Linux установите
gcc
илиclang
из менеджера пакетов. На macOS вы можете установить Xcode из Mac App Store. В Windows вы можете использовать Visual Studio 2015 или более позднюю версию, или MinGW-w64.Клон Git-репозитория godot-headers: это заголовки на C для публичного API Godot, используемого в GDNative.
Мы предлагаем вам создать специальную папку для этого проекта GDNative, открыть терминал в этой папке и выполнить команду:
git clone https://github.com/godotengine/godot-headers.git --branch=3.4
Это позволит загрузить необходимые файлы в эту папку.
Совет
Если вы планируете использовать Git для своего проекта GDNative, вы также можете добавить 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.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.
Ветка master
репозитория godot-headers
поддерживается в соответствии с веткой master
Godot и поэтому содержит определения классов и структур GDNative, которые будут работать с последними разработанными сборками.
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
.
Если вы собираете Godot из исходного кода с собственными изменениями, влияющими на GDNative, вы можете найти обновленное определение классов и структур в <godotsource>/modules/gdnative/include
Исходный файл на С¶
Давайте начнем с написания нашего основного кода. В конечном итоге мы хотим получить файловую структуру, которая будет выглядеть примерно так:
+ <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 и создайте новый проект под названием "simple" рядом с вашим Git-клоном godot-headers
. Это создаст папку simple
и файл project.godot
. Затем вручную создайте папку rc
рядом с папкой simple
и подпапку bin
в папке simple
.
Для начала посмотрим, что содержит наш файл simple.c
. В нашем примере мы создаем один исходный файл на языке Си без заголовка, чтобы все было просто. Когда вы начинаете писать более крупные проекты, рекомендуется разбить проект на несколько файлов. Однако это выходит за рамки данного руководства.
Мы будем рассматривать исходный код по частям, поэтому все части ниже должны быть собраны в один большой файл. Каждый фрагмент будет объясняться по мере его добавления.
#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 и стандартный заголовок, который мы будем использовать далее для операций со строками. Затем он определяет два указателя на две различные структуры. GDNative поддерживает большую коллекцию функций для обратного вызова в основной исполняемый файл Godot. Для того чтобы ваш модуль имел доступ к этим функциям, GDNative предоставляет вашему приложению структуру, содержащую указатели на все эти функции.
Чтобы сохранить модульность и простоту расширения этой реализации, основные функции доступны непосредственно через API структуру "core", но дополнительные функции имеют свои собственные "структуры GDNative", доступные через расширения.
В нашем примере мы обращаемся к одному из этих расширений, чтобы получить доступ к функциям, необходимым для NativeScript.
NativeScript ведет себя в Godot как любой другой сценарий. Поскольку API NativeScript довольно низкоуровневый, он требует от библиотеки более подробного описания многих вещей, чем в других скриптовых системах, таких как 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;
}
Наконец, у нас есть nativescript_init
, которая является самой важной функцией, которая нам понадобится. Эта функция будет вызываться Godot в процессе загрузки библиотеки GDNative и сообщает движку, какие объекты мы делаем доступными.
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
. Первым параметром здесь является указатель на дескриптор, переданный нам. Второй - имя класса нашего объекта. Третий - это тип объекта в Godot, от которого мы "наследуем"; это не настоящее наследование, но достаточно близкое. Наконец, четвертый и пятый параметры - это описания наших конструктора и деструктора.
Затем мы сообщаем Godot о наших методах (в данном случае об одном методе), вызывая nativescript_register_method
для каждого метода нашего класса. В нашем случае это просто get_data
. Первым параметром снова будет указатель нашего дескриптора. Второй - снова имя класса объекта, который мы регистрируем. Третий - имя нашей функции, как оно будет известно GDScript. Четвертый - это установка наших атрибутов (см. перечисление godot_method_rpc_mode
в godot-headers/nativescript/godot_nativescript.h
для возможных значений). Пятый и последний параметр - это описание того, какую функцию следует вызывать при вызове метода.
Описание структуры instance_method
содержит указатель на саму функцию в качестве первого поля. Два других поля в этих структурах предназначены для указания пользовательских данных каждого метода. Второе - это поле method_data
, которое передается при каждом вызове функции как аргумент p_method_data
. Это полезно для повторного использования одной функции для различных методов, возможно, нескольких различных классов скриптов. Если значение method_data
является указателем на память, которую необходимо освободить, третье поле free_func
может содержать указатель на функцию, которая освободит эту память. Эта функция освобождения вызывается при выгрузке самого скрипта (не экземпляра!) (обычно во время выгрузки библиотеки).
Теперь пора приступить к работе над функциями нашего объекта. Сначала мы определим структуру, которую используем для хранения данных-членов экземпляра нашего класса 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;
}
Strings are heap-allocated in Godot, so they have a destructor which frees the
memory. Destructors are named godot_TYPENAME_destroy
. When a Variant gets
created with a String, it references the String. That means that the original
String can be "destroyed" to decrease the ref-count. If that does not happen the
String memory will leak since the ref-count will never be zero and the memory
never deallocated. The returned variant gets automatically destroyed by Godot.
Примечание
In more complex operations it can be confusing the keep track of which value
needs to be deallocated and which does not. As a general rule: call
godot_TYPENAME_destroy
when a C++ destructor would be called instead.
The String destructor would be called in C++ after the Variant was created,
so the same is necessary in C.
The variant we return is destroyed automatically by Godot.
And that is the whole source code of our module.
Компиляция¶
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.
On Linux:
gcc -std=c11 -fPIC -c -I../godot-headers simple.c -o simple.o
gcc -rdynamic -shared simple.o -o ../simple/bin/libsimple.so
On macOS:
clang -std=c11 -fPIC -c -I../godot-headers simple.c -o simple.os
clang -dynamiclib simple.os -o ../simple/bin/libsimple.dylib
On 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.
Creating the GDNativeLibrary (.gdnlib
) file¶
With our module compiled, we now need to create a corresponding
GDNativeLibrary resource with .gdnlib
extension which we place alongside our dynamic libraries. This file tells Godot
what dynamic libraries are part of our module and need to be loaded per
platform.
We can use Godot to generate this file, so open the "simple" project in the editor.
Start by clicking the create resource button in the Inspector:

And select GDNativeLibrary
:

You should see a contextual editor appear in the bottom panel. Use the "Expand Bottom Panel" button in the bottom right to expand it to full height:

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 defines whether the library should be reloaded when the editor loses and gains focus, typically to pick up new or modified symbols from any change made to the library externally.
Platform libraries¶
The GDNativeLibrary editor plugin lets you configure two things for each platform and architecture that you aim to support.
The Dynamic Library column (entry
section in the saved file) tells us for
each platform and feature combination which dynamic library has to be loaded.
This also informs the exporter which files need to be exported when exporting to
a specific platform.
The Dependencies column (also dependencies
section) tells Godot what other
files need to be exported for each platform in order for our library to work.
Say that your GDNative module uses another DLL to implement functionality from a
3rd party library, this is where you list that DLL.
For our example, we only built libraries for Linux, macOS and/or Windows, so you can link them in the relevant fields by clicking the folder button. If you built all three libraries, you should have something like this:

Saving the resource¶
We can then save our GDNativeLibrary resource as bin/libsimple.gdnlib
with
the Save button in the Inspector:

The file is saved in a text-based format and should have contents similar to this:
[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=[ ]
Creating the NativeScript (.gdns
) file¶
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.
Like done for the GDNativeLibrary resource, click the button to create a new
resource in the Inspector and select NativeScript
:

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:

Примечание
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
:

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.

Select the control node and attach a script to it:

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

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:
