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...
Класс Object
См. также
На этой странице описывается реализация объектов в Godot на C++. Ищете справочник по классу Object? Посмотрите здесь.
Общее определение
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
Большинство подклассов 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. Иногда это используется для платформозависимых подклассов.
Регистрация привязок
Классы, производные от Object, могут переопределять статическую функцию 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
};
Чтобы они работали при привязке к методам, перечисление должно быть объявлено преобразуемым в int. Для этого предусмотрен макрос:
VARIANT_ENUM_CAST(MyClass::SomeMode); // now functions that take SomeMode can be bound.
Константы также можно привязывать внутри _bind_methods, используя:
BIND_CONSTANT(MODE_FIRST);
BIND_CONSTANT(MODE_SECOND);
Свойства (set/get)
Объекты экспортируют свойства, которые полезны для следующего:
Сериализации и десирализации объекта.
Создание списка редактируемых значений для наследуемых от Object классов.
Свойства обычно определяются классом PropertyInfo() и конструируются как:
PropertyInfo(type, name, hint, hint_string, usage_flags)
Например:
PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "0,49,1", PROPERTY_USAGE_EDITOR)
Это целочисленное свойство с именем "amount". Подсказка представляет собой диапазон от 0 до 49 с шагом 1 (целые числа). Его можно использовать только в редакторе (визуальном редактировании значения), но он не будет сериализован.
Другой пример:
PropertyInfo(Variant::STRING, "modes", PROPERTY_HINT_ENUM, "Enabled,Disabled,Turbo")
Это строковое свойство, может брать любую строку но редактор будет разрешать только определённые подсказанные(hint). Так как не определенно флагов использования, по умолчанию это PROPERTY_USAGE_STORAGE и PROPERTY_USAGE_EDITOR.
Существует много подсказок и флагов использования доступные в object.h, проверьте их.
Свойства могут также работать как свойства C# и могут быть доступные из скрипта через индексирование, но это их использование в целом не приветствуется, так использования функций предпочтительнее для понятности. Многие свойства также разбиты на категории, такие как "animation/frame" которые делают индексирование невозможным разве что с использованием оператора [].
Из _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(MyRefCounted, 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 позволяет динамическое приведение между классами наследниками Object, к примеру:
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), хотя обычно это делает загрузчик ресурсов. Два разных ресурса не могут иметь одинаковый путь; попытка сделать это приведёт к ошибке.
Ресурсы без пути тоже возможны.
Источники:
Загрузка Ресурсов
Ресурсы могут быть загружены через ResourceLoader API, например так:
Ref<Resource> res = ResourceLoader::load("res://someresource.res")
Если ссылка на этот ресурс уже была загружена ранее и находится в памяти, ResourceLoader вернёт эту ссылку. Это означает, что одновременно может быть загружен только один ресурс из файла, на который ссылается диск.
Источники:
Сохранение Ресурсов
Сохранение ресурса может быть сделано через API сохранения ресурсов:
ResourceSaver::save("res://someresource.res", instance)
Экземпляр будет сохранён, а вложенные ресурсы, имеющие путь к файлу, будут сохранены как ссылка на этот ресурс. Вложенные ресурсы без пути будут объединены с сохранённым ресурсом и им будут назначены вложенные идентификаторы, например, res://someresource.res::1. Это также помогает кэшировать их при загрузке.