注: 這是在Godot4.0中總結(jié)出的內(nèi)容,并且語(yǔ)言是C#。
特別的,下面有的特性和C#關(guān)系比較大。
基本特性
在Godot中,為某個(gè)節(jié)點(diǎn)編寫特別的代碼時(shí),需要為節(jié)點(diǎn)新建腳本,或引用已有腳本。
引用腳本時(shí),填入腳本路徑即可,相當(dāng)于是復(fù)用代碼了。
新建腳本時(shí),一般做法是新建一個(gè)自定義類型,并且這個(gè)類型繼承自原有節(jié)點(diǎn)的類型。
其實(shí),你也可以不繼承自原有節(jié)點(diǎn)的那個(gè)類型。下面的各小節(jié)文章均是針對(duì)這個(gè)情況的。
在選擇繼承的目標(biāo)時(shí),你可以選擇的范圍有:(最常規(guī)的)繼承自原有類型、繼承自自定義類型、繼承自"中間"類型。
繼承自自定義類型
繼承自自定義類型,一般來(lái)說(shuō)是為了滿足這個(gè)需求的:
用戶為某節(jié)點(diǎn)編寫了一個(gè)自定義了類型后,在新的情境下,需要擴(kuò)充這個(gè)類型,讓它有新的功能,并且舊的不能變。
此時(shí),程序員一般第一個(gè)想到的就是繼承。Godot順理成章地允許用戶這樣進(jìn)行繼承。
想要進(jìn)行這樣的繼承時(shí),為節(jié)點(diǎn)新建腳本時(shí)必須先選擇已有類型,若直接填入想繼承的類型,Godot不允許這樣做。
創(chuàng)建完成后,用戶可以打開腳本文件,手動(dòng)修改繼承類型。
雖然看上去就像作弊,但是Godot方面認(rèn)可這種做法,也有人建議在建立腳本時(shí),"繼承"內(nèi)容框可以選擇自定義類型進(jìn)行繼承,但是Godot的開發(fā)者們暫時(shí)并沒有這么做。
需要注意的是,這種做法對(duì)此腳本附加到的節(jié)點(diǎn)有要求。
- 首先,你在編寫一系列一層一層繼承起來(lái)的類時(shí),你建立起來(lái)的最底層的那個(gè)自定義類肯定是繼承自"原生類型"的
- "原生類型"就是新建節(jié)點(diǎn)時(shí),你能在面板里選到的類型(沒錯(cuò),這里找不到你自定義的類型,它們100%是Godot內(nèi)置的!)
- 要將這一系列自定義類中的任何一個(gè)附加到一個(gè)目標(biāo)節(jié)點(diǎn)時(shí),這個(gè)目標(biāo)節(jié)點(diǎn)在編輯器中體現(xiàn)出來(lái)的類型必須能夠兼容自定義類最底層的"原生類型",和它相同,或是繼承自它,都可以。
(注:如果不兼容,運(yùn)行時(shí)會(huì)報(bào)錯(cuò)。)
繼承自"中間"類型
讀了上面的內(nèi)容時(shí),你也許會(huì)意識(shí)到,其實(shí)任何一個(gè)自定義類型底層的"原生類型",可以是它附加的節(jié)點(diǎn)的繼承樹中大于等于Node
的類型之中的任何一個(gè)類型。
我稱這種做法為"繼承自中間類型"。
事實(shí)上,也許需要調(diào)整一下觀念。大家一開始似乎會(huì)認(rèn)為,節(jié)點(diǎn)附加了一個(gè)自定義類型后,節(jié)點(diǎn)就是自定義類型的實(shí)例了。
實(shí)際情況似乎要稍微割裂一點(diǎn)。因?yàn)槔^承自中間類型后,你會(huì)發(fā)現(xiàn),這個(gè)自定義類型無(wú)法完全描述這個(gè)節(jié)點(diǎn)。請(qǐng)看接下來(lái)的例子。
這里,我給一個(gè)精靈Sprite2d
掛上這樣一個(gè)自定義類型,它繼承自Node。
public partial class TNode : Node
{
}
我想知道,這個(gè)節(jié)點(diǎn)從C#繼承體系的角度看,還是不是"精靈(Sprite2D
)"了
讓我們?cè)诟?jié)點(diǎn)中寫一點(diǎn)代碼,試圖了解該節(jié)點(diǎn)的類型。
public partial class AskForClass : Node2D
{
public override void _Ready()
{
var t1node = GetNode("TNode");
//方法1 打印繼承樹
Type tobj;
tobj = t1node.GetType();
while (tobj != null)
{
GD.Print(tobj.Name);
tobj = tobj.BaseType;
}
GD.Print("以上是繼承樹。");
//方法2 類型轉(zhuǎn)換。轉(zhuǎn)換失敗的話就是null。
var t2node = t1node as Sprite2D;
GD.Print(t2node);
//方法3 我發(fā)現(xiàn)Godot有一個(gè)IsClass()方法
GD.Print("is sprite2D class?" + t1node.IsClass("Sprite2D"));
}
}
(小提示:Godot的_Ready()
函數(shù)被執(zhí)行順序是先子節(jié)點(diǎn),再父節(jié)點(diǎn),這樣嵌套的,所以父節(jié)點(diǎn)訪問子節(jié)點(diǎn)總是萬(wàn)無(wú)一失的,當(dāng)然,這個(gè)示例就算順序不是這樣也不存在問題)
上面用了3種方式試圖確認(rèn)我們的t1node
有沒有Sprite2D
的成分,以及Sprite2D
的成分通過哪種方式能查到,猜猜看?
結(jié)論是:
TNode
Node
GodotObject
Object
以上是繼承樹。
null
is sprite2D class?True
除非使用Godot提供的函數(shù)IsClass()
, 光靠C#的繼承體系,查不到Sprite2D
的成分, 而且實(shí)例無(wú)法轉(zhuǎn)換成Sprite2D
類型,這樣就不能以C#通常的方式操作Sprite2D
特有的函數(shù)和變量了。
在C#內(nèi),雖然它"放棄了作為Sprite2D
的身份",但我們的節(jié)點(diǎn)在運(yùn)行時(shí)仍然做著一個(gè)"精靈"會(huì)做的事情,比如我在編輯器里對(duì)它的位置和旋轉(zhuǎn)進(jìn)行了變更,這些變更都沒有丟失。
個(gè)人推測(cè),此時(shí)需要使用GetIndexed()
和SetIndexed()
等方法來(lái)操作那些無(wú)法直接訪問的東西。
這個(gè)實(shí)例和實(shí)際存在于Godot運(yùn)行時(shí)的節(jié)點(diǎn)竟然有這樣的不同。
以后也許時(shí)不時(shí)需要想起來(lái)這樣一件事——C#實(shí)例和Godot內(nèi)的節(jié)點(diǎn)只是連在一起,有一個(gè)映射的關(guān)系罷了,并不100%是那個(gè)節(jié)點(diǎn)本身。
節(jié)點(diǎn)除了有C#能提供給它的函數(shù)和字段外,還可以擁有相當(dāng)突出的Godot賦予它的不同類型的功能,即各種類型的"原生節(jié)點(diǎn)"的各種各樣的功能。
閱讀文檔后,大概可以這樣理解,Godot運(yùn)行時(shí)維護(hù)的節(jié)點(diǎn)身上的函數(shù)和變量的表現(xiàn)更符合動(dòng)態(tài)語(yǔ)言的特征,而不是靜態(tài)類型語(yǔ)言。
不論是C#腳本、Godot腳本、還是"原生類型"的節(jié)點(diǎn),行為都是將自己的各種功能附加或覆蓋到了節(jié)點(diǎn)身上。
Godot is very dynamic. An object's script, and therefore its properties, methods and signals, can be changed at run-time. Because of this, there can be occasions where, for example, a property required by a method may not exist. To prevent run-time errors, see methods such as set, get, call, has_method, has_signal, etc. Note that these methods are much slower than direct references.
Godot很是動(dòng)態(tài)。對(duì)象的腳本及其屬性、方法和信號(hào)可以在運(yùn)行時(shí)更改。因此,在某些情況下,例如,方法所需的屬性可能不存在。若要防止運(yùn)行時(shí)錯(cuò)誤,請(qǐng)參閱設(shè)置、獲取、調(diào)用、has_method、has_signal等方法。請(qǐng)注意,這些方法比直接引用慢得多。
繼承自中間類型后可能遇到的坑
上述特性可能會(huì)引發(fā)一個(gè)問題,當(dāng)你需要用C#找到場(chǎng)景中的所有某一原生類型的節(jié)點(diǎn)時(shí),從"中間"繼承的節(jié)點(diǎn)被獲取后,由于一些身份被放棄了,有可能被漏掉!
也就是說(shuō),在上面的案例中,想找Sprite2D
時(shí),用下面的方法,掛載了TNode
腳本的精靈將被跳過,盡管它這么大一個(gè)放在屏幕上。
List<Sprite2D> lst = new List<Sprite2D>();
var children = FindChildren("*");
foreach (var chi in children)
{
if (chi is Sprite2D sp)
{
lst.Add(sp);
GD.Print("這個(gè)是精靈" + chi.Name);
}
else
{
GD.Print("這個(gè)不是精靈" + chi.Name);
}
}
我沒有測(cè)試GDScript,不知道是否情況會(huì)不同。也許它支持多重繼承?
綜上所述,個(gè)人建議盡量避免附加腳本時(shí)從中間繼承。
實(shí)在有這樣的需求,要么避免一個(gè)類型的每一個(gè)身份都需要被C#直接操作,要么用IsClass()
配合GetIndexed()
和SetIndexed()
等方法處理該對(duì)象。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-470024.html
參考:
https://godotengine.org/qa/141137/best-way-to-add-a-node-that-extends-a-custom-class
https://docs.godotengine.org/en/latest/classes/class_object.html文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-470024.html
到了這里,關(guān)于Godot的幾個(gè)附加腳本和進(jìn)行繼承時(shí)比較特別的特性的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!