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.

Клас об’єкта

Дивись також

На цій сторінці описано реалізацію C++ об’єктів у Godot. Шукаєте посилання на клас об’єктів? Have a look here.

Загальне визначення

Object – це базовий клас майже для всього. Більшість класів у Godot успадковуються від нього прямо чи опосередковано. Їх оголошення здійснюється за допомогою одного макросу, подібного до цього:

class CustomObject : public Object {
    GDCLASS(CustomObject, Object); // This is required to inherit from Object.
};

Об'єкти мають багато вбудованих функцій, таких як відображення та властивості, які можна редагувати:

CustomObject *obj = memnew(CustomObject);
print_line("Object class: ", obj->get_class()); // print object class

OtherClass *obj2 = Object::cast_to<OtherClass>(obj); // Converting between classes, similar to dynamic_cast

Посилання:

Реєстрація класів об'єктів

Більшість підкласів Object реєструються шляхом виклику GDREGISTER_CLASS.

GDREGISTER_CLASS(MyCustomClass)

Це зареєструє його як іменований публічний клас у ClassDB, що дозволить створювати екземпляри класу за допомогою скриптів, коду або десеріалізації. Зверніть увагу, що класи, зареєстровані як GDREGISTER_CLASS, повинні бути створені або звільнені автоматично, наприклад, редактором або системою документації.

Окрім GDREGISTER_CLASS, існує кілька інших режимів конфіденційності:

// Registers the class publicly, but prevents automatic instantiation through ClassDB.
GDREGISTER_VIRTUAL_CLASS(MyCustomClass);

// Registers the class publicly, but prevents all instantiation through ClassDB.
GDREGISTER_ABSTRACT_CLASS(MyCustomClass);

// Registers the class in ClassDB, but marks it as private,
// such that it is not visible to scripts or extensions.
// This is the same as not registering the class explicitly at all
// - in this case, the class is registered as internal automatically
// when it is first constructed.
GDREGISTER_INTERNAL_CLASS(MyCustomClass);

// Registers the class such that it is only available at runtime (but not in the editor).
GDREGISTER_RUNTIME_CLASS(MyCustomClass);

Також можна використовувати GDSOFTCLASS(MyCustomClass, SuperClass) замість GDCLASS(MyCustomClass, SuperClass). Класи, визначені таким чином, взагалі не реєструються в ClassDB. Іноді це використовується для підкласів, специфічних для платформи.

Реєстрація прив'язок

Класи, похідні від об'єктів, можуть перевизначати статичну функцію static void _bind_methods(). Коли клас реєструється, ця статична функція викликається для реєстрації всіх методів, властивостей, констант тощо об'єкта. Вона викликається лише один раз.

Усередині _bind_methods є кілька речей, які можна зробити. Функція реєстрації одна:

ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name", "arg3name"), &MyCustomType::method);

Значення за замовчуванням для аргументів можна передати як параметри в кінці:

ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name", "arg3name"), &MyCustomType::method, DEFVAL(-1), DEFVAL(-2)); // Default values for arg2name (-1) and arg3name (-2).

Значення за замовчуванням потрібно надавати в такому ж порядку, як вони оголошені, пропускаючи необхідні аргументи та надаючи значення за замовчуванням для необов’язкових. Це відповідає синтаксису для оголошення методів у C++.

D_METHOD – це макрос, який перетворює "methodname" на StringName для більшої ефективності. Назви аргументів використовуються для самоаналізу, але під час компіляції після випуску макрос ігнорує їх, тому рядки не використовуються та оптимізовані.

Щоб отримати більше прикладів, перевірте _bind_methods елемента Control або Object.

Якщо ви просто додаєте модулі та функціональні можливості, які, як очікується, не будуть задокументовані настільки ретельно, макрос D_METHOD() можна безпечно проігнорувати, а рядок, що передає назву, можна передати для стислості.

Посилання:

Константи

Класи часто мають такі переліки, як:

enum SomeMode {
   MODE_FIRST,
   MODE_SECOND
};

Щоб вони працювали під час прив’язки до методів, enum має бути оголошено конвертованим у int. Для цього надається макрос:

