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

參照

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)

這個範例會保存資源實體。擁有檔案路徑的子資源會被作為參照保存在該資源中。而沒有路徑的子資源則會被捆綁在保存的資源中,並分配一個 Sub-ID,如「res://someresource.res::1」。這樣一來有助於在載入時進行快取。

參考資料: