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, godot-cpp складніший у використанні, ніж GDScript та C#. Якщо ви вирішите працювати з ним, ось як виглядатиме ваш робочий процес:
Створіть новий проект godot-cpp (з шаблону template або з нуля, як пояснено нижче).
Розробіть свій код за допомогою favorite IDE локально.
Зберіть та протестуйте свій код за допомогою найдавнішої сумісної версії Godot.
Створюйте збірки для всіх платформ, які ви хочете підтримувати (наприклад, використовуючи GitHub Actions).
Додатково: Опублікувати в Бібліотеці ресурсів Godot <https://godotengine.org/asset-library/asset>`__.
Приклад проекту
Для вашого першого проекту godot-cpp ми рекомендуємо почати з цього посібника, щоб зрозуміти технологію, пов'язану з godot-cpp. Після того, як ви закінчите, ви можете скористатися шаблоном godot-cpp, який краще охоплює такі функції, як конвеєр дій GitHub та корисний шаблонний код SConstruct. Однак шаблон не пояснює себе дуже детально, тому ми рекомендуємо спочатку ознайомитися з цим посібником.
Налаштування проекту
Вам знадобиться кілька передумов:
Виконуваний файл Godot 4.
Компілятор C++.
SCons як інструмент побудови.
Копія репозиторію godot-cpp.
Дивіться також Configuring an IDE i Compiling оскільки інструменти збирання ідентичні тим, які вам потрібні для компіляції Godot із вихідних кодів.
Ви можете завантажити репозиторій godot-cpp <https://github.com/godotengine/godot-cpp>`__ з GitHub або дозволити Git зробити роботу за вас. Зауважте, що це сховище має різні гілки для різних версій Godot. GDExtensions не працюватимуть у старіших версіях Godot (тільки Godot 4 і вище) і навпаки, тому переконайтеся, що ви завантажили правильну гілку.
Примітка
Щоб використовувати GDExtension, вам потрібно використовувати гілку godot-cpp, яка відповідає версії Godot, на яку ви орієнтуєтесь. Наприклад, якщо ви орієнтуєтесь на Godot 4.1, використовуйте гілку 4.1. У цьому посібнику ми використовуємо 4.x, яку потрібно буде замінити на версію Godot, на яку ви орієнтуєтесь.
Гілка master — це гілка розробки, яка регулярно оновлюється для роботи з гілкою master Godot.
Попередження
Розширення GDE, що орієнтовані на попередню версію Godot, повинні працювати в пізніших проміжних версіях, але не навпаки. Наприклад, розширення GDE, орієнтоване на Godot 4.2, має чудово працювати в Godot 4.3, але розширення, орієнтоване на Godot 4.3, не працюватиме в Godot 4.2.
Є один виняток: розширення, орієнтовані на 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
Це ініціалізує репозиторій у вашій папці проекту.
Створення простого додатка
Тепер настав час створити справжній плагін. Ми почнемо зі створення порожнього проекту Godot, у якому ми розмістимо кілька файлів.
Відкрийте Godot та створіть новий проєкт. У цьому прикладі ми розмістимо його в папці під назвою project всередині структури папок нашого GDExtension.
У нашому проєкті ми створимо сцену, що містить вузол під назвою "Main", і збережемо її як main.tscn. Ми повернемося до цього пізніше.
Повернувшись до папки модуля GDExtension верхнього рівня, ми також збираємося створити підпапку під назвою src, у яку ми розмістимо наші вихідні файли.
Тепер у вашому модулі GDExtension мають бути каталоги project, godot-cpp та src.
Тепер ваша структура папок має виглядати так:
gdextension_cpp_example/
|
+--project/ # game example/demo to test the extension
|
+--godot-cpp/ # C++ bindings
|
+--src/ # source code of the extension we are building
У папці src ми почнемо зі створення нашого файлу заголовка для вузла GDExtension, який ми будемо створювати. Ми назвемо його gdexample.h:
#pragma once
#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;
};
} // namespace godot
Є кілька речей, які варто відзначити. Ми включаємо 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 вище. Зараз нам потрібен невеликий фрагмент коду, який повідомляє Godot про всі класи в нашому плагіні 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_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();
}
}
Функції initialize_example_module та uninitialize_example_module викликаються відповідно, коли Godot завантажує наш плагін та коли вивантажує його. Все, що ми тут робимо, це розбираємо функції в нашому модулі зв'язок, щоб ініціалізувати їх, але вам, можливо, доведеться налаштувати більше речей залежно від ваших потреб. Ми викликаємо макрос GDREGISTER_CLASS для кожного з наших класів у нашій бібліотеці.
Примітка
Ви можете знайти інформацію про GDREGISTER_CLASS (та альтернативи) за адресою Клас об’єкта.
Важливою функцією є третя функція під назвою example_library_init. Спочатку ми викликаємо функцію в нашій бібліотеці прив’язок, яка створює об’єкт ініціалізації. Цей об’єкт реєструє функції ініціалізації та завершення GDExtension. Крім того, він встановлює рівень ініціалізації (ядро, сервери, сцена, редактор, рівень).
Нарешті, нам потрібен файл заголовка для register_types.cpp під назвою register_types.h.
#pragma once
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level);
void uninitialize_example_module(ModuleInitializationLevel p_level);
Компіляція плагіна
Щоб скомпілювати проєкт, нам потрібно визначити, як SCons повинні його компілювати за допомогою файлу SConstruct, який посилається на файл у godot-cpp. Написання з нуля виходить за рамки цього посібника, але ви можете the SConstruct file we prepared. Ми розглянемо більш настроюваний, детальний приклад використання цих файлів збірки в наступному посібнику.
Примітка
Цей файл SConstruct було створено для використання з останнім godot-cpp master, можливо, вам знадобиться внести невеликі зміни, використовуючи його зі старішими версіями, або зверніться до файлу SConstruct у Godot 4 .x документація.
Після завантаження файлу SConstruct, помістіть його в структуру папок GDExtension разом з godot-cpp, src та project, а потім виконайте команду:
scons platform=<platform>
You can omit the platform option if you are compiling for the platform you
are currently using. The list of available platform options depends on which
platform dependencies are set up (use platform=list to see all available platforms).
See Знайомство з системою побудови for details.
You should now be able to find the compiled library in project/bin/.
Примітка
Here, we've compiled both godot-cpp and our gdexample library as debug
builds, which is the default. For optimized builds, you should compile
them using the target=template_release option.
Використання модуля GDExtension
Перш ніж ми повернемося до Godot, нам потрібно створити ще один файл у project/bin/.
Цей файл дозволяє Godot знати, які динамічні бібліотеки слід завантажити для кожної платформи, і функцію входу для модуля. Він називається gdexample.gdextension.
[configuration]
entry_symbol = "example_library_init"
compatibility_minimum = "4.1"
reloadable = true
[libraries]
macos.debug = "./libgdexample.macos.template_debug.dylib"
macos.release = "./libgdexample.macos.template_release.dylib"
windows.debug.x86_32 = "./gdexample.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "./gdexample.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "./gdexample.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "./gdexample.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "./libgdexample.linux.template_debug.x86_64.so"
linux.release.x86_64 = "./libgdexample.linux.template_release.x86_64.so"
linux.debug.arm64 = "./libgdexample.linux.template_debug.arm64.so"
linux.release.arm64 = "./libgdexample.linux.template_release.arm64.so"
linux.debug.rv64 = "./libgdexample.linux.template_debug.rv64.so"
linux.release.rv64 = "./libgdexample.linux.template_release.rv64.so"
Цей файл містить розділ configuration, який керує функцією входу модуля. Вам також слід встановити мінімальну сумісну версію Godot за допомогою compatibility_minimum, що запобігає спробам завантаження вашого розширення старішою версією Godot. Прапорець reloadable дозволяє автоматичне перезавантаження вашого розширення редактором щоразу, коли ви його перекомпілюєте, без необхідності перезапускати редактор. Це працює, лише якщо ви компілюєте своє розширення в режимі налагодження (за замовчуванням).
Розділ libraries є важливим елементом: він повідомляє Godot про розташування динамічної бібліотеки у файловій системі проекту для кожної підтримуваної платформи. Це також призведе до того, що під час експорту проекту буде експортовано тільки файл, що означає, що пакет даних не міститиме бібліотек, несумісних із цільовою платформою.
Ви можете дізнатися більше про файли .gdextension за адресою Файл розширення .gdextension.
Ось ще один огляд, щоб перевірити правильну структуру файлу:
gdextension_cpp_example/
|
+--project/ # 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
Час повернутися до Godot. Ми завантажуємо основну сцену, яку ми створили ще на початку, і тепер додаємо новий доступний вузол GDExample до сцени:
Ми збираємося призначити логотип Godot цьому вузлу як нашу текстуру, вимкнувши властивість 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;
}
Після компіляції модуля з цими змінами ви побачите, що до нашого інтерфейсу додано властивість. Тепер ви можете змінити цю властивість, і коли ви запустите свій проект, ви побачите, що наш значок Godot рухається вздовж більшої фігури.
Давайте зробимо те саме, але для швидкості нашої анімації та скористаємося функціями встановлення та отримання. Наш файл заголовка 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;
}
Тепер, коли проект скомпільовано, ми побачимо іншу властивість під назвою швидкість. Зміна його значення призведе до пришвидшення або сповільнення анімації. Крім того, ми додали діапазон властивостей, який описує, у якому діапазоні може бути значення. Перші два аргументи – мінімальне та максимальне значення, а третій – розмір кроку.
Примітка
For simplicity, we've only used the hint_range of the property method. There are a lot more options to choose from. These can be used to further configure how properties are displayed and set on the Godot side. You can find more information on property hints here @GlobalScope.
Сигнали
І останнє, але не менш важливе: сигнали також повністю працюють у GDExtension. Щоб ваше розширення реагувало на сигнал від іншого об’єкта, потрібно викликати connect для цього об’єкта. Ми не можемо придумати гарний приклад для нашої хитаючої ікони Godot, нам потрібно було б продемонструвати набагато повніший приклад.
Це необхідний синтаксис:
some_other_node->connect("the_signal", Callable(this, "my_method"));
Щоб підключити наш сигнал the_signal з якогось іншого вузла до нашого методу my_method, нам потрібно надати методу connect ім'я сигналу та Callable. Callable містить інформацію про об'єкт, для якого можна викликати метод. У нашому випадку він пов'язує наш поточний екземпляр об'єкта this з методом my_method об'єкта. Потім метод connect додасть this до спостерігачів the_signal. Щоразу, коли the_signal тепер випускається, Godot знає, який метод якого об'єкта йому потрібно викликати.
Зауважте, що ви можете викликати my_method, лише якщо ви попередньо зареєстрували його у своєму методі _bind_methods. Інакше Godot не дізнається про існування my_method.
Щоб дізнатися більше про Callable, перегляньте посилання на клас тут: Callable.
Більш поширеним є те, що ваш об’єкт надсилає сигнали. Для нашої хитаючої піктограми Godot ми зробимо щось дурне, щоб показати, як це працює. Ми будемо випромінювати сигнал щоразу, коли мине секунда, і передамо нове місце.
У нашому файлі заголовків 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++!
Замість того, щоб базувати ваш проєкт на наведеному вище прикладі налаштування, ми рекомендуємо перезапустити його зараз, клонувавши шаблон godot-cpp, та базувати ваш проєкт на ньому. Він має краще охоплення функцій, таких як дія збірки GitHub та додатковий корисний шаблон SConstruct.