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.

Classe de objeto

Ver também

Esta página descreve a implementação em C++ de objetos no Godot. Procurando pela referência da classe Object? Dê uma olhada aqui.

Definição geral

Object é a classe base para quase tudo. A maioria das classes no Godot herda direta ou indiretamente dela. Declará-las é uma questão de usar uma única macro como esta:

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

Os objetos vêm com muitas funcionalidades embutidas, como reflexão e propriedades editáveis:

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

Referências:

Registrando classes de Objeto

A maioria das subclasses de Object é registrada chamando GDREGISTER_CLASS.

GDREGISTER_CLASS(MyCustomClass)

Isso a registrará como uma classe pública nomeada na ClassDB, o que permitirá que a classe seja instanciada por scripts, código ou por desserialização. Note que as classes registradas como GDREGISTER_CLASS devem esperar ser instanciadas ou liberadas automaticamente, por exemplo, pelo editor ou pelo sistema de documentação.

Além do GDREGISTER_CLASS, existem alguns outros modos de privacidade:

// 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);

Também é possível usar GDSOFTCLASS(MyCustomClass, SuperClass) em vez de GDCLASS(MyCustomClass, SuperClass). As classes definidas desta forma não são registradas na ClassDB. Isso às vezes é usado para subclasses específicas de cada plataforma.

Registrando vinculações

As classes derivadas de Object podem sobrescrever a função estática static void _bind_methods(). Quando a classe é registrada, esta função estática é chamada para registrar todos os métodos, propriedades, constantes do objeto, etc. Ela é chamada apenas uma vez.

Dentro de _bind_methods, existem várias coisas que podem ser feitas. Registrar funções é uma delas:

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

Os valores padrão para os argumentos podem ser passados como parâmetros ao final:

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

Os valores padrão devem ser fornecidos na mesma ordem em que são declarados, pulando os argumentos obrigatórios e depois fornecendo os valores padrão para os opcionais. Isso corresponde à sintaxe para declarar métodos em C++.

D_METHOD é uma macro que converte "nome_do_metodo" em um StringName para maior eficiência. Os nomes dos argumentos são usados para introspecção, mas ao compilar em modo de lançamento (release), a macro os ignora, de modo que as strings ficam sem uso e são otimizadas para fora.

Verifique o _bind_methods de Control ou Object para mais exemplos.

Se estiver apenas adicionando módulos e funcionalidades que não se espera documentar tão minuciosamente, a macro D_METHOD() pode ser ignorada com segurança e uma string passando o nome pode ser informada para fins de brevidade.

Referências:

Constantes

As classes frequentemente possuem enums como:

enum SomeMode {
   MODE_FIRST,
   MODE_SECOND
};

Para que estes funcionem ao serem vinculados a métodos, o enum deve ser declarado como conversível para int. Uma macro é fornecida para ajudar com isso:

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

As constantes também podem ser vinculadas dentro de _bind_methods, usando:

BIND_CONSTANT(MODE_FIRST);
BIND_CONSTANT(MODE_SECOND);

Propriedades (set/get)

Os objetos exportam propriedades; as propriedades são úteis para o seguinte:

  • Serializar e desserializar o objeto.

  • Criar uma lista de valores editáveis para a classe derivada de Object.

As propriedades são geralmente definidas pela classe PropertyInfo() e construídas como:

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

Por exemplo:

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

Esta é uma propriedade inteira chamada "amount". A dica (hint) é um intervalo (range), e o intervalo vai de 0 a 49 em passos de 1 (inteiros). Ela é utilizável apenas para o editor (edição visual do valor), mas não será serializada.

Outro exemplo:

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

Esta é uma propriedade de string, pode receber qualquer string, mas o editor só permitirá as que foram definidas na dica. Como nenhuma flag de uso foi especificada, as padrão são PROPERTY_USAGE_STORAGE e PROPERTY_USAGE_EDITOR.

Existem muitas dicas (hints) e flags de uso disponíveis em object.h, dê uma olhada nelas.

As propriedades também podem funcionar como propriedades do C# e ser acessadas a partir de scripts usando indexação, mas esse uso é geralmente desaconselhado, pois o uso de funções é preferível para a legibilidade. Muitas propriedades também são vinculadas a categorias, como "animation/frame", o que também torna a indexação impossível, a menos que se use o operador [].

A partir de _bind_methods(), as propriedades podem ser criadas e vinculadas desde que existam funções set/get. Exemplo:

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

Isso cria a propriedade usando o setter e o getter.

Vinculando propriedades usando _set/_get/_get_property_list

Um método adicional de criação de propriedades existe quando se deseja mais flexibilidade (ou seja, adicionar ou remover propriedades com base no contexto).

