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 Object

Vedi anche

Questa pagina descrive l'implementazione in C++ degli oggetti in Godot. Stai cercando il riferimento alla classe Object? Dai un'occhiata qui.

Definizione generale

Object è la classe base per quasi tutto. La maggior parte delle classi in Godot eredita direttamente o indirettamente da essa. Per dichiararle basta usare una singola macro come questa:

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

Gli oggetti offrono molte funzionalità integrate, come la riflessione e le proprietà modificabili:

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

Riferimenti:

Registrare classi Object

La maggior parte delle sottoclassi di Object sono registrate chiamando GDREGISTER_CLASS.

GDREGISTER_CLASS(MyCustomClass)

Questo registrerà la classe come classe pubblica con nome nel ClassDB, consentendo di istanziarla tramite script, codice o deserializzazione. Si noti che le classi registrate come GDREGISTER_CLASS dovrebbero tenere conto di essere istanziate o liberate automaticamente, ad esempio dall'editor o dal sistema di documentazione.

Oltre a GDREGISTER_CLASS, ci sono alcune altre modalità di "privatezza":

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

È anche possibile utilizzare GDSOFTCLASS(MyCustomClass, SuperClass) invece di GDCLASS(MyCustomClass, SuperClass). Le classi definite in questo modo non sono proprio registrate nel ClassDB. Talvolta è utilizzato per sottoclassi specifiche a una piattaforma.

Registrare i binding

Le classi derivate da oggetti possono sovrascrivere la funzione statica static void _bind_methods(). Quando una classe viene registrata, questa funzione statica viene chiamata per registrare tutti i metodi, le proprietà, le costanti, ecc. dell'oggetto. Viene chiamata una sola volta.

All'interno di _bind_methods, ci sono un paio di cose che si possono fare. Una di queste è registrare funzioni:

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

I valori predefiniti per gli argomenti si possono passare come parametri alla fine:

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

I valori predefiniti si devono fornire nello stesso ordine in cui sono dichiarati, saltando gli argomenti obbligatori e fornendo i valori predefiniti per quelli facoltativi. Questa sintassi corrisponde a quella usata per dichiarare i metodi in C++.

D_METHOD è una macro che converte "methodname" in StringName per una più efficienza. I nomi degli argomenti sono utilizzati per l'introspezione, ma quando si compila un rilascio, la macro li ignora, quindi le stringhe rimangono inutilizzate e vengono rimosse come misura di ottimizzazione.

Dare un occhiata al metodo _bind_methods di Control o Object per più esempi.

Se si aggiungono solo moduli e funzionalità che non si prevede siano documentate in maniera così approfondita, la macro D_METHOD() si può tranquillamente ignorare e, per brevità, si può passare una stringa che ne passi il nome.

Riferimenti:

Costanti

Le classi spesso hanno enumerazioni come:

enum SomeMode {
   MODE_FIRST,
   MODE_SECOND
};

Affinché funzionino quando si vincolano ai metodi, l'enumerazione deve essere dichiarata convertibile in int. È disponibile una macro per facilitare questa operazione:

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

Le costanti si possono anche vincolare all'interno di _bind_methods, utilizzando:

BIND_CONSTANT(MODE_FIRST);
BIND_CONSTANT(MODE_SECOND);

Proprietà (set/get)

Le proprietà esportate degli oggetti, le proprietà sono utili per:

  • Serializzare e deserializzare l'oggetto.

  • Creare di una lista di valori modificabili per la classe derivata da Object.

Le proprietà sono solitamente definite dalla classe PropertyInfo() e costruite come:

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

Per esempio:

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

Questa è una proprietà di tipo intero denominata "amount". L'indicazione è un intervallo, che va da 0 a 49 con incrementi di 1 (interi). È utilizzabile solo per l'editor (modificare visivamente il valore), ma non sarà serializzato.

Un altro esempio:

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

Questa è una proprietà stringa, può accettare qualsiasi stringa, ma l'editor accetterà solo quelle definite come indicazione. Poiché non sono stati specificati flag di utilizzo, quelli predefiniti sono PROPERTY_USAGE_STORAGE e PROPERTY_USAGE_EDITOR.

Esistono numerose indicazioni e flag di utilizzo disponibili in object.h, vale la pena dargli un'occhiata.

Le proprietà possono anche funzionare come proprietà di C# ed essere accessibili da script tramite indicizzazione, ma questo utilizzo è generalmente sconsigliato, poiché l'uso di funzioni è preferibile per motivi di leggibilità. Molte proprietà sono inoltre vincolate a categorie, come "animation/frame", che rendono impossibile l'indicizzazione a meno che non si utilizzi l'operatore [].

Da _bind_methods(), le proprietà si possono creare e vincolare purché esistano funzioni set/get. Esempio:

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

In questo modo la proprietà viene creata, utilizzando il setter e il getter.

Vincolare proprietà tramite _set/_get/_get_property_list

Esiste un metodo aggiuntivo per creare proprietà quando si desidera più flessibilità (ad esempio aggiungendo o rimuovendo proprietà in base al contesto).

Le seguenti funzioni si possono sovrascrivere in una classe derivata da Object, NON sono virtuali, NON renderle virtuali, vengono chiamate a ogni sovrascrittura e le quelle precedenti non vengono invalidate (chiamata multilivello).

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

Questa soluzione è anche un po' meno efficiente, poiché p_property deve essere confrontato con i nomi desiderati in ordine seriale.

Segnali

Gli oggetti possono avere una serie di segnali definiti (simili ai delegati in altri linguaggi). Questo esempio mostra come connettersi a essi:

// 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 è una macro per creare un puntatore a funzione richiamabile personalizzata per le funzioni membro. Per i valori di p_flags, consulta ConnectFlags.

L'aggiunta di segnali a una classe è effettuata in _bind_methods, attraverso la macro ADD_SIGNAL, ad esempio:

ADD_SIGNAL(MethodInfo("been_killed"))

Appartenenza e casting degli oggetti

Gli oggetti sono allocati nell'heap. Esistono due diversi modelli di appartenenza:

  • Gli oggetti derivati da RefCounted sono contati per riferimento.

  • Tutti gli altri oggetti sono gestiti manualmente in memoria.

I modelli di appartenenza sono fondamentalmente diversi. Consulta la sezione relativa a ciascuno di essi per informazioni su come creare, memorizzare e liberare l'oggetto.

Quando non si sa se un oggetto passato (tramite Object *) è RefCounted ed è necessario memorizzarlo, è consigliabile memorizzare il suo ObjectID anziché un puntatore (come spiegato di seguito, nella sezione sulla gestione manuale della memoria).

Quando un oggetto viene passato tramite Variant, soprattutto quando si utilizzano callback differiti, è possibile che l'oggetto Object * contenuto sia già stato liberato al momento dell'esecuzione della funzione. Invece di convertire direttamente in Object *, è consigliabile utilizzare get_validated_object:

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

Gestione manuale della memoria

Gli oggetti gestiti manualmente in memoria sono creati tramite memnew e liberati tramite memdelete:

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

Quando non si è gli unici proprietari di un oggetto, memorizzare un puntatore ad esso è pericoloso: l'oggetto potrebbe in qualsiasi momento essere liberato attraverso altri riferimenti ad esso, rendendo il puntatore pendente e, di conseguenza, causando un arresto anomalo.

Quando si memorizzano oggetti di cui non si è gli unici proprietari, è consigliabile memorizzare il loro ObjectID anziché un puntatore:

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.

Gestione della memoria RefCounted

Le sottoclassi di RefCounted sono gestite in memoria con la semantica del conteggio dei riferimenti.

Sono costruiti tramite memnew e si dovrebbero memorizzare in istanze di Ref. Quando l'ultima istanza di Ref viene eliminata, l'oggetto si autodistrugge automaticamente.

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

Non si dovrebbe mai chiamare memdelete per le sottoclassi di RefCounted, perché potrebbero esserci altri proprietari.

Inoltre, non si dovrebbero mai memorizzare le sottoclassi di RefCounted utilizzando puntatori grezzi, ad esempio RefCounted *object = memnew(RefCounted). Questo non è sicuro perché altri proprietari potrebbero distruggere l'oggetto, lasciandoti con un puntatore pendente, il che eventualmente causerà un arresto anomalo.

Riferimenti:

Dynamic casting

Godot fornisce un casting dinamico tra classi derivate da Object, ad esempio:

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

Se il cast fallisce, viene restituito nullptr. Questo funziona allo stesso modo di dynamic_cast, ma non utilizza C++ RTTI.

Notifiche

Tutti gli oggetti in Godot hanno un metodo _notification che consente di rispondere ai callback a livello di motore che potrebbero riguardarli. Ulteriori informazioni sono disponibili nella pagina Notifiche di Godot.

Risorse

Resource eredita da RefCounted, quindi tutte le risorse sono conteggiate per riferimento. Le risorse possono facoltativamente contenere un percorso, che fa riferimento a un file su disco. Questo si può impostare con resource.set_path(path), sebbene ciò sia normalmente effettuato dal caricatore di risorse. Due risorse diverse non possono avere lo stesso percorso; tentare di fare ciò causerà un errore.

Anche le risorse senza un percorso sono permesse.

Riferimenti:

Caricamento delle risorse

Le risorse si possono caricare con l'API di ResourceLoader, in questo modo:

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

Se un riferimento a una risorsa è stato caricato in precedenza ed è presente in memoria, il ResourceLoader restituirà tale riferimento. Ciò significa che può essere caricata una sola risorsa alla volta da un file referenziato su disco.

Riferimenti:

Salvataggio delle risorse

È possibile salvare una risorsa tramite l'API del salvatore di risorse:

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

L'istanza sarà salvata. Le sotto-risorse che hanno un percorso a un file saranno salvate come riferimento a quella risorsa. Le sotto-risorse senza un percorso saranno raggruppate con la risorsa salvata e vi saranno assegnati dei sub-ID, come res://someresource.res::1. Questo aiuta anche a memorizzarle nella cache quando vengono caricate.

Riferimenti: