Object 類別

也參考

本頁說明 Godot 中 Object 的 C++ 實作。如果你正在尋找 Object 類別文件,請見 這裡

一般定義

Object 是幾乎所有事物的基底類別。Godot 中大多數類別都直接或間接繼承自它。Object 提供反射與可編輯屬性功能,宣告時只需使用一個巨集,如下:

class CustomObject : public Object {

    GDCLASS(CustomObject, Object); // this is required to inherit
};

這會為 Object 加入許多功能。例如:

obj = memnew(CustomObject);
print_line("Object class: ", obj->get_class()); // print object class

obj2 = Object::cast_to<OtherClass>(obj); // converting between classes, this also works without RTTI enabled.

參考資料:

註冊 Object

ClassDB 是一個靜態類別,負責保存所有繼承自 Object 的已註冊類別列表,並動態綁定這些類別的所有方法、屬性和整數常數。

類別可以透過呼叫下列方式註冊:

ClassDB::register_class<MyCustomClass>()

註冊後,該類別可以被腳本、程式碼實例化,或在反序列化時重新建立。

註冊為虛擬類別的方式相同,但該類別無法被實體化。

ClassDB::register_virtual_class<MyCustomClass>()

從 Object 衍生的類別可以覆寫靜態函式 static void _bind_methods()。當類別被註冊時,這個靜態函式會被呼叫來註冊所有物件的方法、屬性、常數等。這個函式只會被呼叫一次。如果衍生自 Object 的類別被實體化但尚未註冊,將會自動以虛擬類別方式註冊。

_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 必須依序比對所有屬性名稱。

動態型別轉換

Godot 支援在 Object 衍生類別之間進行動態型別轉換,例如:

void somefunc(Object *some_obj) {

     Button *button = Object::cast_to<Button>(some_obj);
}

若轉型失敗會回傳 NULL。這套系統會利用 RTTI,但即使關閉 RTTI 也能正常運作(只是速度較慢)。這對於追求精簡二進位檔案大小的平台(如 HTML5 或主機)特別有用。

訊號

物件可以定義一組訊號(類似其他語言的 Delegate)。這裡示範如何連接訊號:

obj->connect(<signal>, target_instance, target_method)
// for example:
obj->connect("enter_tree", this, "_node_entered_tree")

_node_entered_tree 方法必須用 ClassDB::bind_method 登錄於類別中(如前所述)。

_bind_methods 內使用 ADD_SIGNAL 巨集,即可為類別新增訊號,例如:

ADD_SIGNAL(MethodInfo("been_killed"))

通知

Godot 中所有物件都有 _notification 方法,可以回應引擎層級相關的回呼。詳情請參閱 Godot 通知 頁面。

參考資料

RefCounted 繼承自 Object,並擁有參照計數。它是所有參照計數物件型別的基礎類別。宣告時必須使用 Ref<> 樣板。例如:

class MyReference: public RefCounted {
    GDCLASS(MyReference, RefCounted);
};

Ref<MyReference> myref(memnew(MyReference));

myref 會自動進行參照計數。當沒有任何 Ref<> 樣板指向它時,會自動釋放記憶體。

參考資料:

資源

Resource 繼承自 RefCounted,因此所有資源都會進行參照計數。資源可以選擇性包含路徑,以對應磁碟上的檔案。這可透過 resource.set_path(path) 設定,但通常由資源載入器自動處理。不同資源不可有相同路徑,否則會發生錯誤。

資源也可以沒有路徑。

參考資料:

資源載入

可以用 ResourceLoader API 載入資源,例如:

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

如果該資源的參照先前已經被載入且仍在記憶體中,資源載入器會直接回傳該參照。這表示同一個磁碟檔案的資源,在同一時間只會有一份載入於記憶體中。

  • resourceinteractiveloader(待補)

參考資料:

資源儲存

可使用 ResourceSaver API 儲存資源:

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

此動作會儲存該實體,擁有檔案路徑的子資源會以參照方式儲存。沒有路徑的子資源會與主資源一併打包,並分配子 ID,例如 res://someresource.res::1。這樣也有助於後續快取。

參考資料: