Класс Object
См. также
На этой странице описывается реализация объектов в Godot на C++. Ищете справочник по классу Object? Посмотрите здесь.
Общее определение
Object is the base class for almost everything. Most classes in Godot inherit directly or indirectly from it. Declaring them is a matter of using a single macro like this:
class CustomObject : public Object {
GDCLASS(CustomObject, Object); // This is required to inherit from Object.
};
Objects come with a lot of built-in functionality, like reflection and editable properties:
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
Источники:
Registering Object classes
Most Object subclasses are registered by calling GDREGISTER_CLASS.
GDREGISTER_CLASS(MyCustomClass)
This will register it as a named, public class in the ClassDB, which will allow the class to be instantiated by
scripts, code, or by deserialization. Note that classes registered as GDREGISTER_CLASS should expect to be
instantiated or freed automatically, for example by the editor or the documentation system.
Besides GDREGISTER_CLASS, there are a few other modes of privateness:
// 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);
It is also possible to use GDSOFTCLASS(MyCustomClass, SuperClass) instead of GDCLASS(MyCustomClass, SuperClass).
Classes defined this way are not registered in the ClassDB at all. This is sometimes used for platform-specific
subclasses.
Registering bindings
Object-derived classes can override the static function
static void _bind_methods(). When the class is registered, this
static function is called to register all the object methods,
properties, constants, etc. It's only called once.
Внутри _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 is a macro to create a custom callable function pointer to member functions.
For the values of p_flags, see ConnectFlags.
Добавление сигналов в класс делается в _bind_methods, через использование макроса ADD_SIGNAL например:
ADD_SIGNAL(MethodInfo("been_killed"))
Object ownership and casting
Objects are allocated on the heap. There are two different ownership models:
Objects derived from
RefCountedare reference counted.All other objects are manually memory managed.
The ownership models are fundamentally different. Refer to the section for each respectively to learn how to create, store, and free the object.
When you do not know whether an object passed to you (via Object *) is RefCounted, and you need to store it,
you should store its ObjectID rather than a pointer (as explained below, in the manual memory management section).
When an object is passed to you via Variant, especially when using deferred callbacks, it is
possible that the contained Object * was already freed by the time your function runs.
Instead of converting directly to Object *, you should use get_validated_object:
void do_something(Variant p_variant) {
Object *object = p_variant.get_validated_object();
ERR_FAIL_NULL(object);
}
Manual memory management
Manually memory managed objects are created using memnew and freed using memdelete:
Node *node = memnew(Node);
// ...
memdelete(node);
node = nullptr;
When you are not the sole owner of an object, storing a pointer to it is dangerous: The object may at any point be freed through other references to it, causing your pointer to become a dangling pointer, which will eventually result in a crash.
When storing objects you are not the only owner of, you should store its ObjectID rather than a pointer:
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 memory management
RefCounted subclasses are memory managed with reference counting semantics.
They are constructed using memnew, and should be stored in Ref instances. When the last Ref instance is
dropped, the object automatically self-destructs.
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();
You should never call memdelete for RefCounted subclasses, because there may be other owners of it.
You should also never store RefCounted subclasses using raw pointers, for example
RefCounted *object = memnew(RefCounted). This is unsafe because other owners may destruct the object, leaving you
with a dangling pointer, which will eventually result in a crash.
Источники:
Динамическое приведение
Godot позволяет динамическое приведение между классами наследниками Object, к примеру:
void some_func(Object *p_object) {
Button *button = Object::cast_to<Button>(p_object);
}
If the cast fails, nullptr is returned. This works the same as dynamic_cast, but does not use
C++ RTTI.
Уведомления
All objects in Godot have a _notification method that allows them to respond to engine-level callbacks that may relate to it. More information can be found on the Уведомления Godot page.
Ресурсы
Resource наследует от RefCounted, поэтому все ресурсы подсчитываются по ссылкам. Ресурсы могут содержать путь, ссылающийся на файл на диске. Его можно задать с помощью resource.set_path(path), хотя обычно это делает загрузчик ресурсов. Два разных ресурса не могут иметь одинаковый путь; попытка сделать это приведёт к ошибке.
Ресурсы без пути тоже возможны.
Источники:
Загрузка Ресурсов
Ресурсы могут быть загружены через ResourceLoader API, например так:
Ref<Resource> res = ResourceLoader::load("res://someresource.res")
Если ссылка на этот ресурс уже загружена до этого и находится в памяти, ресурсный загрузчик вернёт эту ссылку. Это означает что только один ресурс загруженный из файла ссылается на диск в данное время.
resourceinteractiveloader (TODO)
Источники:
Сохранение Ресурсов
Сохранение ресурса может быть сделано через API сохранения ресурсов:
ResourceSaver::save("res://someresource.res", instance)
Экземпляр будет сохранён, а вложенные ресурсы, имеющие путь к файлу, будут сохранены как ссылка на этот ресурс. Вложенные ресурсы без пути будут объединены с сохранённым ресурсом и им будут назначены вложенные идентификаторы, например, res://someresource.res::1. Это также помогает кэшировать их при загрузке.