As seguintes funções podem ser sobrescritas em uma classe derivada de Object; elas NÃO são virtuais, NÃO as torne virtuais, elas são chamadas para cada sobrescrita e as anteriores não são invalidadas (chamada multinível).

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

Isso também é um pouco menos eficiente, já que p_property deve ser comparado contra os nomes desejados em ordem serial.

Sinais

Os objetos podem ter um conjunto de sinais definidos (semelhante a Delegates em outras linguagens). Este exemplo mostra como se conectar a eles:

// 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 é uma macro para criar um ponteiro de função callable customizado para funções de membro. Para os valores de p_flags, veja ConnectFlags.

A adição de sinais a uma classe é feita em _bind_methods, usando a macro ADD_SIGNAL, por exemplo:

ADD_SIGNAL(MethodInfo("been_killed"))

Propriedade e casting de Objeto

Os objetos são alocados no heap. Existem dois modelos de propriedade diferentes:

  • Os objetos derivados de RefCounted têm contagem de referências.

  • Todos os outros objetos são gerenciados manualmente na memória.

Os modelos de propriedade são fundamentalmente diferentes. Consulte a seção correspondente a cada um para aprender como criar, armazenar e liberar o objeto.

Quando você não souber se um objeto passado para você (via Object *) é RefCounted e precisar armazená-lo, você deve armazenar o seu ObjectID em vez de um ponteiro (conforme explicado abaixo, na seção de gerenciamento manual de memória).

Quando um objeto é passado para você via Variant, especialmente ao usar callbacks diferidos (deferred), é possível que o Object * contido já tenha sido liberado no momento em que sua função é executada. Em vez de converter diretamente para Object *, você deve usar get_validated_object:

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

Gerenciamento manual de memória

Os objetos gerenciados manualmente na memória são criados usando memnew e liberados usando memdelete:

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

Quando você não é o único dono de um objeto, armazenar um ponteiro para ele é perigoso: o objeto pode ser liberado a qualquer momento por meio de outras referências a ele, fazendo com que seu ponteiro se torne um ponteiro flutuante (dangling pointer), o que eventualmente resultará em um travamento.

Ao armazenar objetos dos quais você não é o único dono, você deve armazenar o seu ObjectID em vez de um ponteiro:

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.

Gerenciamento de memória RefCounted

As subclasses de RefCounted são gerenciadas na memória com semântica de contagem de referências.

Eles são construídos usando memnew e devem ser armazenados em instâncias de Ref. Quando a última instância de Ref é descartada, o objeto se destrói automaticamente.

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();

Você nunca deve chamar memdelete para subclasses de RefCounted, porque pode haver outros donos dele.

Você também nunca deve armazenar subclasses de RefCounted usando ponteiros brutos (raw pointers), por exemplo, RefCounted *object = memnew(RefCounted). Isso não é seguro porque outros donos podem destruir o objeto, deixando você com um ponteiro flutuante, o que eventualmente resultará em um travamento.

Referências:

Conversão dinâmica de tipo (Dynamic casting)

O Godot fornece conversão dinâmica de tipo (casting) entre classes derivadas de Object, por exemplo:

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

Se o cast falhar, nullptr é retornado. Isso funciona da mesma forma que o dynamic_cast, mas não utiliza o RTTI do C++.

Notificações

Todos os objetos no Godot possuem um método _notification que permite que respondam a callbacks de nível de motor que podem se relacionar a eles. Mais informações podem ser encontradas na página Notificações em Godot.

Recursos

O Resource herda de RefCounted, portanto todos os recursos têm contagem de referências. Os recursos podem opcionalmente conter um caminho (path), que faz referência a um arquivo no disco. Isso pode ser definido com resource.set_path(path), embora isso normalmente seja feito pelo carregador de recursos. Dois recursos diferentes não podem ter o mesmo caminho; tentar fazer isso resultará em um erro.

Recursos sem um caminho também funcionam perfeitamente.

Referências:

Carregamento de recursos

Os recursos podem ser carregados com a API ResourceLoader, assim:

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

Se uma referência àquele recurso tiver sido carregada anteriormente e estiver na memória, o ResourceLoader retornará aquela referência. Isso significa que só pode haver um recurso carregado a partir de um arquivo referenciado no disco ao mesmo tempo.

Referências:

Economia de recursos

Salvar um recurso pode ser feito com a API de salvamento de recursos (ResourceSaver):

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

A instância será salva, e os sub-recursos que possuem um caminho para um arquivo serão salvos como uma referência àquele recurso. Os sub-recursos sem um caminho serão empacotados junto com o recurso salvo e receberão sub-IDs, como res://someresource.res::1. Isso também ajuda a colocá-los em cache quando carregados.

Referências: