Godot入門到棄坑:GDScript精要 拾遺


3樓貓 發佈時間:2024-02-15 17:32:41 作者:cameLcAsE Language

這一篇文章是GDScript精要部分的最後一篇。這裡主要講一些前面沒有講到的、不方便歸類的內容。但是其中一些特性是很常用、很方便的,因此依然建議閱讀。

枚舉

試想一下,你要用一個變量來表示一週的每一天(星期幾)。
自然而然地可以想到用整數來表示。但是合法的星期幾只能是1-7,而一個int類型的變量取值範圍要大得多。我們無法限制取值範圍。
這裡可以通過定義枚舉(enum)類型來提供更明確的約束。枚舉類型定義了一組合法的值,一個枚舉類型的變量應當取這些值中的一個(GDScript的枚舉目前無法限制枚舉類型的變量只取枚舉中定義的值)。有時候枚舉類型也被視作”有名字的常量“(named constants)。
在GDScript中,使用enum關鍵字定義一個枚舉類型。enum後是枚舉類型的名稱,然後是一對花括號。括號中是枚舉類型可用取值的列表,以逗號隔開:
定義枚舉類型後,可以像這樣使用枚舉類型:
枚舉可以作為類型在類型標註處出現,枚舉的某個取值也用點語法獲得。
你此時可能會說,誒,那我需要獲得它對應的數字怎麼辦呢,你這沒用數字表示啊。實際上枚舉定義時已經暗含了這一點。前面已經提到枚舉相當於一系列常量,定義枚舉時如果不給他們指定值,默認就會從整數1開始按出現順序給它們設置值。你可以試著print一下Monday和Sunday看它們是不是分別為1和7。
實際上GDScript中的枚舉類型很難說是真正的枚舉類型。在Godot 3.x中它甚至不能作為類型使用。定義枚舉類型時enum後面的名字可以省略。此時它等價於一系列常量定義:
這樣寫有一個問題就是可能會造成命名衝突,因為我們直接就能訪問到這些常量。而有名字的枚舉需要通過枚舉類型的名稱來訪問具體的選項。
不過不管那種方式定義枚舉,有一個好處就是如果你要定義一系列連續編號常量,那麼只需要指定枚舉中某一個項目的取值,後續的取值都會自動加一:
如果像這樣定義,星期三後面的取值會自動編號為4、5、6、7。但是要注意的是,星期三前面不會自動減一。此時Monday為0,Tuesday為1。當然你也可以分別為各個選項指定值而不按順序。
GDScript的枚舉類型的值只能是整數。
有名字的枚舉類型,實際上就是一個字典。這就意味著字典的所有方法對於一個枚舉類型來說也是可用的:
順帶一提,C#中星期幾就是用一個枚舉類型來表示的。不過由於習慣的不同,星期天對應的是0。

特別的字典語法

除了像之前那樣提到的用花括號和冒號來寫字典字面量,還有另一種語法來初始化字典:
這種寫法是Lua式的——Godot文檔中也是這樣稱呼這種寫法的。Lua是另一門編程語言,它的設計很有特點,並且在歷史的不同階段也是遊戲開發的熱門語言之一。Lua提供了——且只提供了一種內置數據結構那就是表(table),它實際上就是類似於字典的一種數據結構。這種Lua式的寫法也用花括號,但是各鍵值對的鍵和值用等號連接。此外這種寫法中鍵必須是合法的變量名,也就是說不能以數字開頭。
另一方面,字典可以直接用點語法來訪問鍵和值:
當然這種語法糖並不侷限於使用Lua式寫法的字典。只要字典中有這樣的字符串鍵,且這個字符串可以作為合法的變量名,那它就可以用這種語法訪問。並且不要求字典中所有的鍵都滿足這一約束。

格式化字符串

前面提到可以用加號和字符串提供的各種轉換函數來根據某變量的值來構造字符串。實際上還有更方便的方法來往字符串中填入某些變量的值。
例如按照之前的方法,在需要根據名字來輸出問候語時,我們需要這樣:
這種寫法單純拼接兩個字符串還好,如果要拼的字符串太多就很麻煩,尤其是變量出現在一串字符中間的時候,要多寫幾個加號。
利用GDScript中提供的格式化字符串來寫,就是這樣:
模板字符串中的%s相當於佔位符。它可以被填入各種各樣的值。在格式化字符串後面用%運算符加上具體的值來替換佔位符後就可以得到具體的字符串。也可以寫上多個佔位符,然後在百分號後用數組來填充佔位符:
格式化字符串中的佔位符支持多種格式。%s裡面的s代表simple,它會直接把傳入的表達式轉換為字符串。不過GDScript格式化字符串的其他格式主要是改變數字和向量的顯示格式,因此並不算常用。有興趣可以看文檔
當然還有一個問題就是,如果我就是要在字符串裡包含百分號怎麼辦?比如我要輸出一個百分數。這個時候需要給百分號轉義(escape),用兩個連續的百分號來表示百分號本身:
除了格式化字符串以外,還有一種格式化字符串的方法,那就是String.format實例方法。字符串模板中的佔位符需要用花括號圍起來,然後把對應的字典或者數組作為參數傳遞給format方法:
format的第二個參數(可選)用於指定佔位符,默認是花括號。如果你的字符串中包含花括號就可以指定其他符號作為佔位符。
注意format方法會返回一個新的字符串而不會就地修改字符串本身。

實現轉換為字符串的方法

在print和格式化字符串中,Godot會利用某個方法來將對象轉換為字符串。內置類型實際上都能夠很好地轉換為字符串。但是對於我們自定義的類型,如果不編寫額外的代碼print出來就是一些讓人難懂的東西。比如之前的一個Item:
輸出的是:
讓人摸不著頭腦。實際上這是它的基類和它的對象id——但是這對於一般的開發者和開發工作來說沒啥用。
重寫_to_string方法之後,它就可以按照我們指定的方法來將它轉換為字符串。在Item的定義中定義如下函數:
實際上寫這個函數的時候編輯器會自動補全,它是知道你可能在重寫基類方法的。這樣之後再print就能看到我們需要的信息了:

print的更多用法

前面的示例代碼中只講到直接用print輸出單個值(表達式)。實際上print可以接受任意多個參數:
不過這種寫法不是特別有用,因為它會直接把幾個參數連在一起,上面這行代碼輸出的是11.5?。
另外目前GDScript不支持自己編寫支持可變數量參數(vararg)的函數。但是C#支持。
prints和printt和print類似,也接受任意多個參數,但是它們會在輸出時給個參數之間分別加上空格(space)和製表符(tab)。
print輸出完畢後自帶換行,如果你不需要換行,就可以用printraw函數來輸出。
在字符串中還有一些特殊的以反斜槓加字母表示的轉義字符。這些特殊字符是不可見的,所以要用轉義字符來表示。最常用的\n表示換行:
輸出:
完整的轉義字符列表參見文檔
push_error和printerr類似,都會輸出紅色的錯誤信息。但是隻有push_error會在調試面板的錯誤頁面中加入一個錯誤。並且這兩者都不會中斷遊戲運行。類似的push_warning則是輸出黃色的警告信息。
此外print_rich支持BBCode格式的富文本。

斷言

前面的文章中我們在需要測試某個東西時往往都是直接用print輸出來檢查。在編程過程中可以用斷言(assert)來驗證某個布爾表達式是否為真,並在表達式為假時直接報錯終端程序運行。
GDScript中用assert函數來進行斷言。它可以接受兩個參數,第一個參數是條件;第二個參數是可選的,它是斷言失敗時輸出的錯誤信息。
這行代碼在遊戲運行到這裡時會直接報錯中斷。
斷言也是一種調試(debug)常用技巧。在後續文章中我會適時講解更多有關調試的內容。

代碼規範

在編寫代碼中我們必須要給很多不同的東西命名,因此有必要講一下命名規範及其重要性。
在開發過程中,我們很可能需要和他人合作。符合規範的代碼能夠讓大家更容易理解,能夠讓協作更順暢。首要的一條是要儘可能使用有意義的名字來命名各種元素。如今很多開發工具都支持自動補全且能按首字母進行提示,因此應當儘量避免縮寫,用完整的單詞來命名。
當然,包括GDScript在內的很多編程語言都支持用英文字母以外的字符來作為代碼符號的名稱,因此我們也不一定要用英文命名。但是無論如何,你的代碼在團隊中應當有一以貫之的規範。順帶一提中文命名有一個小問題就是,很多開發工具支持英文首字母自動補全,但是不支持漢字的拼音首字母補全,所以頻繁切換輸入法有點麻煩。有一個通用的小竅門是在漢字名稱前面加上拼音首字母縮寫,這樣就可以讓編輯器識別到。當然這樣一來就不好看了,因此究竟採用何種規範還需要開發者自行定奪。
由於GDScript和Python有很高的相似性,因此官方的規範也有很多地方和Python重合。
命名方面變量名、方法名採用snake_case。意味著所有字母都小寫,多個單詞用下劃線連接。比如GDScript內置的二維向量類中的:
常量全部字母大寫,單詞用下劃線連接(CONSTANT_CASE):
類型名首字母大寫,多個單詞不用符號連接(PascalCase):
對於枚舉類型來說,枚舉的類型名應該是PascalCase,但是枚舉的可選值應當是CONSTANT_CASE。
此外文檔在組織文件中的內容時,前後的空格、換行等格式上也有一些建議,完整的官方代碼風格指南參見文檔
當然,這些規範都是“君子協定”,它們並不屬於GDScript語法要求的一部分(除了用來界定代碼塊的縮進),不遵循這些規範也不會報錯。你也可以有自己團隊認可的不同的規範。

函數也是對象

沒錯,函數也是對象。這就意味著函數也可以放在變量裡、也可以作為函數參數傳遞:
函數的類型在GDScript中叫Callable(可調用對象)。不過GDScript中沒法顯式表明函數的簽名,也沒法在引用函數的變量上直接用常規語法調用引用的函數。這裡需要用Callable的call方法來調用:
在call中傳入引用的函數所需要的參數即可。
如果一定要說為什麼要這麼做,原因在於它能夠分離函數調用者和函數實現者。比如送奶工給訂戶送牛奶。送牛奶的才知道什麼時候送,送怎麼樣的牛奶,而需要牛奶的訂戶只關心消費牛奶而不知道什麼時候送來。所以”送牛奶“這個函數要交給送奶的調用,而牛奶送到之後具體怎麼做,是訂戶自己的事情。
我們會在後續的文章中見到這個特性的具體用例。

花式註釋

註釋除了單純的筆記其實還有更多用處。
在註釋開頭寫上特定的單詞可以讓註釋以不同的顏色顯示,讓它更醒目:
這些單詞的完整列表參見這裡
有時候代碼寫得太多,而有的代碼暫時又不需要管,很礙眼。可以用註釋來定義一個區域,讓編輯器可以把這部分代碼收起來。
註釋的井號後面跟上region(不能有空格)表示可收起區域的開頭。region後面可以跟上任意內容,方便在收起之後依然能夠明白這一段寫的啥。在要結束區域的地方寫上井號和endregion,這就算定義了一個可收起區域了。此時開頭的地方左邊會出現一個箭頭表示可以收起。

限定類型的數組

前面提到GDScript的容器類型可以容納不同類型的元素。但是如果你確實只需要在數組中保存某一種類型的元素,也可以給數組加上類型標註來提高安全性。要指定數組元素的類型,需要像這樣寫:
這樣一來,GDScript就會對這個數組進行靜態分析,在遊戲運行前就會檢測出任何違反類型約束的操作。也就是說你沒法往裡面加入其他類型的元素,也不能讓這個變量引用其他類型的數組。
這種操作看起來有點像模板或者泛型。但是目前GDScript並沒有這類特性。

match語句

如果你有其他編程語言的使用經驗(特別是語法和C/C++接近的),可能會想GDScript怎麼沒有switch語句呢(當然Python也沒有)。傳統的switch語句主要是為了在某個表達式取不同的值時執行不同的代碼。一方面,多個if語句確實可以替代switch,另一方面傳統switch語句中的case只支持常量(字面量),這就限制了它的能力。比如我們沒法用switch語句去測試一個對象中的各字段是否滿足某個條件。
在相對現代的編程語言(以及一些語言的較新版本中)都衍生出了所謂”模式匹配“(pattern matching)的語法,它的作用之一就是解決switch的侷限性。模式匹配可以檢查表達式的值是否滿足某種模式,而模式可以是多種多樣的,可以是類型,可以是某個常量,也可以是對某類對象的各屬性取值的要求。
match語句以match關鍵字開頭,後面是一個表達式,然後是一個代碼塊。代碼塊中又有若干代碼塊,它們分別代表一種模式以及該模式能夠匹配時執行的代碼。match有多種模式可以出現在分支中:
字面量模式很簡單,直接檢查是否相等。字典和數組模式除了常規的檢查各個元素,還支持一個通配符。這個通配符的意思就是隻要匹配了明確給出的元素就行,其他的就不管了。
綁定模式(var n)實際上就是讓一個變量綁定到整個模式或者模式中的某一個部分,這樣在處理這個模式的代碼中就可以直接用它而不用再通過match後面的表達式來訪問。
還有一個終極的通配符,就是一個下劃線。它是作為兜底的模式,正常情況下只應該作為最後一個模式出現在match中。它能夠匹配任何的值,相當於switch中的default。C#的模式匹配語法中也有這個東西,給它的名字叫棄元(discard)。但要注意在數組模式中它和兩點的區別。下劃線只能任意匹配一個,而兩點可以任意匹配多個元素。
when分句很好用。它可以在模式匹配之後進行額外的約束。when後面是一個布爾類型的表達式,when前面的模式匹配上之後,when後面的表達式必須為true才能視作整個模式匹配成功。例如我們可以先用綁定模式,然後再讓確認它的某個屬性是否為真。
match的語法看起來有點複雜,但是合理運用可以減少很多亂七八糟的if-else。在文檔中可以看到更詳細的例子。

撒花

好了GDScript精要部分就算結束了!下一篇文章開始就要運用前面所學的知識開始做些更像做遊戲做的事了!


© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com