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...
Спеціальні модулі в C++
Модулі
Godot дозволяє розширити двигун за модульним принципом. Нові модулі можна створювати, а потім увімкнути/вимкнути. Це дозволяє додавати нові функції двигуна на кожному рівні без модифікації ядра, яке можна розділити для використання та повторного використання в різних модулях.
Модулі знаходяться в підкаталозі modules/ системи збірки. За замовчуванням увімкнено десятки модулів, таких як GDScript (який, так, не є частиною базового движка), підтримка GridMap, модуль регулярних виразів та інші. Можна створювати та комбінувати стільки нових модулів, скільки забажаєте. Система збірки SCons прозоро подбає про це.
для чого?
Незважаючи на те, що більшість ігор рекомендується писати за допомогою сценаріїв (оскільки це значно економить час), замість цього цілком можливо використовувати C++. Додавання модулів C++ може бути корисним у таких випадках:
Прив’язка зовнішньої бібліотеки до Godot (наприклад, PhysX, FMOD тощо).
Оптимізуйте важливі частини гри.
Додавання нових функцій до движка та/або редактора.
Перенесення існуючої гри на Godot.
Напишіть цілу нову гру на C++, тому що ви не можете жити без C++.
Примітка
Хоча можна використовувати модулі для налаштування логіки гри, GDExtension загалом підходить більше, оскільки не вимагає перекомпіляції рушія після кожної зміни коду.
Модулі C++ потрібні переважно тоді, коли GDExtension недостатньо, і потрібна глибша інтеграція з движком.
Створення модуля
Перед створенням модуля обов’язково download the source code of Godot and compile it.
Щоб створити новий модуль, першим кроком є створення каталогу всередині modules/. Якщо ви хочете підтримувати модуль окремо, ви можете розділити інший VCS на модулі та використовувати його.
Приклад модуля буде називатися "summator" (godot/modules/summator). Всередині ми створимо клас суматора:
#pragma once
#include "core/object/ref_counted.h"
class Summator : public RefCounted {
GDCLASS(Summator, RefCounted);
int count;
protected:
static void _bind_methods();
public:
void add(int p_value);
void reset();
int get_total() const;
Summator();
};
А потім файл cpp.
#include "summator.h"
void Summator::add(int p_value) {
count += p_value;
}
void Summator::reset() {
count = 0;
}
int Summator::get_total() const {
return count;
}
void Summator::_bind_methods() {
ClassDB::bind_method(D_METHOD("add", "value"), &Summator::add);
ClassDB::bind_method(D_METHOD("reset"), &Summator::reset);
ClassDB::bind_method(D_METHOD("get_total"), &Summator::get_total);
}
Summator::Summator() {
count = 0;
}
Потім новий клас потрібно якимось чином зареєструвати, тому необхідно створити ще два файли:
register_types.h
register_types.cpp
Важливо
Ці файли мають бути у папці верхнього рівня вашого модуля (поруч із файлами SCsub і config.py), щоб модуль було зареєстровано належним чином.
Ці файли повинні містити наступне:
#include "modules/register_module_types.h"
void initialize_summator_module(ModuleInitializationLevel p_level);
void uninitialize_summator_module(ModuleInitializationLevel p_level);
/* yes, the word in the middle must be the same as the module folder name */
#include "register_types.h"
#include "core/object/class_db.h"
#include "summator.h"
void initialize_summator_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<Summator>();
}
void uninitialize_summator_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
// Nothing to do here in this example.
}
Далі нам потрібно створити файл SCsub, щоб система збірки скомпілювала цей модуль:
# SCsub
Import('env')
env.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build
З кількома джерелами ви також можете додати кожен файл окремо до списку рядків Python:
src_list = ["summator.cpp", "other.cpp", "etc.cpp"]
env.add_source_files(env.modules_sources, src_list)
Це дає потужні можливості за допомогою Python для створення списку файлів за допомогою циклів і логічних операторів. Подивіться на приклади деяких модулів, які стандартно поставляються з Godot.
Щоб додати каталоги include для перегляду компілятором, ви можете додати його до шляхів середовища:
env.Append(CPPPATH=["mylib/include"]) # this is a relative path
env.Append(CPPPATH=["#myotherlib/include"]) # this is an 'absolute' path
Якщо ви хочете додати власні прапорці компілятора під час створення свого модуля, вам потрібно спочатку клонувати env, щоб він не додавав ці прапорці до всієї збірки Godot (що може спричинити помилки). Приклад SCsub із спеціальними прапорцями:
Import('env')
module_env = env.Clone()
module_env.add_source_files(env.modules_sources, "*.cpp")
# Append CCFLAGS flags for both C and C++ code.
module_env.Append(CCFLAGS=['-O2'])
# If you need to, you can:
# - Append CFLAGS for C code only.
# - Append CXXFLAGS for C++ code only.
І, нарешті, файл конфігурації для модуля, це скрипт Python, який повинен мати назву config.py:
# config.py
def can_build(env, platform):
return True
def configure(env):
pass
Модуль запитується, чи його можна створювати для конкретної платформи (у цьому випадку True означає, що він збиратиметься для кожної платформи).
І все. Сподіваюся, це було не надто складно! Ваш модуль має виглядати так:
godot/modules/summator/config.py
godot/modules/summator/summator.h
godot/modules/summator/summator.cpp
godot/modules/summator/register_types.h
godot/modules/summator/register_types.cpp
godot/modules/summator/SCsub
Потім ви можете заархівувати його та поділитися модулем з усіма іншими. Під час створення для кожної платформи (інструкції в попередніх розділах) ваш модуль буде включено.
Користування модулем
Тепер ви можете використовувати ваш щойно створений модуль з будь-якого сценарію:
var s = Summator.new()
s.add(10)
s.add(20)
s.add(30)
print(s.get_total())
s.reset()
Результатом буде 60.
Дивись також
Попередній приклад Summator чудово підходить для невеликих спеціальних модулів, але що, якщо ви хочете використовувати більшу зовнішню бібліотеку? Зверніться до Прив'язка до зовнішніх бібліотек, щоб дізнатися більше про прив’язку до зовнішніх бібліотек.
Попередження
Якщо ваш модуль призначений для доступу з запущеного проекту (а не лише з редактора), ви також повинні перекомпілювати кожен шаблон експорту, який ви плануєте використовувати, а потім указати шлях до спеціального шаблону в кожному стилі експорту. Інакше ви отримаєте помилки під час запуску проекту, оскільки модуль не скомпільовано в шаблон експорту. Перегляньте сторінки Compiling для отримання додаткової інформації.
Зовнішня компіляція модуля
Компіляція модуля передбачає переміщення вихідних кодів модуля безпосередньо в каталог modules/ механізму. Хоча це найпростіший спосіб компіляції модуля, є кілька причин, чому це може бути непрактично:
Потрібно вручну копіювати вихідні коди модулів кожного разу, коли ви хочете скомпілювати механізм з модулем або без нього, або вживати додаткових кроків, необхідних для ручного вимкнення модуля під час компіляції за допомогою параметра збірки, подібного до
module_summator_enabled=no. Створення символічних посилань також може бути рішенням, але вам може додатково знадобитися подолати обмеження ОС, як-от потреба в привілеї символічного посилання, якщо це робити за допомогою сценарію.Залежно від того, чи потрібно вам працювати з вихідним кодом двигуна, файли модулів, додані безпосередньо до
modules/, змінюють робоче дерево до точки, коли використання VCS (наприклад,git) виявляється громіздким, оскільки ви необхідно переконатися, що лише пов’язаний з двигуном код фіксується шляхом фільтрації змін.
Отже, якщо ви вважаєте, що потрібна незалежна структура користувальницьких модулів, візьмемо наш модуль «summator» і перемістимо його до батьківського каталогу механізму:
mkdir ../modules
mv modules/summator ../modules
Скомпілюйте движок із нашим модулем, надавши опцію збірки custom_modules, яка приймає розділений комами список шляхів до каталогу, що містить спеціальні модулі C++, подібно до наступного:
scons custom_modules=../modules
Система збірки має виявити всі модулі в каталозі ../modules і скомпілювати їх відповідно, включаючи наш модуль «summator».
Попередження
Будь-який шлях, переданий до custom_modules, буде внутрішньо перетворено на абсолютний шлях, щоб відрізнити спеціальні та вбудовані модулі. Це означає, що такі речі, як створення документації модуля, можуть покладатися на певну структуру шляху на вашій машині.
Налаштування ініціалізації типів модулів
Модулі можуть взаємодіяти з іншими вбудованими класами двигуна під час виконання та навіть впливати на спосіб ініціалізації основних типів. Поки що ми використовували register_summator_types як спосіб додати класи модулів, які будуть доступні в механізмі.
Приблизний порядок налаштування двигуна можна підсумувати як список таких методів реєстрації типів:
preregister_module_types();
preregister_server_types();
register_core_singletons();
register_server_types();
register_scene_types();
EditorNode::register_editor_types();
register_platform_apis();
register_module_types();
initialize_physics();
initialize_navigation_server();
register_server_singletons();
register_driver_types();
ScriptServer::init_languages();
Наш клас Summator ініціалізується під час виклику register_module_types(). Уявіть, що нам потрібно задовольнити якусь загальну залежність середовища виконання модуля (наприклад, одиночних елементів) або дозволити нам перевизначати існуючі зворотні виклики методів двигуна, перш ніж їх зможе призначити сам механізм. У цьому випадку ми хочемо переконатися, що наші класи модулів зареєстровані перед будь-яким іншим вбудованим типом.
Тут ми можемо визначити необов’язковий метод preregister_summator_types(), який буде викликаний перед будь-яким іншим під час етапу налаштування двигуна preregister_module_types().
Тепер нам потрібно додати цей метод до заголовка register_types і вихідних файлів:
#define MODULE_SUMMATOR_HAS_PREREGISTER
void preregister_summator_types();
void register_summator_types();
void unregister_summator_types();
Примітка
На відміну від інших методів реєстру, ми повинні явно визначити MODULE_SUMMATOR_HAS_PREREGISTER, щоб дозволити системі збирання знати, які відповідні виклики методів слід включити під час компіляції. Ім'я модуля також має бути перетворено у верхній регістр.
#include "register_types.h"
#include "core/object/class_db.h"
#include "summator.h"
void preregister_summator_types() {
// Called before any other core types are registered.
// Nothing to do here in this example.
}
void register_summator_types() {
ClassDB::register_class<Summator>();
}
void unregister_summator_types() {
// Nothing to do here in this example.
}
Написання індивідуальної документації
Написання документації може здатися нудним завданням, але настійно рекомендуємо задокументувати ваш щойно створений модуль, щоб користувачам було легше використовувати його. Не кажучи вже про те, що код, який ви написали рік тому, може стати невідрізнимим від коду, написаного кимось іншим, тому будьте добрі до себе в майбутньому!
Існує кілька кроків, щоб налаштувати власні документи для модуля:
Створіть новий каталог у корені модуля. Ім’я каталогу може бути будь-яким, але в цьому розділі ми використовуватимемо ім’я
doc_classes.Тепер нам потрібно відредагувати
config.py, додати наступний фрагмент:def get_doc_path(): return "doc_classes" def get_doc_classes(): return [ "Summator", ]
Функція get_doc_path() використовується системою збирання для визначення розташування документів. У цьому випадку вони будуть розташовані в каталозі modules/summator/doc_classes. Якщо ви не визначите це, шлях документа для вашого модуля повертатиметься до головного каталогу doc/classes.
Метод get_doc_classes() необхідний, щоб система збирання знала, які зареєстровані класи належать до модуля. Тут потрібно вказати всі свої класи. Класи, які ви не вказали, потраплять у головний каталог doc/classes.
Порада
Ви можете використовувати Git, щоб перевірити, чи пропустили ви деякі заняття, перевіривши невідстежувані файли за допомогою git status. Наприклад:
git status
Приклад виводу:
Untracked files:
(use "git add <file>..." to include in what will be committed)
doc/classes/MyClass2D.xml
doc/classes/MyClass4D.xml
doc/classes/MyClass5D.xml
doc/classes/MyClass6D.xml
...
Тепер ми можемо створити документацію:
Ми можемо зробити це, запустивши doctool Godot, наприклад godot --doctool <path>, який створить дамп посилання API механізму на заданий <path> у форматі XML.
У нашому випадку ми вкажемо його на корінь клонованого сховища. Ви можете вказати його в іншу папку та просто скопіювати файли, які вам потрібні.
Виконати команду:
bin/<godot_binary> --doctool .
Тепер, якщо ви перейдете до папки godot/modules/summator/doc_classes, ви побачите, що вона містить файл Summator.xml або будь-які інші класи, на які ви посилалися у get_doc_classes функція.
Edit the file(s) following the class reference primer and recompile the engine.
Після завершення процесу компіляції документи стануть доступними у вбудованій системі документації двигуна.
Щоб підтримувати документацію в актуальному стані, все, що вам потрібно зробити, це просто змінити один із файлів XML і відтепер перекомпілювати механізм.
Якщо ви зміните API свого модуля, ви також можете повторно розпакувати документи, вони міститимуть те, що ви додали раніше. Звичайно, якщо ви вказуєте його на свою папку godot, переконайтеся, що ви не втратите роботу, витягнувши старіші документи зі старішої збірки двигуна поверх новіших.
Зауважте, що якщо у вас немає прав доступу на запис до наданого <path>, ви можете зіткнутися з помилкою, подібною до наведеної нижче:
ERROR: Can't write doc file: docs/doc/classes/@GDScript.xml
At: editor/doc/doc_data.cpp:956
Написання індивідуальних тестів
Можна писати самодостатні модульні тести як частину модуля C++. Якщо ви ще не знайомі з процесом модульного тестування в Godot, зверніться до Модульне тестування.
Процедура така:
Створіть новий каталог під назвою
tests/у корені вашого модуля:
cd modules/summator
mkdir tests
cd tests
Створіть новий набір тестів:
test_summator.h. Заголовок має мати префіксtest_, щоб система збірки могла зібрати його та включити як частинуtests/test_main.cpp, де виконуються тести.Напишіть кілька тестів. Ось приклад:
#pragma once
#include "tests/test_macros.h"
#include "modules/summator/summator.h"
namespace TestSummator {
TEST_CASE("[Modules][Summator] Adding numbers") {
Ref<Summator> s = memnew(Summator);
CHECK(s->get_total() == 0);
s->add(10);
CHECK(s->get_total() == 10);
s->add(20);
CHECK(s->get_total() == 30);
s->add(30);
CHECK(s->get_total() == 60);
s->reset();
CHECK(s->get_total() == 0);
}
} // namespace TestSummator
Скомпілюйте механізм за допомогою
scons tests=yesі запустіть тести за допомогою такої команди:
./bin/<godot_binary> --test --source-file="*test_summator*" --success
Тепер ви повинні побачити мимохідні твердження.
Додавання власних піктограм редактора
Подібно до того, як ви можете писати самодостатню документацію в модулі, ви також можете створювати власні значки для класів, які відображатимуться в редакторі.
Для фактичного процесу створення піктограм редактора, які будуть інтегровані в механізм, будь ласка, спочатку зверніться до Іконки редактора.
Створивши піктограми, виконайте такі дії:
Створіть новий каталог у корені модуля під назвою
icons. Це типовий шлях для механізму пошуку піктограм редактора модуля.Перемістіть щойно створені піктограми
svg(оптимізовані чи ні) до цієї папки.Перекомпілюйте двигун і запустіть редактор. Тепер піктограми з’являться в інтерфейсі редактора, де це необхідно.
Якщо ви хочете зберігати свої піктограми десь у своєму модулі, додайте наступний фрагмент коду до config.py, щоб замінити шлях за замовчуванням:
def get_icons_path(): return "path/to/icons"
Підведення підсумків
Не забудьте:
Використовуйте макрос
GDCLASSдля успадкування, щоб Godot міг його обернути.Використовуйте
_bind_methods, щоб прив’язати свої функції до сценаріїв і дозволити їм працювати як зворотні виклики для сигналів.Уникайте множинного успадкування для класів, які піддаються Godot, оскільки
GDCLASSне підтримує це. Ви все ще можете використовувати множинне успадкування у своїх власних класах, якщо вони не піддаються дії API сценаріїв Godot.
Але це ще не все, залежно від того, що ви робите, вас чекають деякі (сподіваємось, позитивні) сюрпризи.
Якщо ви успадкуєте від class_Node (або будь-якого похідного типу вузла, наприклад Sprite2D), ваш новий клас з’явиться в редакторі в дереві успадкування в діалоговому вікні «Додати вузол».
Якщо ви успадкуєте від class_Resource, він з’явиться у списку ресурсів, і всі доступні властивості можна буде серіалізувати під час збереження/завантаження.
За цією ж логікою ви можете розширити редактор і майже будь-яку область движка.