VARIANT_ENUM_CAST(MyClass::SomeMode); // now functions that take SomeMode can be bound.

Константи також можна зв’язати всередині _bind_methods за допомогою:

BIND_CONSTANT(MODE_FIRST);
BIND_CONSTANT(MODE_SECOND);

Властивості (встановити/отримати)

Властивості експорту об’єктів, властивості корисні для наступного:

  • Серіалізація та десеріалізація об’єкта.

  • Створення списку редагованих значень для похідного класу Object.

Властивості зазвичай визначаються класом PropertyInfo() і створюються як:

PropertyInfo(type, name, hint, hint_string, usage_flags)

Приклад:

PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "0,49,1", PROPERTY_USAGE_EDITOR)

Це цілочисельна властивість під назвою "сума". Підказка — це діапазон, який змінюється від 0 до 49 із кроком 1 (цілі числа). Його можна використовувати лише для редактора (редагування значення візуально), але не буде серіалізовано.

Ще один приклад:

PropertyInfo(Variant::STRING, "modes", PROPERTY_HINT_ENUM, "Enabled,Disabled,Turbo")

Це властивість рядка, може приймати будь-який рядок, але редактор дозволятиме лише визначені підказки. Оскільки прапорці використання не вказано, стандартними є PROPERTY_USAGE_STORAGE та PROPERTY_USAGE_EDITOR.

У object.h є багато підказок і позначок використання, перевірте їх.

Властивості також можуть працювати як властивості C# і доступ до них можна отримати зі сценарію за допомогою індексування, але таке використання зазвичай не рекомендується, оскільки використання функцій є кращим для читабельності. Багато властивостей також пов’язані з категоріями, такими як «анімація/кадр», які також унеможливлюють індексування без використання оператора [].

За допомогою _bind_methods() властивості можна створювати та зв’язувати, доки існують функції set/get. приклад:

ADD_PROPERTY(PropertyInfo(Variant::INT, "amount"), "set_amount", "get_amount")

Це створює властивість за допомогою сеттера та геттера.

Властивості прив’язки за допомогою _set/_get/_get_property_list

Існує додатковий метод створення властивостей, коли потрібна більша гнучкість (тобто додавання або видалення властивостей у контексті).

Наступні функції можна перевизначати в похідному класі Object, вони НЕ є віртуальними, НЕ робіть їх віртуальними, вони викликаються для кожного перевизначення, а попередні не анулюються (багаторівневий виклик).

protected:
     void _get_property_list(List<PropertyInfo> *r_props) const;      // return list of properties
     bool _get(const StringName &p_property, Variant &r_value) const; // return true if property was found
     bool _set(const StringName &p_property, const Variant &p_value); // return true if property was found

Це також трохи менш ефективно, оскільки p_property потрібно порівнювати з бажаними іменами в послідовному порядку.

Сигнали

Для об’єктів може бути визначений набір сигналів (подібно до делегатів в інших мовах). Цей приклад показує, як підключитися до них:

// This is the function signature:
//
// Error connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags = 0)
//
// For example:
obj->connect("signal_name_here", callable_mp(this, &MyCustomType::method), CONNECT_DEFERRED);

callable_mp — це макрос для створення власного вказівника на викликану функцію, що вказує на функції-члени. Значення p_flags див. у ConnectFlags.

Додавання сигналів до класу виконується в _bind_methods за допомогою макросу ADD_SIGNAL, наприклад:

ADD_SIGNAL(MethodInfo("been_killed"))

Володіння об'єктами та їх перетворення

Об'єкти розміщуються в купі. Існує дві різні моделі володіння:

  • Об'єкти, похідні від RefCounted, підраховуються за посиланнями.

  • Усі інші об'єкти керуються пам'яттю вручну.

Моделі власності принципово відрізняються. Зверніться до розділу для кожної з них відповідно, щоб дізнатися, як створювати, зберігати та звільняти об'єкт.

Коли ви не знаєте, чи є об'єкт, переданий вам (через Object *), RefCounted, і вам потрібно його зберегти, вам слід зберігати його ObjectID, а не вказівник (як пояснено нижче, у розділі про ручне керування пам'яттю).

