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 中 Object 的 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 以提高效率的巨集。引數名稱僅用於自省,在釋出編譯時會被忽略,字串也會被最佳化移除。
更多範例請參考 Control 或 Object 的 _bind_methods。
如果只是新增一些不需要詳細文件的模組或功能,可以略過 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 會匯出屬性,屬性在下列情境中特別有用:
序列化與反序列化物件。
為 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")
這是一個字串屬性,可接受任意字串,但編輯器僅允許使用提示中定義的字串。由於未指定使用旗標,預設會使用 PROPERTY_USAGE_STORAGE 與 PROPERTY_USAGE_EDITOR。
object.h 中有許多提示(hint)和使用旗標可用,歡迎參閱。
屬性也可以像 C# 的屬性一樣,透過索引在腳本中存取,但一般不建議這麼做,建議為了可讀性使用函式。許多屬性也會與分類綁定,例如「animation/frame」,除非使用運算子 [],否則無法直接以索引存取。
只要存在 set/get 函式,在 _bind_methods() 內即可建立並綁定屬性。例如:
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount"), "set_amount", "get_amount")
這會利用 Setter 與 Getter 建立該屬性。
用 _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 必須依序比對所有屬性名稱。
訊號
物件可以定義一組訊號(類似其他語言的 Delegate)。這裡示範如何連接訊號:
// 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")
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.
參考資料:
資源儲存
可使用 ResourceSaver API 儲存資源:
ResourceSaver::save("res://someresource.res", instance)
此動作會儲存該實體,擁有檔案路徑的子資源會以參照方式儲存。沒有路徑的子資源會與主資源一併打包,並分配子 ID,例如 res://someresource.res::1。這樣也有助於後續快取。