資料偏好
GDScript 是為 Godot 打造的高階、物件導向、命令式,並支援 漸進式型別 的程式語言。它使用和 Python 類似的縮排語法。設計目的是最佳化 Godot 引擎的整合度,讓內容創作與整合更加彈性。
GDScript 是完全獨立於 Python 存在的,沒有繼承或擴充關係。
歷史記錄
備註
關於 GDScript 歷史的文件移到了 常見問題 。
GDScript 範例
有些人瞭解語法後可以學得更好,所以以下是簡單的 GDScript 例子。
# Everything after "#" is a comment.
# A file is a class!
# (optional) icon to show in the editor dialogs:
@icon("res://path/to/optional/icon.svg")
# (optional) class definition:
class_name MyClass
# Inheritance:
extends BaseClass
# Member variables.
var a = 5
var s = "Hello"
var arr = [1, 2, 3]
var dict = {"key": "value", 2: 3}
var other_dict = {key = "value", other_key = 2}
var typed_var: int
var inferred_type := "String"
# Constants.
const ANSWER = 42
const THE_NAME = "Charly"
# Enums.
enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
enum Named {THING_1, THING_2, ANOTHER_THING = -1}
# Built-in vector types.
var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)
# Functions.
func some_function(param1, param2, param3):
const local_const = 5
if param1 < local_const:
print(param1)
elif param2 > 5:
print(param2)
else:
print("Fail!")
for i in range(20):
print(i)
while param2 != 0:
param2 -= 1
match param3:
3:
print("param3 is 3!")
_:
print("param3 is not 3!")
var local_var = param1 + 3
return local_var
# Functions override functions with the same name on the base/super class.
# If you still want to call them, use "super":
func something(p1, p2):
super(p1, p2)
# It's also possible to call another function in the super class:
func other_something(p1, p2):
super.something(p1, p2)
# Inner class
class Something:
var a = 10
# Constructor
func _init():
print("Constructed!")
var lv = Something.new()
print(lv.a)
如果你曾經有使用如 C、C++、或 C# 等靜態型別語言的經驗卻沒用過動態型別的話,建議你閱讀這個教學: GDScript:動態語言入門 。
識別項
所有只包含英文字元( a 到 z 與 A 到 Z )、數字( 0 到 9 )、與 _ 的字串都算是一個識別項。另外,識別項不可以以數字開頭。識別項的大小寫有別( foo 與 FOO 是不同的)。
識別碼也可能包含 UAX#31 的大多數 Unicode 字元部分。這允許您使用以英語以外的語言編寫的識別符名稱。標識符中不允許使用被視為與 ASCII 字元和表情符號「混淆」的 Unicode 字元。
關鍵字
下面是 GDScript 所支援的關鍵字列表。這些單字是保留字(符記,Token),所以不能當作識別項來用。有些運算子(如 in 、 not 、 and 、或是 or )與下一個章節會提到的內建型別的名稱也是保留字。
如果你想瞭解一下的話,關鍵字定義在 GDScript Tokenizer 裡。
關鍵字 |
說明 |
|---|---|
if |
請參考 if/else/elif 。 |
elif |
請參考 if/else/elif 。 |
else |
請參考 if/else/elif 。 |
for |
請參考 for 。 |
while |
請參考 while 。 |
match |
請參考 match 。 |
when |
於 |
break |
跳出目前的 |
continue |
馬上跳至 |
階段 |
當語法上需要有敘述句但不需要執行任何東西的時候可以使用。如:空函式。 |
return |
從函式裡回傳數值。 |
類別 |
定義一個內部類別。詳見 內部類別 。 |
class_name |
將腳本定義為具有指定名稱的全域可存取類別。詳見 註冊命名類別 。 |
extends |
定義目前類別所要繼承的類別。 |
is |
測試一個變數是否為繼承自給定的類別,或判斷其是否為指定的內建型別。 |
in |
檢查某個值是否包含在字串、陣列、範圍、字典或節點中。搭配 |
as |
嘗試轉換為指定型別。 |
self |
參照目前的類別實體。詳見 self。 |
super |
呼叫父類別的方法。詳見 繼承。 |
signal |
定義一個訊號。詳見 訊號。 |
func |
定義一個函式。詳見 函式。 |
static |
定義一個靜態函式。不能用來定義靜態成員變數。 |
const |
定義一個常數。詳見 常數。 |
enum |
定義一個列舉型別(enum)。詳見 列舉型別。 |
var |
定義一個變數。詳見 變數。 |
breakpoint |
除錯器斷點的編輯器助手。與透過點擊裝訂線建立的斷點不同,「斷點」會儲存在腳本本身中。這使得在使用版本控制時它可以在不同的機器上保持不變。 |
preload |
預先載入一個類別或變數。請參閱 以類別作為資源 。 |
await |
等待訊號或協程完成。詳見 等待訊號或協程。 |
yield |
以前用於協程。保留為關鍵字,方便遷移。 |
assert |
判定一個條件,當判定失敗的時候記錄錯誤。在非除錯用建置中會忽略。請參考 Assert 關鍵字 。 |
void |
用於代表函式不返回任何值。 |
PI |
PI(圓周率)常數。 |
TAU |
TAU 常數。 |
INF |
無窮大常數。用於比較。 |
NAN |
NAN(Not a Number,不是數字)常數。用於比較。 |
運算子
以下是支援的運算子列表及其優先順序。所有二元運算子皆為 左結合 ,包括 ** 運算子。這代表 2 ** 2 ** 3 等同於 (2 ** 2) ** 3。如需明確指定優先順序,請使用括號,例如 2 ** (2 ** 3)。三元 if/else 運算子則為右結合。
運算子 |
說明 |
|---|---|
|
陣列索引(最高優先度) 括弧其實不是運算子,但是能夠讓你明確的指定運算的優先順序。 |
|
說明 |
|
屬性參照 |
|
呼叫函式 |
|
|
x is Nodex 不是 Node |
型別轉換 另見 is_instance_of() 函式。 |
|
力度 將 |
|
按位元 (Bitwise) NOT(非) |
+x-x |
縮 排 |
x * 2^expx / yx % y |
乘法/除法/餘數
注意: 這些運算子的行為與 C++ 一致,對於來自 Python、JavaScript 等語言的使用者可能存在意外的行為。詳情見表後。 |
x + yx - y |
加法/陣列的串聯 |
x << yx >> y |
位元移位 |
|
按位元 AND(與) |
|
按位元 XOR (互斥或) |
|
按位元 OR(或) |
x == yx != yx < yx > yx <= yx >= y |
比較 詳情見表後。 |
x in yx not in y |
遮擋模式
|
not x!x |
布林“非”,以及 不推薦使用 的別名 |
x and yx && y |
布林“與”,以及 不推薦使用 的別名 |
x or yx || y |
布林“或”,以及 不推薦使用 的別名 |
|
三元 if/else |
|
|
x = yx += yx -= yx *= yx /= yx **= yx %= yx &= yx |= yx ^= yx <<= yx >>= y |
賦值(最低優先度) 運算式中不能使用設定運算子。 |
備註
某些運算符的行為可能與您的預期不同:
如果
/運算子的兩個運算元都是 整數,就會執行整數除法而非浮點數除法。例如5 / 2 == 2,而非2.5。如果不希望這樣,請至少使用一個 浮點數 字面值 (x / 2.0)、轉型 (float(x) / y),或乘以1.0(x * 1.0 / y)。%運算子僅可用於整數,對於浮點數請使用 fmod() 函式。對於負值,
%運算子和fmod()使用 截斷 而非向負無限大捨去。這表示餘數會帶有正負號。如果您需要數學意義上的餘數,請改用 posmod() 和 fposmod() 函數。==和!=運算子有時允許您比較不同型別的值 (例如,1 == 1.0為真),但在其他情況下它可能會導致執行階段錯誤。如果您不確定運算元的型別,您可以安全地使用 is_same() 函式 (但請注意,它對型別和參照更嚴格)。若要比較浮點數,請改用 is_equal_approx() 和 is_zero_approx() 函式。
字面值
範例 |
說明 |
|
空值 |
|
布林值 |
|
10 進位整數 |
|
16 進位整數 |
|
2 進位整數 |
|
浮點數(實數) |
|
一般字串 |
|
三重引號一般字串 |
|
原始字串 |
|
三重引號原始字串 |
|
|
|
另外還有兩種寫法看起來像字面值,但其實不是:
範例 |
說明 |
|
|
|
|
整數與浮點數可以用 _ 分隔以提升可讀性。以下數字寫法皆有效:
12_345_678 # Equal to 12345678.
3.141_592_7 # Equal to 3.1415927.
0x8080_0000_ffff # Equal to 0x80800000ffff.
0b11_00_11_00 # Equal to 0b11001100.
一般字串常值 可以包含以下逸出序列:
逸出序列 |
會被解析為 |
|
換行 (LF) |
|
水平 TAB 字元 |
|
歸位字元 |
|
警告 (警示嗶聲/鈴聲) |
|
倒退鍵 |
|
Formfeed 分頁字元 |
|
縱向 TAB 字元 |
|
雙引號 |
|
單引號 |
|
反斜線 |
|
Unicode 字碼指標 |
|
Unicode 字碼指標 |
對於高於 0xFFFF 的 Unicode 字元,有兩種逸出方式:
as a UTF-16 surrogate pair
\uXXXX\uXXXX.as a single UTF-32 codepoint
\UXXXXXX.
此外,在字串中使用“”後跟換行符將允許您在下一行中繼續它,而無需在字串本身中插入換行符。
被一種引號包住的字串(例如 ")可以直接包含另一種引號(例如 ')而無須逸出。三重引號字串允許你避免逸出至多兩個連續同型的引號(除非這些引號位於字串開頭或結尾)。
原始字串 會如原始碼寫法完整編碼,不處理逸出序列,非常適合正則表達式使用。原始字串只會對 \\ 及 \"``(或 ``\')做逸出處理,也就是說,字串內可以有一個跟起始相同的引號,只要前面有加反斜線即可。
print("\tchar=\"\\t\"") # Prints ` char="\t"`.
print(r"\tchar=\"\\t\"") # Prints `\tchar=\"\\t\"`.
備註
有些字串無法用原始字串表示:例如字串結尾不能有奇數個反斜線,也不能在字串內直接出現未逸出的起始引號。不過實務上可以改用另一種引號或與一般字串串接即可解決。
GDScript 亦支援 GDScript 格式化字串 。
註釋
註解(annotation)是 GDScript 的特殊標記,用於修飾腳本或程式碼,可能會影響 Godot 引擎或編輯器對腳本的處理方式。
每個註解(Annotation)都以 @ 字元開頭,並以名稱指定。每個註解的詳細說明與範例,請參閱 GDScript 類別參考。
例如,你可以使用它將值匯出到編輯器:
@export_range(1, 100, 1, "or_greater")
var ranged_var: int = 50
有關匯出的說明文件已移至 GDScript 匯出屬性 。
任何與所需參數型別相容的常數運算式,都可以作為註解的參數值傳入:
const MAX_SPEED = 120.0
@export_range(0.0, 0.5 * MAX_SPEED)
var initial_speed: float = 0.25 * MAX_SPEED
可以在每行指定一個註釋,也可以在同一行中指定所有註釋。它們影響下一個不是註釋的敘述。註釋可以將參數傳送到括號之間並用逗號分隔。
兩者相同:
@annotation_a
@annotation_b
var variable
@annotation_a @annotation_b var variable
“@onready”註釋
使用節點時,我們通常會想以變數來參照到場景的某個部分。由於場景只有在進入有效場景樹後才能保證有正確配置,所以在 Node._ready() 呼叫後才能取得子節點。
var my_label
func _ready():
my_label = get_node("MyLabel")
這麼做會有些繁瑣,尤其當節點與外部參照愈來愈多時。為此,GDScript 提供 @onready 註解,會將成員變數的初始化延後到 _ready() 被呼叫時再進行。它可以用一行程式碼取代上述寫法:
@onready var my_label = get_node("MyLabel")
警告
將 @onready 與任何 @export 同時套用到同一個變數,結果可能不如預期。@onready 會在 @export 生效之後才設定預設值,進而覆蓋它:
@export var a = "init_value_a"
@onready @export var b = "init_value_b"
func _init():
prints(a, b) # init_value_a <null>
func _notification(what):
if what == NOTIFICATION_SCENE_INSTANTIATED:
prints(a, b) # exported_value_a exported_value_b
func _ready():
prints(a, b) # exported_value_a init_value_b
因此,會產生“ONREADY_WITH_EXPORT”警告,預設將其視為錯誤。我們不建議禁用或忽略它。
程式碼區段
程式碼區段是一種特殊註解,讓腳本編輯器可辨識為 可摺疊區塊。寫下這類註解後,可透過點擊註解左側的箭頭摺疊或展開區段。這個箭頭會顯示在紫色方框中,與一般程式碼摺疊做區別。
語法如下:
# Important: There must be *no* space between the `#` and `region` or `endregion`.
# Region without a description:
#region
...
#endregion
# Region with a description:
#region Some description that is displayed even when collapsed
...
#endregion
小訣竅
若要快速建立程式區塊,可在腳本編輯器中選取多行,右鍵點擊後選擇 建立程式區塊。區塊描述會自動選取以便編輯。
程式區塊可相互巢狀。
以下是一個程式碼區塊的具體使用範例:
# This comment is outside the code region. It will be visible when collapsed.
#region Terrain generation
# This comment is inside the code region. It won't be visible when collapsed.
func generate_lakes():
pass
func generate_hills():
pass
#endregion
#region Terrain population
func place_vegetation():
pass
func place_roads():
pass
#endregion
這功能可幫助你將大量程式碼整理成較易理解的區段。不過請注意,外部編輯器通常不支援這個功能,因此請確保你的程式碼在未使用這功能時也能易於閱讀。
備註
個別函式與縮排區塊(如 if、for)在腳本編輯器中本來就可以摺疊。因此不建議用程式碼區段包住單一函式或縮排區塊,這樣效果有限。程式碼區段最適合用來群組多個相關區塊。
程式碼樣式設定
GDScript 中的一行代碼可以使用反斜杠 (''') 在下一行繼續。在一行末尾添加一個,下一行上的程式碼將像反斜杠一樣。下面是一個示例:
var a = 1 + \
2
一行可以連續多次,如下圖所示:
var a = 1 + \
4 + \
10 + \
4
內建型別
內建型別為堆疊配置(stack-allocated),傳遞時以值傳遞。也就是每次賦值或作為參數傳給函式時都會建立一份副本。例外的是 Object、Array、Dictionary 與 Packed 陣列(如 PackedByteArray),這些是以參考傳遞,所以內容會共用。所有陣列、Dictionary 以及某些物件(如 Node、Resource)皆有 duplicate() 方法可用來複製。
基礎內建型別
GDScript 的變數可以被指派為多種內建型別。
null
null 是一個沒有包含任何資訊的空資料型別,不能指派為其他任何的值。
只有繼承自 Object 的型別才能有 null 值(Object 因此稱為「可為 null」型別)。Variant 型別 必須始終有有效值,因此不可為 null。
bool
「布林 (Boolean)」的縮寫,只會是 true 或 false 。
int
「整數 (Interger)」的縮寫。可以保存整數(正數與負數)。可以儲存 64 位元的值,相當於 C++ 的「int64_t」。
float
使用浮點數值,儲存包含小數的實數。保存為 64 位元的數值,相當於 C++ 中的「倍精確 (double)」型別。注意:目前,如 Vector2、Vector3、與 PoolRealArray 資料結構都儲存 32 位元的單精確「float(浮點)」值。
String
一串 Unicode 格式 的字元序列。
StringName
一個不可變的字串,僅允許每個名稱有一個實例。它們的建立速度較慢,並且可能會導致在多執行緒時等待鎖。作為交換,它們的比較速度非常快,這使得它們成為字典鍵的良好候選者。
NodePath
節點或節點屬性的預先解析路徑,可以輕鬆地與 String 互相轉換。常用於在場景樹中取得節點,或操作 Tween 等需要指定路徑的功能。
內建向量型別
Vector2
2D 向量包含 x 與 y 欄位。也能用與陣列一樣的方式存取。
Vector2i
與 Vector2 相同,但分量是整數。對於表示 2D 網格中的專案很有用。
Rect2
2D 矩形包含兩個向量欄位 position (位置)與 size (大小)。也包含了一個 end 欄位,為 position + size 。
Vector3
3D 向量包含 x 、 y 、與 z 欄位。也能用與陣列一樣的方式存取。
Vector3i
與 Vector3 相同,但分量是整數。可用於對 3D 網格中的專案進行索引。
Transform2D
用於 2D 幾何變換的 3×2 矩陣。
Plane
包含 normal 向量欄位與 d 常數距離的標準形式的 3D 平面型別。
Quaternion
四元數 (Quaternion) 是一種用於表示 3D 旋轉的資料型別。進行內插旋轉時很有用。
AABB
座標軸對齊定界框 (AABB, Axis-aligned Bounding Box),或稱為 3D 框 (3D Box),包含了兩個向量欄位: position (位置)與 size (大小)。也包含了一個 end 欄位,即為 position + size 。
Basis
用於 3D 旋轉與縮放的 3x3 矩陣。包含了三個向量欄位( x 、 y 、與 z ),一樣可以視為 3D 向量的陣列的來存取。
Transform3D
3D 變換包含了一個 Basis 欄位 basis 以及一個 Vector3 欄位 origin 。
引擎內建型別
Color
色彩資料型別包含 r 、 g 、 b 、與 a 欄位。也可以存取 h 、 s 、與 v ,代表色相 (Hue) /飽和度 (Saturation)/明度 (Value)。
RID
資源 ID (RID, Resource ID)。伺服器使用通用 RID 來參照不透明資料。
Object
所有非內建型別的基礎類別。
內建容器型別
Array
可包含任意物件型別,也包含其他陣列或字典型別(詳見下方)。陣列的大小可以動態調整。陣列的索引從 0 開始。若使用負數索引則自尾端開始。
var arr = []
arr = [1, 2, 3]
var b = arr[1] # This is 2.
var c = arr[arr.size() - 1] # This is 3.
var d = arr[-1] # Same as the previous line, but shorter.
arr[0] = "Hi!" # Replacing value 1 with "Hi!".
arr.append(4) # Array is now ["Hi!", 2, 3, 4].
切線陣列
Godot 4.0 新增了對型別陣列的支援。在寫入操作中,Godot 檢查元素值是否與指定型別配對,因此陣列不能包含無效值。 GDScript 靜態分析器考慮型別化陣列,但像「front()」和「back()」這樣的陣列方法仍然具有「Variant」回傳型別。
型別化陣列的語法為“Array[Type]”,其中“Type”可以是任何“Variant”型別、本機類別、使用者類別或列舉。不支援巢狀陣列型別(如“Array[Array[int]]”)。
var a: Array[int]
var b: Array[Node]
var c: Array[MyClass]
var d: Array[MyEnum]
var e: Array[Variant]
Array 和 Array[Variant] 是同一件事。
備註
陣列是透過引用傳遞的,因此陣列元素型別也是運作時變數引用的記憶體結構的屬性。變數的靜態型別限制了它可以引用的結構。因此,您**不能**為陣列指派不同的元素型別,即使該型別是所需型別的子型別。
如果你想要「轉換」具型別陣列,可以建立一個新陣列並使用 Array.assign() 方法:
var a: Array[Node2D] = [Node2D.new()]
# (OK) You can add the value to the array because `Node2D` extends `Node`.
var b: Array[Node] = [a[0]]
# (Error) You cannot assign an `Array[Node2D]` to an `Array[Node]` variable.
b = a
# (OK) But you can use the `assign()` method instead. Unlike the `=` operator,
# the `assign()` method copies the contents of the array, not the reference.
b.assign(a)
唯一的例外是“Array”(“Array[Variant]”)型別,以方便使用者並與舊程式碼相容。但是,對無型別陣列的操作被認為是不安全的。
本地坐標
Packed 陣列通常比同型別的型別化 Array(如 PackedInt64Array 對 Array[int])更快,且佔用更少記憶體。最差情況下,其速度也應與未型別化 Array 相當。相對地,非 Packed 陣列(不論是否型別化)則有更多方便的方法(如 Array.map),而 Packed 陣列沒有。詳細可查閱 class reference。型別化 Array 通常也比未型別化 Array 有更佳的操作效能。
所有 Array 若足夠大都可能造成記憶體碎裂。若你在意記憶體用量與效能(特別是遍歷與修改速度),且資料型別能對應到 Packed Array,建議優先使用 Packed Array。若沒有這些需求(例如你的陣列不超過數萬筆),可以直接用一般 Array 或型別化 Array,因為它們有更多方便的方法讓程式更好寫、更易維護(若常用這些操作也可能較快)。若資料型別已知(包含自訂類別),建議使用型別化 Array,通常遍歷與修改效能會優於未型別化 Array。
PackedByteArray :位元組的陣列(0 至 255 的整數)。
PackedInt32Array : 32 位元整數的陣列。
壓縮 64 位元整數陣列 : 64 位元整數的陣列。
PackedFloat32Array: 32 位元浮點數的陣列。
PackedFloat64Array : 64 位元浮點數的陣列。
PackedStringArray : 字串陣列。
PackedVector2Array : Vector2 的陣列。
PackedVector3Array : Vector3 的陣列。
PackedVector4Array : Vector4 值的陣列。
PackedColorArray : Color 值的陣列。
Dictionary
可以包含以獨立 Key 參照數值的關聯式容器。
var d = {4: 5, "A key": "A value", 28: [1, 2, 3]}
d["Hi!"] = 0
d = {
22: "value",
"some_key": 2,
"other_key": [2, 3, 4],
"more_key": "Hello"
}
也支援 Lua 風格的表格語法。Lua 風格用 = 取代 :,且字串鍵不需加引號(可少寫一點)。不過這種寫法的鍵不能以數字開頭(如同 GDScript 識別字),且必須是字串常值。
var d = {
test22 = "value",
some_key = 2,
other_key = [2, 3, 4],
more_key = "Hello"
}
要為現有的字典新增鍵,像存取既有鍵一樣存取並賦值即可:
var d = {} # Create an empty Dictionary.
d.waiting = 14 # Add String "waiting" as a key and assign the value 14 to it.
d[4] = "hello" # Add integer 4 as a key and assign the String "hello" as its value.
d["Godot"] = 3.01 # Add String "Godot" as a key and assign the value 3.01 to it.
var test = 4
# Prints "hello" by indexing the dictionary with a dynamic key.
# This is not the same as `d.test`. The bracket syntax equivalent to
# `d.test` is `d["test"]`.
print(d[test])
備註
方括號語法不僅可用於 Dictionary,也可用來存取任何 Object 的屬性。請記得當嘗試存取不存在的屬性時會導致腳本錯誤。要避免錯誤,請使用 Object.get() 與 Object.set() 替代。
具型別字典
Godot 4.4 新增對具型別字典的支援。在寫入操作時,Godot 會檢查元素的鍵與值是否符合指定型別,因此字典不會包含無效的鍵或值。GDScript 的靜態分析器也會考慮具型別字典。不過,回傳值的字典方法其回傳型別仍然是 Variant。
具型別字典的語法為 Dictionary[KeyType, ValueType],其中 KeyType 與 ValueType 可以是任何 Variant 型別、內建或使用者類別,或列舉。鍵與值的型別都**必須**指定,但你可以使用 Variant 讓其中之一不指定型別。目前不支援巢狀的具型別集合(例如 Dictionary[String, Dictionary[String, int]])。
var a: Dictionary[String, int]
var b: Dictionary[String, Node]
var c: Dictionary[Vector2i, MyClass]
var d: Dictionary[MyEnum, float]
# String keys, values can be any type.
var e: Dictionary[String, Variant]
# Keys can be any type, boolean values.
var f: Dictionary[Variant, bool]
Dictionary 與 Dictionary[Variant, Variant] 是同一回事。
Signal
訊號是物件可以向想要收聽它的人發出的訊息。訊號型別可用於傳遞發射器。
透過從實際物件獲取訊號可以更好地使用它們,例如“$Button.button_up”。
Callable
包含一個物件和一個函式,這對於將函式作為值傳遞非常有用(例如,連接到訊號時)。
取得一個方法作為成員會傳回一個可呼叫物件。 var x = $Sprite2D.rotate 會將``x`` 的值設為可呼叫的對象,其中``$Sprite2D`` 作為對象,rotate 作為方法。
您可以使用“call”方法來呼叫它:“x.call(PI)”。
變數
變數可以作為類別成員或函式的區域變數存在。變數使用 var 關鍵字建立,並且可選擇性地在初始化時賦值。
var a # Data type is 'null' by default.
var b = 5
var c = 3.8
var d = b + c # Variables are always initialized in direct order (see below).
變數也可選擇性地指定型別。當指定型別,變數將強制必須維持相同型別,當試著指派不相容的型別會造成錯誤。
在變數宣告時在變數名稱後使用 : (冒號)接上型別來執行型別。
var my_vector2: Vector2
var my_node: Node = Sprite2D.new()
若變數在宣告時就初始化,型別可以被推斷,因此可以省略型別名稱:
var my_vector2 := Vector2() # 'my_vector2' is of type 'Vector2'.
var my_node := Sprite2D.new() # 'my_node' is of type 'Sprite2D'.
型別推定僅可於指派的值有定義型別時發生,否則將發生錯誤。
有效的型別為:
內建型別(Array、Vector2、int、String…等)。
引擎類別(Node、Resource、RefCounted 等)。
常數名稱,若該常數包含腳本資源(若宣告
const MyScript = preload("res://my_script.gd")則可使用MyScript)。在相同腳本內的其他類別,尊重作用域(
InnerClass.NestedClass若在class InnerClass內宣告class NextedClass則為相同作用域)。使用
class_name關鍵字宣告的腳本類別。自動加載註冊為單例。
備註
雖然「Variant」是有效的型別規範,但它不是實際的型別。它僅意味著沒有設定型別,相當於根本沒有靜態型別。因此,預設情況下不允許對“Variant”進行推理,因為這可能是錯誤。
您可以透過在專案設定中變更它來關閉此檢查,或將其僅作為警告。有關詳細信息,請參閱 GDScript 警告系統 。
初始化順序
成員變數會依下列順序初始化:
根據變數的靜態型別,變數會是
null``(未型別化變數與物件)或型別預設值(如 ``int為0,bool為false等)。依腳本內變數宣告順序(由上到下)指派指定的值。
(僅適用於
Node衍生類別)若變數有@onready註解,其初始化會延後至步驟 5。
如有定義,會呼叫
_init()方法。場景與資源實體化時會指派匯出的屬性值。
(僅適用於
Node衍生類別)初始化@onready變數。(僅適用於
Node衍生類別)如有定義,會呼叫_ready()方法。
警告
你可以使用複雜運算式作為變數初始值,包含函式呼叫。請確保變數以正確順序初始化,否則值可能被覆蓋。例如:
var a: int = proxy("a", 1)
var b: int = proxy("b", 2)
var _data: Dictionary = {}
func proxy(key: String, value: int):
_data[key] = value
print(_data)
return value
func _init() -> void:
print(_data)
輸出:
{ "a": 1 }
{ "a": 1, "b": 2 }
{ }
若要修正,請將 _data 變數定義移到 a 上方,或移除空字典指定(= {})。
本地坐標
類別成員變數可以宣告為 static:
static var a
靜態變數屬於類,而不屬於實例。這意味著靜態變數在多個實例之間共享值,這與常規成員變數不同。
在類別內部,您可以從任何函式(靜態和非靜態)存取靜態變數。從類別外部,您可以使用類別或實例存取靜態變數(不建議使用第二種,因為它的可讀性較差)。
備註
“@export” 和 “@onready” 註解不能應用於靜態變數。局部變數不能是靜態的。
以下範例定義了一個具有名為“max_id”的靜態變數的“Person”類別。我們在“_init()”函式中增加“max_id”。這使得我們可以輕鬆追蹤遊戲中「Person」實例的數量。
# person.gd
class_name Person
static var max_id = 0
var id
var name
func _init(p_name):
max_id += 1
id = max_id
name = p_name
在這段程式碼中,我們建立了「Person」類別的兩個實例,並檢查該類別和每個實例是否具有相同的「max_id」值,因為該變數是靜態的並且每個實例都可以存取。
# test.gd
extends Node
func _ready():
var person1 = Person.new("John Doe")
var person2 = Person.new("Jane Doe")
print(person1.id) # 1
print(person2.id) # 2
print(Person.max_id) # 2
print(person1.max_id) # 2
print(person2.max_id) # 2
靜態變數可以有型別提示、setter 與 getter:
static var balance: int = 0
static var debt: int:
get:
return -balance
set(value):
balance = -value
基底類別的靜態變數也可以透過子類別存取:
class A:
static var x = 1
class B extends A:
pass
func _ready():
prints(A.x, B.x) # 1 1
A.x = 2
prints(A.x, B.x) # 2 2
B.x = 3
prints(A.x, B.x) # 3 3
備註
當你在工具腳本中參照某個靜態變數時,包含該靜態變數的另一個腳本也**必須**是工具腳本。詳情請參閱 Running code in the editor。
“@static_unload”註釋
由於 GDScript 類別是資源,腳本中的靜態變數會防止它被卸載,即使該類別沒有更多實例且沒有其他參考剩餘。如果靜態變數儲存大量資料或持有對其他專案資源 (例如場景) 的參考,這會很重要。您應該手動清理這些資料,或如果靜態變數沒有儲存重要資料且可以被重設,則使用 @static_unload 標記。
警告
目前,由於錯誤,即使使用“@static_unload”註釋,腳本也永遠不會被釋放。
請注意,@static_unload 會套用到整個腳本(包含內部類別),並且必須放在腳本最前面、位於 class_name 與 extends 之前:
@static_unload
class_name MyNode
extends Node
型別轉換
當指派值給有型別的變數時,該值必須有相容的型別。若有需要強制轉換值為特定型別,特別是物件型別時,可以使用型別轉換運算子 as 。
在物件型別間進行型別轉換時,若值為相同型別或該轉換型別的子型別時,取得的結果將為相同型別。
var my_node2D: Node2D
my_node2D = $Sprite2D as Node2D # Works since Sprite2D is a subtype of Node2D.
若該值非子型別,則轉換操作會導致 null 值。
var my_node2D: Node2D
my_node2D = $Button as Node2D # Results in 'null' since a Button is not a subtype of Node2D.
對於內建型別,若可能的話將強制轉換,否則引擎將產生錯誤。
var my_int: int
my_int = "123" as int # The string can be converted to int.
my_int = Vector2() as int # A Vector2 can't be converted to int, this will cause an error.
在與場景樹互動時,透過轉型也能讓變數更具型別安全性:
# Will infer the variable to be of type Sprite2D.
var my_sprite := $Character as Sprite2D
# Will fail if $AnimPlayer is not an AnimationPlayer, even if it has the method 'play()'.
($AnimPlayer as AnimationPlayer).play("walk")
常數
常數是一種無法在遊戲執行時更改的數值。常數的值必須在編譯時期就已定好。使用 const 關鍵字即可為常數設定名稱。若在常數定義後試圖為常數賦值會回傳錯誤。
我們建議,當有不會更改的值時,一概使用常數。
const A = 5
const B = Vector2(20, 20)
const C = 10 + 20 # Constant expression.
const D = Vector2(20, 30).x # Constant expression: 20.
const E = [1, 2, 3, 4][0] # Constant expression: 1.
const F = sin(20) # 'sin()' can be used in constant expressions.
const G = x + 20 # Invalid; this is not a constant expression!
const H = A + 20 # Constant expression: 25 (`A` is a constant).
雖然常數的型別會從指定的值推斷,但也可以加入明確的型別指定:
const A: int = 5
const B: Vector2 = Vector2()
當指派的值的型別不相容時將產生錯誤。
您也可以在函式內建立常數,這對於命名局部魔術值很有用。
Enum 列舉型別
Enum 基本上是常數的簡寫,當需要給一些常數指派連續的整數時滿有用的。
enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}
# Is the same as:
const TILE_BRICK = 0
const TILE_FLOOR = 1
const TILE_SPIKE = 2
const TILE_TELEPORT = 3
如果您為 enum 指定一個名稱,它會將所有鍵放入一個以該名稱命名的常數 字典 中。這表示字典的所有常數方法也可用於已命名的 enum。
重要
在 Godot 3.1 與之後的版本中,在有名稱的 Enum 中的鍵並不會註冊為全域常數。存取時必須要在前面加上 Enum 的名稱( Name.KEY )。請參考下方範例。
enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT}
# Is the same as:
const State = {STATE_IDLE = 0, STATE_JUMP = 5, STATE_SHOOT = 6}
# Access values with State.STATE_IDLE, etc.
func _ready():
# Access values with Name.KEY, prints '5'
print(State.STATE_JUMP)
# Use dictionary methods:
# prints '["STATE_IDLE", "STATE_JUMP", "STATE_SHOOT"]'
print(State.keys())
# prints '{ "STATE_IDLE": 0, "STATE_JUMP": 5, "STATE_SHOOT": 6 }'
print(State)
# prints '[0, 5, 6]'
print(State.values())
若沒有給 enum 的鍵賦值,則該鍵會自動設為前一個值加一,若是第一個則為 0。允許多個鍵對應同一個值。
函式
所有函式都屬於某個 class 。變數查找順序為:區域 → 類別成員 → 全域。self 變數總是可用的,可用來存取類別成員(詳見 self),但不是必需(也*不*應該像 Python 那樣當作第一個參數傳入)。
func my_function(a, b):
print(a)
print(b)
return a + b # Return is optional; without it 'null' is returned.
函式可以於任何時機 return 。預設的回傳值為 null 。
如果函式只包含一行程式碼,可以寫在同一行:
func square(a): return a * a
func hello_world(): print("Hello World")
func empty_function(): pass
函式也可以替參數與回傳值指定型別。參數型別可用與變數類似的方式來指定:
func my_function(a: int, b: String):
pass
若函式參數有預設值,就可以透過該值推斷型別:
func my_function(int_arg := 42, String_arg := "string"):
pass
函式的回傳型別可以在參數列表後使用箭頭符號(->)來指定:
func my_int_function() -> int:
return 0
有回傳型別的函式 必須 回傳適當的值。將型別設為 void 則表示函式不回傳任何東西。Void 函式可以使用 return 關鍵字來提早回傳,但無法回傳任何值。
func void_function() -> void:
return # Can't return a value.
備註
非 Void 函式必須 總是 回傳一個值。若程式碼中有分歧陳述式(如 if/else 結構),則所有可能的路徑都必須要有 return。如,若在 if 內有 return 但 if 後卻沒有,則編輯器會產生錯誤,因為若區塊程式碼未被執行,則該函式將不會有有效的回傳值。
函式參照
在 Callable 的語意下,函式屬於第一級公民。以名稱參照函式但不呼叫時,會自動產生對應的 Callable 物件,可用於將函式作為參數傳遞。
func map(arr: Array, function: Callable) -> Array:
var result = []
for item in arr:
result.push_back(function.call(item))
return result
func add1(value: int) -> int:
return value + 1;
func _ready() -> void:
var my_array = [1, 2, 3]
var plus_one = map(my_array, add1)
print(plus_one) # Prints `[2, 3, 4]`.
備註
Callable 物件**必須**使用 call() 方法來呼叫,不能直接用 () 運算子。這麼設計是為了避免直接呼叫函式造成的效能問題。
隨機數函式
Lambda 函式可以宣告不隸屬於任何類別的函式,而是直接建立一個 Callable 物件並指定給變數。這很適合建立可傳遞的 Callable,不會污染類別作用域。
var lambda = func (x):
print(x)
要呼叫建立好的 lambda,可以使用 call() 方法:
lambda.call(42) # Prints `42`.
Lambda 函式可以命名以利除錯(名稱會顯示在除錯器中):
var lambda = func my_lambda(x):
print(x)
你可以像一般函式一樣,替 lambda 函式指定型別提示:
var lambda := func (x: int) -> void:
print(x)
注意:若要從 lambda 函式回傳值,必須明確撰寫 return,不可省略:
var lambda = func (x): return x ** 2
print(lambda.call(2)) # Prints `4`.
Lambda 函式會捕捉區域環境:
var x = 42
var lambda = func ():
print(x) # Prints `42`.
lambda.call()
警告
區域變數在建立 lambda 當下會以值的方式捕捉一次。之後即使在外部函式改變了該變數,lambda 內部也不會更新:
var x = 42
var lambda = func (): print(x)
lambda.call() # Prints `42`.
x = "Hello"
lambda.call() # Prints `42`.
另外,lambda 不能重新賦值給外部的區域變數。離開 lambda 後,該變數仍不會改變,因為被捕捉的是一份遮蔽原變數的副本:
var x = 42
var lambda = func ():
print(x) # Prints `42`.
x = "Hello" # Produces the `CONFUSABLE_CAPTURE_REASSIGNMENT` warning.
print(x) # Prints `Hello`.
lambda.call()
print(x) # Prints `42`.
不過,如果捕捉的是以參照傳遞的資料型別(陣列、字典與物件),那麼在你重新賦值之前,內容的變更會被共享:
var a = []
var lambda = func ():
a.append(1)
print(a) # Prints `[1]`.
a = [2] # Produces the `CONFUSABLE_CAPTURE_REASSIGNMENT` warning.
print(a) # Prints `[2]`.
lambda.call()
print(a) # Prints `[1]`.
靜態函式
函式可以宣告為 static。當函式是 static 時,無法存取實例成員變數或 self,但可以存取靜態變數。static 函式也很適合用來製作工具函式的函式庫:
static func sum2(a, b):
return a + b
Lambda 函式無法宣告為 static。
可變參數函式
可變參數函式是指能接受可變數量參數的函式。自 Godot 4.5 起,GDScript 支援可變參數函式。要宣告可變參數函式,需使用「剩餘參數(rest parameter)」,它會把多出的所有參數收集成一個陣列。
func my_func(a, b = 0, ...args):
prints(a, b, args)
func _ready():
my_func(1) # 1 0 []
my_func(1, 2) # 1 2 []
my_func(1, 2, 3) # 1 2 [3]
my_func(1, 2, 3, 4) # 1 2 [3, 4]
my_func(1, 2, 3, 4, 5) # 1 2 [3, 4, 5]
函式至多只能有一個剩餘參數,且必須位於參數列表的最後。剩餘參數不能有預設值。靜態函式與 lambda 函式也可以是可變參數的。
靜態型別同樣適用於可變參數函式。不過,目前不支援使用具型別陣列作為剩餘參數的靜態型別:
# You cannot specify `...values: Array[int]`.
func sum(...values: Array) -> int:
var result := 0
for value in values:
assert(value is int)
result += value
return result
備註
雖然你可以使用剩餘參數來宣告可變參數函式,但 GDScript 目前不支援像某些語言(JavaScript、PHP)那樣在呼叫時使用「展開語法」來解包參數。不過,你可以使用 callv() 以陣列的形式傳入參數來呼叫函式:
func log_data(...values):
# ...
func other_func(...args):
#log_data(...args) # This won't work.
log_data.callv(args) # This will work.
抽象函式
請參閱 抽象類別與方法。
稱述句與流程控制
稱述句為標準,可以為複製、函式呼叫、流程結構…等(相見下方)。 作為稱述句分隔字元的 ; 是完全可選的。
Expression Node 運算式節點
表達式是依序排列的運算子及其運算元的序列。表達式本身也可以是敘述,但只有呼叫才可以合理地用作敘述,因為其他表達式沒有副作用。
表達式傳回可指派給有效目標的值。某些運算符的操作數可以是另一個表達式。賦值不是表達式,因此不傳回任何值。
以下是一些運算式範例:
2 + 2 # Binary operation.
-5 # Unary operation.
"okay" if x > 4 else "not okay" # Ternary operation.
x # Identifier representing variable or constant.
x.a # Attribute access.
x[4] # Subscript access.
x > 2 or x < 5 # Comparisons and logic operators.
x == y + 2 # Equality test.
do_something() # Function call.
[1, 2, 3] # Array definition.
{A = 1, B = 2} # Dictionary definition.
preload("res://icon.png") # Preload builtin function.
self # Reference to current instance.
標識符、屬性和下標是有效的賦值目標。其他表達式不能位於賦值的左側。
self
self 可用來參照當前實體,通常等同於直接存取目前腳本可用的符號。不過,self 也能用來存取動態定義的屬性、方法與其他名稱(例如預期存在於目前類別子類中的名稱,或是透過 _set() 和/或 _get() 提供的名稱)。
extends Node
func _ready():
# Compile time error, as `my_var` is not defined in the current class or its ancestors.
print(my_var)
# Checked at runtime, thus may work for dynamic properties or descendant classes.
print(self.my_var)
# Compile time error, as `my_func()` is not defined in the current class or its ancestors.
my_func()
# Checked at runtime, thus may work for descendant classes.
self.my_func()
警告
請注意,在基底類別中存取子類別成員通常被視為不良習慣,這會模糊程式碼的責任範圍,讓遊戲各部分間的關係變得難以理解。此外,也很容易忘記父類別對其子類有特定預期。
if/else/elif
簡單的條件判斷可以使用 if/else/elif 語法來建立。可以在條件周圍加上括號,但並非必要。由於基於 Tab 排版的性質,可以使用 elif 代替 else/if 來維持縮排的等級。
if (expression):
statement(s)
elif (expression):
statement(s)
else:
statement(s)
較短的敘述可以與條件寫在同一行:
if 1 + 1 == 2: return 2 + 2
else:
var x = 3 + 3
return x
有時你會想依據布林運算式來指派不同的初始值。此時三元條件運算式就派上用場了:
var x = (value) if (expression) else (value)
y += 3 if y < 10 else -1
三元條件運算式可以巢狀使用來處理多於兩種情況。當巢狀三元條件運算式時,建議將完整運算式分成多行以維持可讀性:
var count = 0
var fruit = (
"apple" if count == 2
else "pear" if count == 1
else "banana" if count == 0
else "orange"
)
print(fruit) # banana
# Alternative syntax with backslashes instead of parentheses (for multi-line expressions).
# Less lines required, but harder to refactor.
var fruit_alt = \
"apple" if count == 2 \
else "pear" if count == 1 \
else "banana" if count == 0 \
else "orange"
print(fruit_alt) # banana
你也可能需要檢查某個值是否包含於某集合中。可以透過 if 敘述搭配 in 運算子來完成:
# Check if a letter is in a string.
var text = "abc"
if 'b' in text: print("The string contains b")
# Check if a variable is contained within a node.
if "varName" in get_parent(): print("varName is defined in parent!")
while
可以使用 while 語法來建立簡單的迴圈。迴圈可以使用 break 來中斷或是使用 continue 來繼續:
while (expression):
statement(s)
for
要在如陣列或表格中的一段範圍內迭代,可以使用 for 迴圈。當在陣列中迭代時,目前的陣列元素會儲存於迴圈變數中。當於字典中迭代時,保存在迴圈變數內的則會是 索引 。
for x in [5, 7, 11]:
statement # Loop iterates 3 times with 'x' as 5, then 7 and finally 11.
var names = ["John", "Marta", "Samantha", "Jimmy"]
for name: String in names: # Typed loop variable.
print(name) # Prints name's content.
var dict = {"a": 0, "b": 1, "c": 2}
for i in dict:
print(dict[i]) # Prints 0, then 1, then 2.
for i in range(3):
statement # Similar to [0, 1, 2] but does not allocate an array.
for i in range(1, 3):
statement # Similar to [1, 2] but does not allocate an array.
for i in range(2, 8, 2):
statement # Similar to [2, 4, 6] but does not allocate an array.
for i in range(8, 2, -2):
statement # Similar to [8, 6, 4] but does not allocate an array.
for c in "Hello":
print(c) # Iterate through all characters in a String, print every letter on new line.
for i in 3:
statement # Similar to range(3).
for i in 2.2:
statement # Similar to range(ceil(2.2)).
如果要在迭代陣列時為其賦值,最好使用「for i in array.size()」。
for i in array.size():
array[i] = "Hello World"
迴圈變數是 for 迴圈的局部變量,為其賦值不會改變陣列上的值。透過引用傳遞的物件(例如節點)仍然可以透過呼叫循環變數上的方法來操作。
for string in string_array:
string = "Hello World" # This has no effect
for node in node_array:
node.add_to_group("Cool_Group") # This has an effect
match
match 陳述式用於在程式內分歧執行。與其他許多語言內的 switch 表達式相同,但有些額外的功能。
警告
“match” 比“==” 運算符的型別更嚴格。例如“1”將**不**配對“1.0”。唯一的例外是 String 與 StringName 配對:例如,字串 "hello" 被認為等於 StringName &"hello"。
基本語法
match <test value>:
<pattern(s)>:
<block>
<pattern(s)> when <pattern guard>:
<block>
<...>
給熟悉 switch 陳述式的人的速成說明
將
switch取代為match。移除
case。移除
case。將
default改為底線。
流程控制
搜尋模式會按照由上到下的順序來配對。若與搜尋模式相符,則會執行第一個對應的區塊。之後會繼續執行 match 陳述式下方的程式。若想往下執行,可以使用 continue 來停止執行目前的區塊,並往下搜尋其他符合的搜尋模式。
備註
3.x 中支援的「match」中特殊的「繼續」行為在 Godot 4.0 中被刪除。
可使用下列樣式類型:
- 常值樣式
比對一個 literal:
match x: 1: print("We are number one!") 2: print("Two are better than one!") "test": print("Oh snap! It's a string!")
- 運算式樣式
比對常數運算式、識別字或屬性存取(
A.B):match typeof(x): TYPE_FLOAT: print("float") TYPE_STRING: print("text") TYPE_ARRAY: print("array")
- 萬用字元
尋找所有東西。寫成一個底線。
其用法相當於其他語言中
switch敘述的default:match x: 1: print("It's one!") 2: print("It's one times two!") _: print("It's not 1 or 2. I don't care to be honest.")
- 繫結
繫結樣式會引入一個新變數。就像萬用字元樣式一樣,它能比對所有東西,並且替該值命名。這在陣列與字典樣式中特別實用:
match x: 1: print("It's one!") 2: print("It's one times two!") var new_var: print("It's not 1 or 2, it's ", new_var)
- 陣列
搜尋陣列。陣列中的每一個元素也都是搜尋模式,可以巢狀使用。
會先檢查陣列的長度,長度必須與搜尋模式相同,否則將不會視為相符。
開放結尾陣列 :通過將最後一個子搜尋模式設為
..可配對大於模式的陣列。所有子搜尋模式都以逗號分隔。
match x: []: print("Empty array") [1, 3, "test", null]: print("Very specific array") [var start, _, "test"]: print("First element is ", start, ", and the last is \"test\"") [42, ..]: print("Open ended array")
- 字典
與陣列模式相同。所有索引鍵都必須為常數搜尋模式。
會先檢查字典的大小,必須與搜尋模式相同,否則將不會視為相符。
開放結尾字典 :通過將最後一個子搜尋模式設為
..可允許配對大於搜尋模式的字典。所有子模式都必須以逗號分隔。
若不指定值,則只會檢查索引鍵是否存在。
值搜尋模式使用
:來與索引值搜尋模式區分。match x: {}: print("Empty dict") {"name": "Dennis"}: print("The name is Dennis") {"name": "Dennis", "age": var age}: print("Dennis is ", age, " years old.") {"name", "age"}: print("Has a name and an age, but it's not Dennis :(") {"key": "godotisawesome", ..}: print("I only checked for one entry and ignored the rest")
- 多重搜尋模式
也可以逗號區分來指定多個搜尋模式。這些搜尋模式中不允許有任何繫結。
match x: 1, 2, 3: print("It's 1 - 3") "Sword", "Splash potion", "Fist": print("Yep, you've taken damage")
模式守衛
模式守衛 是跟在模式列表之後的可選條件,讓你在選擇 match 分支前額外做判斷。與模式不同,模式守衛可以是任意運算式。
每個 match 最多只能執行一個分支。一旦選定分支,其餘分支便不再檢查。若你想對同一樣式制定多個分支,或避免選到過於寬鬆的一般性樣式,可以在樣式列表後使用 when 關鍵字加上樣式守衛:
match point:
[0, 0]:
print("Origin")
[_, 0]:
print("Point on X-axis")
[0, _]:
print("Point on Y-axis")
[var x, var y] when y == x:
print("Point on line y = x")
[var x, var y] when y == -x:
print("Point on line y = -x")
[var x, var y]:
print("Point (%s, %s)" % [x, y])
若目前分支的模式不符合,則不會檢查該分支的模式守衛,會直接檢查下一個分支的模式。
若模式符合,則會檢查該分支的模式守衛。
若為 true,則執行該分支內容,並結束
match。若為 false,則繼續檢查下一個分支的模式。
類別
預設情況下,所有腳本檔案都是未命名類別。此時只能透過檔案路徑(相對或絕對路徑)來參照它們。例如,若腳本檔案名為 character.gd:
# Inherit from 'character.gd'.
extends "res://path/to/character.gd"
# Load character.gd and create a new node instance from it.
var Character = load("res://path/to/character.gd")
var character_node = Character.new()
將腳本註冊為類別
你可以替類別命名,將其註冊為 Godot 編輯器中的新型別。做法是使用 class_name 關鍵字。你也可以選擇使用 @icon 註解並指定一張圖片路徑作為圖示。之後,該類別會在編輯器中以新圖示顯示:
# item.gd
@icon("res://interface/icons/item.png")
class_name Item
extends Node
小訣竅
作為自訂節點圖示的 SVG 圖檔,建議啟用 編輯器 > 隨編輯器縮放 以及 編輯器 > 按編輯器主題轉換圖示 這兩個 匯入選項。如此可讓圖示隨 Godot 編輯器縮放和主題設定自動調整(前提是你的圖示配色與 Godot 官方圖示一致)。
下列為類別檔案範例:
# Saved as a file named 'character.gd'.
class_name Character
var health = 5
func print_health():
print(health)
func print_this_script_three_times():
print(get_script())
print(ResourceLoader.load("res://character.gd"))
print(Character)
如果也要使用 extends,可以把兩者放在同一行:
class_name MyNode extends Node
具名類別會全域註冊,這表示在其他腳本中可直接使用,無需先 load 或 preload:
var player
func _ready():
player = Character.new()
備註
Godot 的類別語法很簡短,類別中只有成員變數與成員函式。函式可以為靜態函式,但變數則不可定義為靜態。同樣地,Godot 會在每次實體化時都初始化變數,包含陣列與字典。這樣一來能讓腳本在使用者不知情的情況下於不同的執行續中初始化,這正是執行緒安全的精神。
警告
Godot 編輯器會將名稱以 "Editor" 為開頭的自訂類別,在「建立新節點」或「建立新場景」等對話框中隱藏起來。這些類別仍可在執行時用類別名稱建立實例,但在編輯器介面上會自動隱藏,與 Godot 內建編輯器節點一同不顯示。
抽象類別與方法
自 Godot 4.5 起,你可以使用 @abstract 註解來定義抽象類別與抽象方法。
抽象類別無法被直接實例化,它的用途是讓其他類別繼承。嘗試實例化抽象類別會導致錯誤。
抽象方法是不包含實作的函式。因此在函式宣告後應以換行或分號結束。這相當於定義了一個契約,要求繼承的類別在覆寫時必須提供相容的方法簽章。
繼承的類別必須為所有抽象方法提供實作,否則就必須將該類別標記為抽象。若某個類別擁有至少一個抽象方法(無論是自身的,或是繼承而未實作的),則它也必須被標記為抽象。不過反過來並不成立:抽象類別可以完全沒有抽象方法。
小訣竅
如果你想將某方法設為可選擇性覆寫,應將其定義為非抽象方法,並提供預設實作。
例如,你可以有一個名為 Shape 的抽象類別,並在其中定義抽象方法 draw()。接著建立像 Circle、Square 等子類別,以各自的方式實作 draw()。這讓你能為所有形狀定義共同的「介面」,而不必在抽象類別中實作所有細節:
@abstract class Shape:
@abstract func draw()
# This is a concrete (non-abstract) subclass of Shape.
# You **must** implement all abstract methods in concrete classes.
class Circle extends Shape:
func draw():
print("Drawing a circle.")
class Square extends Shape:
func draw():
print("Drawing a square.")
內部類別與使用 class_name 建立的類別都可以是抽象的。以下範例建立了兩個抽象類別,其中一個是另一個抽象類別的子類別:
@abstract
class_name AbstractClass
extends Node
@abstract class AbstractSubClass:
func _ready():
pass
# This is an example of a concrete subclass of AbstractSubClass.
# This class can be instantiated using `AbstractClass.ConcreteSubclass.new()`
# in other scripts, even though it's part of an abstract `class_name` script.
class ConcreteClass extends AbstractSubClass:
func _ready():
print("Concrete class ready.")
警告
由於抽象類別無法實例化,因此不能將抽象類別掛在節點上。若你嘗試這麼做,引擎會在執行場景時輸出錯誤訊息:
Cannot set object script. Script '<path to script>' should not be abstract.
未命名類別也可以定義為抽象類別,此時 @abstract 註解必須寫在 extends 之前:
@abstract
extends Node
繼承
類別 (儲存為檔案) 可以繼承自:
全域類別。
另一個類別檔案。
在另一個類別檔案中的內部類別。
無法多重繼承。
繼承使用 extends 關鍵字:
# Inherit/extend a globally available class.
extends SomeClass
# Inherit/extend a named class file.
extends "somefile.gd"
# Inherit/extend an inner class in another file.
extends "somefile.gd".SomeInnerClass
備註
如果沒有明確定義繼承,則該類別將預設繼承 RefCounted 。
要檢查某個實例是否繼承自特定類別,可以使用 is 關鍵字:
# Cache the enemy class.
const Enemy = preload("enemy.gd")
# [...]
# Use 'is' to check inheritance.
if entity is Enemy:
entity.apply_damage()
要呼叫「超類別」(也就是目前類別所 extends 的那個類別)中的函式,請使用 super 關鍵字:
super(args)
這特別有用,因為延伸類別中的函式會取代超類別裡相同名稱的函式。若仍想呼叫超類別版本,可以使用 super:
func some_func(x):
super(x) # Calls the same function on the super class.
如果需要呼叫超類別中其他函式,你可以用屬性運算子指定函式名稱:
func overriding():
return 0 # This overrides the method in the base class.
func dont_override():
return super.overriding() # This calls the method as defined in the base class.
警告
常見的誤解之一是試圖覆寫*非虛擬*引擎方法,例如「get_class()」、「queue_free()」等。由於技術原因,不支援此方法。
在Godot 3中,您可以在GDScript中「隱藏」引擎方法,如果您在GDScript中呼叫此方法,它將起作用。但是,如果在某些事件中在引擎內部呼叫該方法,引擎將**不會**執行您的程式碼。
在 Godot 4 中,即使是陰影也可能不總是有效,因為 GDScript 優化了本機方法呼叫。因此,我們新增了“NATIVE_METHOD_OVERRIDE”警告,預設將其視為錯誤。我們強烈建議不要禁用或忽略該警告。
請注意,這不適用於諸如“_ready()”、“_process()”等虛擬方法(在檔案中以“virtual”限定符標記,並且名稱以下劃線開頭)。這些方法專門用於自訂引擎行為,並且可以在 GDScript 中覆寫。訊號和通知也可用於這些目的。
類別建置函式
在類別實例化時被呼叫的建構函式名為 _init。若要呼叫基底類別的建構函式,也可以使用 super 語法。請注意,每個類別都有一個一定會被呼叫的隱式建構函式(用來定義類別變數的預設值)。super 則是用來呼叫顯式建構函式:
func _init(arg):
super("some_default", arg) # Call the custom base constructor.
以下透過範例說明,請看這個情境:
# state.gd (inherited class).
var entity = null
var message = null
func _init(e=null):
entity = e
func enter(m):
message = m
# idle.gd (inheriting class).
extends "state.gd"
func _init(e=null, m=null):
super(e)
# Do something with 'e'.
message = m
還有幾件事需要注意:
如果被繼承的類別(
state.gd)定義了需要參數的_init建構函式(此例為e),那麼繼承的類別(idle.gd)也**必須**定義_init,並將適當的參數傳遞給state.gd的_init。Idle.gd的參數數量可與母類別State.gd不同。在上方的例子中,傳遞給
State.gd建置函式的e與傳遞給Idle.gd之e相同。即使
idle.gd的_init建構函式不接受參數,也仍需要傳遞某個值給state.gd的基底類別,即使該值不做任何事。這也表示不只能傳變數給基底建構函式,你也可以直接傳遞運算式,例如:# idle.gd- func _init():
super(5)
類別建置函式
靜態建構子是一個名為 _static_init 的靜態函式,會在類別載入、並完成靜態變數初始化後自動被呼叫:
static var my_static_var = 1
static func _static_init():
my_static_var = 2
靜態建構函式不能接受參數,也不能傳回任何值。
內類別
類別檔可以再包含內類別。內類別使用 class 關鍵字來定義。這些內部類別使用 類別名稱.new() 函式來實體化。
# Inside a class file.
# An inner class in this class file.
class SomeInnerClass:
var a = 5
func print_value_of_a():
print(a)
# This is the constructor of the class file's main class.
func _init():
var c = SomeInnerClass.new()
c.print_value_of_a()
以類別當作資源
以檔案形式儲存的類別會被視為 GDScripts。若要在其他類別中使用,必須先從磁碟載入,可透過 load 或 preload``(見下文)。而要將載入的類別資源實例化,則呼叫類別物件的 ``new 函式即可:
# Load the class resource when calling load().
var MyClass = load("myclass.gd")
# Preload the class only once at compile time.
const MyClass = preload("myclass.gd")
func _init():
var a = MyClass.new()
a.some_function()
匯出
備註
有關匯出的說明文件已移至 GDScript 匯出屬性 。
屬性 (Set/Get)
若能知道類別成員變數何時更改與為何更改可能很有用,我們也可能會希望以某種方法來封裝成員變數的存取。
為此,GDScript 提供了一個特殊的語法,可以在變數宣告後使用「set」和「get」關鍵字來定義屬性。然後,您可以定義一個程式碼區塊,該程式碼區塊將在存取或指派變數時執行。
範例:
var milliseconds: int = 0
var seconds: int:
get:
return milliseconds / 1000
set(value):
milliseconds = value * 1000
備註
與舊版 Godot 的 setget 不同,現在的 set/get 方法**永遠**會被呼叫(除非下述例外),即使在同一個類別內(不論有無 self. 前綴)也一樣。這讓行為更一致。如果你需要直接存取該值,請另外設一個變數存放,然後讓屬性方法去用那個變數。
替代語法
另外還有一種寫法,適合想要把屬性方法與變數宣告分開,或在多個屬性間重用同一段 setter/getter 程式碼(但無法分辨究竟是哪個屬性觸發)時使用:
var my_prop:
get = get_my_prop, set = set_my_prop
也可以在同一行完成:
var my_prop: get = get_my_prop, set = set_my_prop
setter 和 getter 必須用相同語法,不允許同一變數混用不同寫法。
備註
內嵌 寫法的 setter/getter 無法指定型別提示,這是刻意設計為減少重複樣板。如果變數有型別,setter 的參數會自動用同型別,getter 的回傳值也必須符合。若用獨立函式寫 setter/getter,可以加型別提示,但型別必須與變數相符或更寬泛。
何時不會呼叫 setter/getter
變數初始化時,初始值會直接寫入變數本身(即使有 @onready 註記也一樣)。
在 setter 或 getter 內直接用變數名稱來設定/取得值,會直接存取底層成員,因此不會產生無限遞迴,也不用另外宣告變數:
signal changed(new_value)
var warns_when_changed = "some value":
get:
return warns_when_changed
set(value):
changed.emit(value)
warns_when_changed = value
這也同樣適用於替代語法:
var my_prop: set = set_my_prop
func set_my_prop(value):
my_prop = value # No infinite recursion.
警告
但這個例外情況**不會**延伸到 setter/getter 內部呼叫的其他函式。例如以下程式碼**會**造成無限遞迴:
var my_prop:
set(value):
set_my_prop(value)
func set_my_prop(value):
my_prop = value # Infinite recursion, since `set_my_prop()` is not the setter.
工具模式
預設情況下,腳本不會在編輯器內執行,且僅能修改匯出的屬性。但有時會希望腳本能在編輯器內執行(前提是不要執行遊戲邏輯,或自行避免)。為此可以使用 @tool 註解,並將其放在檔案最上方:
@tool
extends Button
func _ready():
print("Hello")
請參考 在編輯器中執行程式碼 以瞭解詳情。
警告
在工具腳本中以 queue_free() 或 free() 釋放節點時請特別謹慎(特別是該節點為腳本擁有者時)。由於工具腳本會在編輯器中執行程式碼,若錯誤使用這些方法可能會使編輯器當掉。
記憶體管理
Godot 實作了參考計數,用來釋放某些不再使用的實例,而非垃圾回收器或要求純粹的手動管理。任何 RefCounted 類別 (或任何繼承它的類別,例如 Resource) 的實例,當不再使用時將會被自動釋放。對於任何不是 RefCounted 的類別的實例 (例如 Node 或基底 Object 型別),它將會保留在記憶體中,直到使用 free() (對於 Node 而言是 queue_free()) 刪除為止。
備註
如果透過 free() 或queue_free() 刪除 Node,它的所有子節點也會被遞歸刪除。
為了避免無法釋放的參考循環,提供了一個 WeakRef 函式來建立弱引用,它允許存取物件而不阻止 RefCounted 釋放。這是一個例子:
extends Node
var my_file_ref
func _ready():
var f = FileAccess.open("user://example_file.json", FileAccess.READ)
my_file_ref = weakref(f)
# the FileAccess class inherits RefCounted, so it will be freed when not in use
# the WeakRef will not prevent f from being freed when other_node is finished
other_node.use_file(f)
func _this_is_called_later():
var my_file = my_file_ref.get_ref()
if my_file:
my_file.close()
或者,不適用參照時,亦可使用 is_instance_valid(實體) 來判斷一個物件是否已被釋放。
訊號
訊號是用來從物件發送可讓其他物件做出反應的一項工具。若要為類別建立自定訊號,請使用 signal 關鍵字。
extends Node
# A signal named health_depleted.
signal health_depleted
備註
訊號是一種 回呼 機制。訊號也充當了 Observer 角色 (一種常見的程式設計模式)。更多資訊請參考 Game Programming Patterns 電子書中的 Observer tutorial (英語) 。
可通過與內建節點訊號 (如 Button 或 RigidBody3D ) 相同的方法來將自定訊號連接至方法。
在以下範例中,我們把 Character 節點的 health_depleted 訊號連接到 Game 節點。當 Character 節點送出該訊號時,會呼叫 Game 節點的 _on_character_health_depleted:
# game.gd
func _ready():
var character_node = get_node('Character')
character_node.health_depleted.connect(_on_character_health_depleted)
func _on_character_health_depleted():
get_tree().reload_current_scene()
也可以與訊號一起送出任意數量的參數。
下列範例說明了如何有效使用這個功能。假設螢幕上有一個血槽,可以動畫顯示生命值的改變,但同時我們也想在場景樹中將使用者界面與玩家分開來。
在 character.gd 腳本中,我們定義了 health_changed 訊號,並透過 Signal.emit() 送出。在場景樹較上層的 Game 節點,我們再使用 Signal.connect() 方法,將該訊號連接到 Lifebar:
# character.gd
...
signal health_changed
func take_damage(amount):
var old_health = health
health -= amount
# We emit the health_changed signal every time the
# character takes damage.
health_changed.emit(old_health, health)
...
# lifebar.gd
# Here, we define a function to use as a callback when the
# character's health_changed signal is emitted.
...
func _on_Character_health_changed(old_value, new_value):
if old_value > new_value:
progress_bar.modulate = Color.RED
else:
progress_bar.modulate = Color.GREEN
# Imagine that `animate` is a user-defined function that animates the
# bar filling up or emptying itself.
progress_bar.animate(old_value, new_value)
...
我們在 Game 中同時取得了 Character 與 Lifebar 節點,然後將送出訊號的 Characeter 連接到接收器,也就使本例中的 Lifebar 節點。
# game.gd
func _ready():
var character_node = get_node('Character')
var lifebar_node = get_node('UserInterface/Lifebar')
character_node.health_changed.connect(lifebar_node._on_Character_health_changed)
這樣一來便能讓 Lifebar 對生命值的更改做出反應而無需與 Character 節點耦合。
你可以在訊號定義後的括號中寫上可選參數名稱:
# Defining a signal that forwards two arguments.
signal health_changed(old_value, new_value)
這些參數會在編輯器的節點 Dock 中顯示,Godot 會使用這些參數來產生回呼函式。但,送出訊號時還是可以送出任意數量的參數,可自行決定是否要送出正確數量的引數。
你也可以使用 Callable.bind() 建立能接受額外參數的 Callable 物件複本。當訊號本身未提供你需要的所有資料時,這能讓你在連接中附加額外資訊。
當訊號送出時,除了訊號原本提供的參數外,回呼方法也會一併接收到先前繫結的值。
延續上面的範例,假設我們想在螢幕上顯示每個角色受到的傷害紀錄,例如 Player1 took 22 damage.。health_changed 訊號並不會提供受到傷害的角色名稱,因此在把訊號連到遊戲內主控台時,我們可以用 bind 方法加上角色名稱:
# game.gd
func _ready():
var character_node = get_node('Character')
var battle_log_node = get_node('UserInterface/BattleLog')
character_node.health_changed.connect(battle_log_node._on_Character_health_changed.bind(character_node.name))
BattleLog 節點會把每個繫結的元素當作額外參數接收:
# battle_log.gd
func _on_Character_health_changed(old_value, new_value, character_name):
if not new_value <= old_value:
return
var damage = old_value - new_value
label.text += character_name + " took " + str(damage) + " damage."
等待訊號或協程
await 關鍵字可以用來建立 共常式 ,會等待某個訊號發出之後再繼續執行。對訊號或者對同為協程的函式呼叫使用 await 關鍵字會立即將控制權返回給呼叫方。發出訊號時(或者呼叫的協程完成時),就會從停止的地方繼續執行。
例如,若要暫停執行直到使用者按下按鈕,可以這樣做:
func wait_confirmation():
print("Prompting user")
await $Button.button_up # Waits for the button_up signal from Button node.
print("User confirmed")
return true
在此情況下,wait_confirmation 會變成協程,呼叫端也需要對其進行 await:
func request_confirmation():
print("Will ask the user")
var confirmed = await wait_confirmation()
if confirmed:
print("User confirmed")
else:
print("User cancelled")
注意:若不使用 await 卻直接要求協程的回傳值,會觸發錯誤:
func wrong():
var confirmed = wait_confirmation() # Will give an error.
不過,如果你不需要結果,也可以直接非同步呼叫。這不會阻斷執行流程,也不會讓目前函式成為協程:
func okay():
wait_confirmation()
print("This will be printed immediately, before the user press the button.")
如果對一個既不是訊號、也不是協程的運算式使用 await,會立即回傳其值,而且函式不會把控制權交還給呼叫端:
func no_wait():
var x = await get_five()
print("This doesn't make this function a coroutine.")
func get_five():
return 5
這也表示,若在非協程的函式中回傳一個訊號,呼叫端就會對該訊號進行等待:
func get_signal():
return $Button.button_up
func wait_button():
await get_signal()
print("Button was pressed")
備註
與之前版本 Godot 中的 yield 不同,你無法獲取函式狀態物件。這是出於型別安全的考慮。要實作型別安全,函式就沒法說自己在返回 int 的同時還可能在運作時返回函式狀態物件。
Assert 關鍵字
assert 關鍵字可以用來在除錯建置中檢查條件。這些判斷提示會在非除錯建置中忽略。這表示作為參數傳遞的運算式在以發行模式匯出的專案中將不會被計算。因此,判斷提示 不可 包含有副作用的運算式。否則,腳本會因為專案是否於除錯建置中而有不同的行為。
# Check that 'i' is 0. If 'i' is not 0, an assertion error will occur.
assert(i == 0)
若從編輯器中執行專案,則專案會在判斷提示發生錯誤時暫停。
你也可以傳入自訂錯誤訊息,當斷言失敗時會顯示該訊息:
assert(enemy_power < 256, "Enemy is too powerful!")
注釋
從
#開始到行未的所有東西都會被忽略並當作註解。# This is a comment.小訣竅
在 Godot 腳本編輯器內,特定註解關鍵字會被高亮顯示,以提醒使用者注意特定註解:
重大 (紅色顯示):
ALERT、ATTENTION、CAUTION、CRITICAL、DANGER、SECURITY警告 (黃色顯示):
BUG、DEPRECATED、FIXME、HACK、TASK、TBD、TODO、WARNING通知 (綠色顯示):
INFO、NOTE、NOTICE、TEST、TESTING這些關鍵字區分大小寫,必須以全大寫才能被辨識:
這些高亮關鍵字及其顏色可以在「文字編輯器 > 主題 > 註解標記」裡自訂。
使用兩個井字號(
##)而非一個(#)可以新增 說明註解,這類註解會顯示於腳本說明檔與 Inspector 的說明欄位。說明註解必須直接放在可文件化項目(如成員變數)上方,或檔案最前面。也有專用的格式化功能,詳見 GDScript 文件註解。