Коли об'єкт передається вам через Variant, особливо при використанні відкладених зворотних викликів, можливо, що об'єкт Object *, що міститься в ньому, вже був звільнений на момент виконання вашої функції. Замість безпосереднього перетворення на Object *, слід використовувати get_validated_object:

void do_something(Variant p_variant) {
    Object *object = p_variant.get_validated_object();
    ERR_FAIL_NULL(object);
}

Ручне керування пам'яттю

Об'єкти пам'яті, що керуються вручну, створюються за допомогою memnew та звільняються за допомогою memdelete:

Node *node = memnew(Node);
// ...
memdelete(node);
node = nullptr;

Коли ви не є єдиним власником об'єкта, зберігання вказівника на нього є небезпечним: об'єкт може бути звільнений у будь-який момент через інші посилання на нього, що призведе до перетворення вашого вказівника на завислий вказівник, що зрештою призведе до аварійного завершення роботи.

Під час зберігання об'єктів, власником яких ви не є єдиним, слід зберігати їх ObjectID, а не вказівник:

Node *node = memnew(Node);
ObjectID node_id = node.get_instance_id();
// ...
Object *maybe_node = ObjectDB::get_instance(node_id);
ERR_FAIL_NULL(maybe_node); // The node may have been freed between calls.

RefCounted управління пам'яттю

Підкласи RefCounted керуються пам'яттю за допомогою семантики підрахунку посилань.

Вони створюються за допомогою memnew та повинні зберігатися в екземплярах Ref. Коли останній екземпляр Ref видаляється, об'єкт автоматично самознищується.

class MyRefCounted: public RefCounted {
    GDCLASS(MyReference, RefCounted);
};

Ref<MyRefCounted> my_ref = memnew(MyRefCounted);
// ...
// Ref holds shared ownership over the object, so the object
// will not be freed. As long as you have a valid, non-null
// Ref, it can be safely assumed the object is still valid.
my_ref->get_class_name();

Ніколи не слід викликати memdelete для підкласів RefCounted, оскільки можуть бути інші власники таких класів.

Також ніколи не слід зберігати підкласи RefCounted, використовуючи необроблені вказівники, наприклад RefCounted *object = memnew(RefCounted). Це небезпечно, оскільки інші власники можуть знищити об'єкт, залишивши вам завислий вказівник, що зрештою призведе до аварійного завершення роботи.

Посилання:

Динамічний кастинг

Godot забезпечує динамічне приведення між класами, похідними від об’єктів, наприклад:

void some_func(Object *p_object) {
     Button *button = Object::cast_to<Button>(p_object);
}

Якщо приведення типів не вдається, повертається nullptr. Це працює так само, як dynamic_cast, але не використовує C++ RTTI.

Сповіщення

Усі об'єкти в Godot мають метод _notification, який дозволяє їм реагувати на зворотні виклики рівня движка, що можуть бути пов'язані з ним. Більше інформації можна знайти на сторінці Сповіщення Godot.

Ресурси

Resource успадковується від RefCounted, тому всі ресурси підраховуються посиланнями. Ресурси можуть додатково містити шлях, який посилається на файл на диску. Це можна встановити за допомогою resource.set_path(path), хоча зазвичай це робить завантажувач ресурсів. Жодні два різні ресурси не можуть мати однаковий шлях; спроба зробити це призведе до помилки.

Ресурси без шляху теж підходять.

Посилання:

Завантаження ресурсів

Ресурси можна завантажувати за допомогою API ResourceLoader, наприклад:

Ref<Resource> res = ResourceLoader::load("res://someresource.res")

If a reference to that resource has been loaded previously and is in memory, the ResourceLoader will return that reference. This means that there can be only one resource loaded from a file referenced on disk at the same time.

Посилання:

Економія ресурсів

Збереження ресурсу можна виконати за допомогою API збереження ресурсів:

ResourceSaver::save("res://someresource.res", instance)

Екземпляр буде збережено, а підресурси, які мають шлях до файлу, будуть збережені як посилання на цей ресурс. Підресурси без шляху буде об’єднано зі збереженим ресурсом і призначеними підідентифікаторами, як-от res://someresource.res::1. Це також допомагає кешувати їх під час завантаження.

Посилання: