Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Приклад GDExtension C++
Вступ
Прив’язки C++ для GDExtension створені на основі API C GDExtension і забезпечують кращий спосіб «розширити» вузли та інші вбудовані класи в Godot за допомогою C++. Ця нова система дозволяє розширити Godot майже до того ж рівня, що й статично пов’язані модулі C++.
Ви можете завантажити включений приклад у тестову папку репозиторію godot-cpp на GitHub.
Налаштування проекту
Вам знадобиться кілька передумов:
виконуваний файл Godot 4,
компілятор C++,
SCons як інструмент для створення,
копія репозиторію godot-cpp <https://github.com/godotengine/godot-cpp>`__.
See also Configuring an IDE and Compiling as the build tools are identical to the ones you need to compile Godot from source.
Ви можете завантажити репозиторій godot-cpp <https://github.com/godotengine/godot-cpp>`__ з GitHub або дозволити Git зробити роботу за вас. Зауважте, що це сховище має різні гілки для різних версій Godot. GDExtensions не працюватимуть у старіших версіях Godot (тільки Godot 4 і вище) і навпаки, тому переконайтеся, що ви завантажили правильну гілку.
Примітка
Ви можете завантажити репозиторій godot-cpp <https://github.com/godotengine/godot-cpp>`__ з GitHub або дозволити Git зробити роботу для вас. Зауважте, що це сховище має різні гілки для різних версій Годо. GDExtensions не працюють у старих версіях Godot (тільки Godot 4 і вище) і навпаки, тому переконайтеся, що ви завантажили правильну гілку.
Гілка master
— це гілка розробки, яка регулярно оновлюється для роботи з гілкою master
Годо.
Попередження
Наша довгострокова мета полягає в тому, щоб GDExtensions, націлені на попередню версію Godot, працювали в наступних проміжних версіях, але не навпаки. Наприклад, GDExtension, націлене на Godot 4.1, має добре працювати в Godot 4.2, але розширення, націлене на Godot 4.2, не працюватиме в Godot 4.1.
Однак наразі GDExtension є експериментальним, що означає, що ми можемо порушити сумісність, щоб виправити основні помилки або включити критичні функції. Наприклад, GDExtensions, створені для Godot 4.0, несумісні з Godot 4.1 (див. Оновлення GDExtension до 4.1).
Якщо ви керуєте версіями свого проекту за допомогою Git, рекомендуємо додати його як субмодуль Git:
mkdir gdextension_cpp_example
cd gdextension_cpp_example
git init
git submodule add -b 4.x https://github.com/godotengine/godot-cpp
cd godot-cpp
git submodule update --init
Крім того, ви також можете клонувати його до папки проекту:
mkdir gdextension_cpp_example
cd gdextension_cpp_example
git clone -b 4.x https://github.com/godotengine/godot-cpp
Примітка
Якщо ви вирішите завантажити репозиторій або клонувати його у свою папку, переконайтеся, що макет папки такий самий, як ми налаштувати тут. Значна частина коду, який ми тут демонструватимемо, припускає, що проект має такий макет.
Якщо ви клонували приклад за посиланням, указаним у вступі, субмодулі не ініціалізуються автоматично. Вам потрібно буде виконати наступні команди:
cd gdextension_cpp_example
git submodule update --init
Це ініціалізує репозиторій у вашій папці проекту.
Побудова прив'язок C++
Тепер, коли ми завантажили наші передумови, настав час створювати прив’язки C++.
The repository contains a copy of the metadata for the current Godot release, but if you need to build these bindings for a newer version of Godot, call the Godot executable:
godot --dump-extension-api
Отриманий файл extension_api.json
буде створено в каталозі виконуваного файлу. Скопіюйте його до папки проекту та додайте custom_api_file=<PATH_TO_FILE>
до команди scons нижче.
Щоб створити та скомпілювати прив’язки, використовуйте цю команду (замінивши <платформа>
на windows
, linux
або macos
залежно від вашої ОС):
Процес збірки автоматично визначає кількість потоків ЦП, які потрібно використовувати для паралельних збірок. Щоб вказати кількість потоків ЦП для використання, додайте -jN
в кінці командного рядка SCons, де N
— це кількість потоків ЦП для використання.
cd godot-cpp
scons platform=<platform> custom_api_file=<PATH_TO_FILE>
cd ..
Цей крок займе деякий час. Після завершення у вас повинні бути статичні бібліотеки, які можна скомпілювати у ваш проект, які зберігаються в godot-cpp/bin/
.
Примітка
Можливо, вам знадобиться додати bits=64
до команди в Windows або Linux.
Створення простого додатка
Тепер настав час створити справжній плагін. Ми почнемо зі створення порожнього проекту Godot, у якому ми розмістимо кілька файлів.
Відкрийте Godot і створіть новий проект. Для цього прикладу ми розмістимо його в папці під назвою demo
у структурі папок нашого GDExtension.
У нашому демонстраційному проекті ми створимо сцену, що містить вузол під назвою «Main» і збережемо його як main.tscn
. Ми повернемося до цього пізніше.
Повернувшись до папки модуля GDExtension верхнього рівня, ми також збираємося створити підпапку під назвою src
, у яку ми розмістимо наші вихідні файли.
Тепер у вашому модулі GDExtension повинні бути каталоги demo
, godot-cpp
і src
.
Тепер ваша структура папок має виглядати так:
gdextension_cpp_example/
|
+--demo/ # game example/demo to test the extension
|
+--godot-cpp/ # C++ bindings
|
+--src/ # source code of the extension we are building
У папці src
ми почнемо зі створення нашого файлу заголовка для вузла GDExtension, який ми будемо створювати. Ми назвемо його gdexample.h
:
#ifndef GDEXAMPLE_H
#define GDEXAMPLE_H
#include <godot_cpp/classes/sprite2d.hpp>
namespace godot {
class GDExample : public Sprite2D {
GDCLASS(GDExample, Sprite2D)
private:
double time_passed;
protected:
static void _bind_methods();
public:
GDExample();
~GDExample();
void _process(double delta) override;
};
}
#endif
Є кілька речей, які варто відзначити. Ми включаємо sprite2d.hpp
, який містить прив’язки до класу Sprite2D. Ми розширимо цей клас у нашому модулі.
Ми використовуємо простір імен godot
, оскільки все в GDExtension визначено в цьому просторі імен.
Тоді ми маємо наше визначення класу, яке успадковує наш Sprite2D через клас-контейнер. Пізніше ми побачимо кілька побічних ефектів цього. Макрос GDCLASS` налаштовує для нас кілька внутрішніх речей.
Після цього ми оголошуємо єдину змінну-член під назвою time_passed
.
У наступному блоці ми визначаємо наші методи, у нас визначені конструктор і деструктор, але є ще дві функції, які, ймовірно, здадуться комусь знайомими, і один новий метод.
Перший — це _bind_methods
, яка є статичною функцією, яку Godot викликає, щоб дізнатися, які методи можна викликати та які властивості вона надає. Друга — це наша функція _process
, яка працюватиме точно так само, як функція _process
, до якої ви звикли в GDScript.
Давайте реалізуємо наші функції, створивши наш файл gdexample.cpp
:
#include "gdexample.h"
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void GDExample::_bind_methods() {
}
GDExample::GDExample() {
// Initialize any variables here.
time_passed = 0.0;
}
GDExample::~GDExample() {
// Add your cleanup here.
}
void GDExample::_process(double delta) {
time_passed += delta;
Vector2 new_position = Vector2(10.0 + (10.0 * sin(time_passed * 2.0)), 10.0 + (10.0 * cos(time_passed * 1.5)));
set_position(new_position);
}
Цей має бути простим. Ми реалізуємо кожен метод нашого класу, який ми визначили у нашому файлі заголовка.
Зверніть увагу на нашу функцію _process
, яка відстежує, скільки часу минуло, і обчислює нову позицію нашого спрайту за допомогою функції синуса та косинуса.
Нам потрібен ще один файл C++; ми назвемо його register_types.cpp
. Наш плагін GDExtension може містити кілька класів, кожен із власним заголовком і вихідним файлом, як ми реалізували GDExample
вище. Зараз нам потрібен невеликий фрагмент коду, який повідомляє Годо про всі класи в нашому плагіні GDExtension.
#include "register_types.h"
#include "gdexample.h"
#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
GDREGISTER_RUNTIME_CLASS(GDExample);
}
void uninitialize_example_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}
extern "C" {
// Initialization.
GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
init_obj.register_initializer(initialize_example_module);
init_obj.register_terminator(uninitialize_example_module);
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init();
}
}
The initialize_example_module
and uninitialize_example_module
functions get
called respectively when Godot loads our plugin and when it unloads it. All
we're doing here is parse through the functions in our bindings module to
initialize them, but you might have to set up more things depending on your
needs. We call the GDREGISTER_RUNTIME_CLASS
macro for each of our classes
in our library. This will make them run only in game, like the default for GDScript.
Важливою функцією є третя функція під назвою example_library_init
. Спочатку ми викликаємо функцію в нашій бібліотеці прив’язок, яка створює об’єкт ініціалізації. Цей об’єкт реєструє функції ініціалізації та завершення GDExtension. Крім того, він встановлює рівень ініціалізації (ядро, сервери, сцена, редактор, рівень).
Нарешті, нам потрібен файл заголовка для register_types.cpp
під назвою register_types.h
.
#ifndef GDEXAMPLE_REGISTER_TYPES_H
#define GDEXAMPLE_REGISTER_TYPES_H
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level);
void uninitialize_example_module(ModuleInitializationLevel p_level);
#endif // GDEXAMPLE_REGISTER_TYPES_H
Компіляція плагіна
To compile the project we need to define how SCons using should compile it
using a SConstruct
file which references the one in godot-cpp
.
Writing it from scratch is outside the scope of this tutorial, but you can
the SConstruct file we prepared
.
We'll cover a more customizable, detailed example on how to use these
build files in a subsequent tutorial.
Примітка
Цей файл SConstruct
було створено для використання з останнім godot-cpp
master, можливо, вам знадобиться внести невеликі зміни, використовуючи його зі старішими версіями, або зверніться до файлу SConstruct
у Godot 4 .x документація.
Завантаживши файл SConstruct
, розмістіть його в структурі папок GDExtension поряд з godot-cpp
, src
і demo
, а потім запустіть:
scons platform=<platform>
Тепер ви зможете знайти модуль у demo/bin/<platform>
.
When building for iOS, package the module as a static .xcframework, you can use following commands to do so:
# compile simulator and device modules
scons arch=universal ios_simulator=yes platform=ios target=<target>
scons arch=arm64 ios_simulator=no platform=ios target=<target>
# assemble xcframeworks
xcodebuild -create-xcframework -library demo/bin/libgdexample.ios.<target>.a -library demo/bin/libgdexample.ios.<target>.simulator.a -output demo/bin/libgdexample.ios.<target>.xcframework
xcodebuild -create-xcframework -library godot-cpp/bin/libgodot-cpp.ios.<target>.arm64.a -library godot-cpp/bin/libgodot-cpp.ios.<target>.universal.simulator.a -output demo/bin/libgodot-cpp.ios.<target>.xcframework
Примітка
Тут ми скомпілювали як godot-cpp, так і нашу бібліотеку gdexample як збірки для налагодження. Для оптимізованих збірок вам слід скомпілювати їх за допомогою перемикача target=template_release
.
Використання модуля GDExtension
Перш ніж повернутися до Godot, нам потрібно створити ще один файл у demo/bin/
.
Цей файл дозволяє Godot знати, які динамічні бібліотеки слід завантажити для кожної платформи, і функцію входу для модуля. Він називається gdexample.gdextension
.
[configuration]
entry_symbol = "example_library_init"
compatibility_minimum = "4.1"
reloadable = true
[libraries]
macos.debug = "res://bin/libgdexample.macos.template_debug.framework"
macos.release = "res://bin/libgdexample.macos.template_release.framework"
ios.debug = "res://bin/libgdexample.ios.template_debug.xcframework"
ios.release = "res://bin/libgdexample.ios.template_release.xcframework"
windows.debug.x86_32 = "res://bin/libgdexample.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "res://bin/libgdexample.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "res://bin/libgdexample.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "res://bin/libgdexample.linux.template_debug.x86_64.so"
linux.release.x86_64 = "res://bin/libgdexample.linux.template_release.x86_64.so"
linux.debug.arm64 = "res://bin/libgdexample.linux.template_debug.arm64.so"
linux.release.arm64 = "res://bin/libgdexample.linux.template_release.arm64.so"
linux.debug.rv64 = "res://bin/libgdexample.linux.template_debug.rv64.so"
linux.release.rv64 = "res://bin/libgdexample.linux.template_release.rv64.so"
android.debug.x86_64 = "res://bin/libgdexample.android.template_debug.x86_64.so"
android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so"
android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so"
android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so"
[dependencies]
ios.debug = {
"res://bin/libgodot-cpp.ios.template_debug.xcframework": ""
}
ios.release = {
"res://bin/libgodot-cpp.ios.template_release.xcframework": ""
}
This file contains a configuration
section that controls the entry function of the module.
You should also set the minimum compatible Godot version with compatability_minimum
,
which prevents older version of Godot from trying to load your extension.
The reloadable
flag enables automatic reloading of your extension by the editor every time you recompile it,
without needing to restart the editor. This only works if you compile your extension in debug mode (default).
Розділ libraries
є важливим елементом: він повідомляє Godot про розташування динамічної бібліотеки у файловій системі проекту для кожної підтримуваної платформи. Це також призведе до того, що під час експорту проекту буде експортовано тільки файл, що означає, що пакет даних не міститиме бібліотек, несумісних із цільовою платформою.
Нарешті, у розділі залежностей
можна назвати додаткові динамічні бібліотеки, які також слід включити. Це важливо, коли ваш плагін GDExtension реалізує чужу бібліотеку та вимагає від вас надати сторонню динамічну бібліотеку з вашим проектом.
Ось ще один огляд, щоб перевірити правильну структуру файлу:
gdextension_cpp_example/
|
+--demo/ # game example/demo to test the extension
| |
| +--main.tscn
| |
| +--bin/
| |
| +--gdexample.gdextension
|
+--godot-cpp/ # C++ bindings
|
+--src/ # source code of the extension we are building
| |
| +--register_types.cpp
| +--register_types.h
| +--gdexample.cpp
| +--gdexample.h
Час повернутися до Годо. Ми завантажуємо основну сцену, яку ми створили ще на початку, і тепер додаємо новий доступний вузол GDExample до сцени:

Ми збираємося призначити логотип Годо цьому вузлу як нашу текстуру, вимкнувши властивість centered
:

Ми нарешті готові до запуску проекту:
Додавання властивостей
GDScript дозволяє додавати властивості до сценарію за допомогою ключового слова export
. У GDExtension вам потрібно зареєструвати властивості за допомогою функції getter і setter або безпосередньо реалізувати методи _get_property_list
, _get
і _set
об’єкта (але це виходить далеко за межі цього посібника). ).
Давайте додамо властивість, яка дозволяє нам контролювати амплітуду нашої хвилі.
У наш файл gdexample.h
нам потрібно додати змінну-член і функції getter і setter:
...
private:
double time_passed;
double amplitude;
public:
void set_amplitude(const double p_amplitude);
double get_amplitude() const;
...
У нашому файлі gdexample.cpp
нам потрібно внести низку змін, ми покажемо лише методи, які ми змінили, не видаляйте рядки, які ми пропускаємо:
void GDExample::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_amplitude"), &GDExample::get_amplitude);
ClassDB::bind_method(D_METHOD("set_amplitude", "p_amplitude"), &GDExample::set_amplitude);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amplitude"), "set_amplitude", "get_amplitude");
}
GDExample::GDExample() {
// Initialize any variables here.
time_passed = 0.0;
amplitude = 10.0;
}
void GDExample::_process(double delta) {
time_passed += delta;
Vector2 new_position = Vector2(
amplitude + (amplitude * sin(time_passed * 2.0)),
amplitude + (amplitude * cos(time_passed * 1.5))
);
set_position(new_position);
}
void GDExample::set_amplitude(const double p_amplitude) {
amplitude = p_amplitude;
}
double GDExample::get_amplitude() const {
return amplitude;
}
Після компіляції модуля з цими змінами ви побачите, що до нашого інтерфейсу додано властивість. Тепер ви можете змінити цю властивість, і коли ви запустите свій проект, ви побачите, що наш значок Годо рухається вздовж більшої фігури.
Давайте зробимо те саме, але для швидкості нашої анімації та скористаємося функціями встановлення та отримання. Наш файл заголовка gdexample.h
знову потребує лише кількох рядків коду:
...
double amplitude;
double speed;
...
void _process(double delta) override;
void set_speed(const double p_speed);
double get_speed() const;
...
Це потребує ще кількох змін у нашому файлі gdexample.cpp
, ми знову показуємо лише методи, які змінилися, тому не видаляйте нічого, що ми пропускаємо:
void GDExample::_bind_methods() {
...
ClassDB::bind_method(D_METHOD("get_speed"), &GDExample::get_speed);
ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &GDExample::set_speed);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");
}
GDExample::GDExample() {
time_passed = 0.0;
amplitude = 10.0;
speed = 1.0;
}
void GDExample::_process(double delta) {
time_passed += speed * delta;
Vector2 new_position = Vector2(
amplitude + (amplitude * sin(time_passed * 2.0)),
amplitude + (amplitude * cos(time_passed * 1.5))
);
set_position(new_position);
}
...
void GDExample::set_speed(const double p_speed) {
speed = p_speed;
}
double GDExample::get_speed() const {
return speed;
}
Тепер, коли проект скомпільовано, ми побачимо іншу властивість під назвою швидкість. Зміна його значення призведе до пришвидшення або сповільнення анімації. Крім того, ми додали діапазон властивостей, який описує, у якому діапазоні може бути значення. Перші два аргументи – мінімальне та максимальне значення, а третій – розмір кроку.
Примітка
Для простоти ми використали лише hint_range методу властивості. Є ще багато варіантів на вибір. Їх можна використовувати для подальшого налаштування того, як властивості відображаються та встановлюються на стороні Godot.
Сигнали
І останнє, але не менш важливе: сигнали також повністю працюють у GDExtension. Щоб ваше розширення реагувало на сигнал від іншого об’єкта, потрібно викликати connect
для цього об’єкта. Ми не можемо придумати гарний приклад для нашої хитаючої ікони Годо, нам потрібно було б продемонструвати набагато повніший приклад.
Це необхідний синтаксис:
some_other_node->connect("the_signal", Callable(this, "my_method"));
Щоб з’єднати наш сигнал the_signal
з іншого вузла з нашим методом my_method
, нам потрібно надати метод connect
з назвою сигналу та Callable
. Callable
містить інформацію про об’єкт, для якого можна викликати метод. У нашому випадку він пов’язує наш поточний екземпляр об’єкта this з методом my_method об’єкта. Тоді метод connect
додасть це до спостерігачів the_signal
. Щоразу, коли the_signal
випромінюється, Годо знає, який метод якого об’єкта йому потрібно викликати.
Зауважте, що ви можете викликати my_method
, лише якщо ви попередньо зареєстрували його у своєму методі _bind_methods
. Інакше Годо не дізнається про існування my_method
.
Щоб дізнатися більше про Callable
, перегляньте посилання на клас тут: Callable.
Більш поширеним є те, що ваш об’єкт надсилає сигнали. Для нашої хитаючої піктограми Годо ми зробимо щось дурне, щоб показати, як це працює. Ми будемо випромінювати сигнал щоразу, коли мине секунда, і передамо нове місце.
У нашому файлі заголовків gdexample.h
нам потрібно визначити новий член time_emit
:
...
double time_passed;
double time_emit;
double amplitude;
...
Цього разу зміни в gdexample.cpp
більш детальні. По-перше, вам потрібно буде встановити time_emit = 0.0;
або в нашому методі _init
, або в нашому конструкторі. Ми почергово розглянемо інші 2 необхідні зміни.
У нашому методі _bind_methods
нам потрібно оголосити наш сигнал. Це робиться наступним чином:
void GDExample::_bind_methods() {
...
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");
ADD_SIGNAL(MethodInfo("position_changed", PropertyInfo(Variant::OBJECT, "node"), PropertyInfo(Variant::VECTOR2, "new_pos")));
}
Тут наш макрос ADD_SIGNAL
може бути одним викликом з аргументом MethodInfo
. Першим параметром MethodInfo
буде ім'я сигналу, а решта його параметрів - це типи PropertyInfo
, які описують суть кожного з параметрів методу. Параметри PropertyInfo
визначаються типом даних параметра, а потім назвою, яку параметр матиме за замовчуванням.
Отже, тут ми додаємо сигнал із MethodInfo
, який називає сигнал "position_changed". Параметри PropertyInfo
описують два важливі аргументи, один типу Object
, інший типу Vector2
, відповідно названих «node» і «new_pos».
Далі нам потрібно буде змінити наш метод _process
:
void GDExample::_process(double delta) {
time_passed += speed * delta;
Vector2 new_position = Vector2(
amplitude + (amplitude * sin(time_passed * 2.0)),
amplitude + (amplitude * cos(time_passed * 1.5))
);
set_position(new_position);
time_emit += delta;
if (time_emit > 1.0) {
emit_signal("position_changed", this, new_position);
time_emit = 0.0;
}
}
Через секунду ми видаємо свій сигнал і скидаємо лічильник. Ми можемо додати значення наших параметрів безпосередньо до emit_signal
.
Після компіляції бібліотеки GDExtension ми можемо зайти в Godot і вибрати наш вузол спрайту. У док-станції Node ми можемо знайти наш новий сигнал і зв’язати його, натиснувши кнопку Connect або двічі клацнувши сигнал. Ми додали сценарій на наш головний вузол і реалізували наш сигнал так:
extends Node
func _on_Sprite2D_position_changed(node, new_pos):
print("The position of " + node.get_class() + " is now " + str(new_pos))
Кожну секунду ми виводимо свою позицію на консоль.
Наступні кроки
Сподіваємося, наведений вище приклад показав вам основи. На основі цього прикладу можна створювати повноцінні сценарії для керування вузлами в Godot за допомогою C++.