Object 類別

也參考

本頁說明的是 Godot 中 Object 的 C++ 實作。在找 Object 類別的參照文件嗎? 請參考這裡。

一般性定義

Object 幾乎是所有東西的基礎類別。在 Godot 中幾乎所有的類別都是直接或間接地繼承 Object。Object 提供了反射 (Reflection) 與可編輯的屬性,而定義 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"), &MyCustomMethod);

引數的預設值可以通過相反的順序來傳遞:

ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name"), &MyCustomType::method, DEFVAL(-1)); // default value for arg2name

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」,Hint 則為一個範圍 (range),為 0 值 49 且間隔為 1 的值 (整數)。Hint 只會用於編輯器 (用於進行視覺化編輯),而不會被序列化。

另一個範例:

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

這個範圍是一個字串屬性,其中可以保存任何字串,但編輯器只允許使用在 Hint 中定義的值。由於沒有指定使用旗標 (Usage Flag),預設的旗標為 PROPERTY_USAGE_STORAGE 與 PROPERTY_USAGE_EDITOR。

在 object.h 中還有許多可使用的 Hint 與使用旗標,請參考該檔案瞭解詳情。

屬性的運作也與 C# 屬性類似,且能在腳本中通過索引進行存取,但這種使用方法並不推薦,建議使用函式來提升可讀性。許多屬性也會與分類互相關連,如「animation/frame (動畫/幀)」,這時候除非使用 [] 運算元,否則將無法使用索引來存取。

_bind_methods() 中,只要有 set/get 函式就能建立屬性並進行繫結。如:

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"))

通知

All objects in Godot have a _notification method that allows it to respond to engine level callbacks that may relate to it. More information can be found on the Godot 通知 page.

參照

Reference 是從 Object 繼承來的,內涵了參照計數。該類別為參照計數物件型別的基礎類別。要定義 Reference,必須要使用 Ref<> 樣板。如:

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

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

myref 有進行參照計數。該變數會在沒有任何 Ref<> 樣板指向這個變數時釋放。

參考資料:

資源:

Resource 繼承自 Reference,因此所有資源都是參照計數的。資源可以選擇是否要包含路徑,用來參照到硬碟上的檔案。要包含路徑,可以通過 resource.set_path(path) 來設定,但設定路徑通常是由資源加載器來進行的。兩個不同的資源不能具有相同的路徑,如果設定了相同路徑會發生錯誤。

資源也可以不包含路徑。

參考資料:

載入資源

可以通過 ResourceLoader API 來載入資源,如:

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

如果之前已經有加載過該資源的參照,且該參照仍在記憶體中的話,則資源加載器會直接回傳該參照。者表示,參照到硬碟上同一個檔案的資源一次只能被加載一次。

  • resourceinteractiveloader (TODO)

參考資料:

保存資源

可以通過 ResourceSaver API 來保存資源:

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

Instance will be saved. Sub resources that have a path to a file will be saved as a reference to that resource. Sub resources without a path will be bundled with the saved resource and assigned sub-IDs, like res://someresource.res::1. This also helps to cache them when loaded.

參考資料: