Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

GDScript 参考

GDScript 是一种面向对象的高级指令式渐进类型编程语言,专为 Godot 构建,以与 Python 等语言类似的缩进结构为其基本语句结构。设计 GDScript 这门语言旨在与 Godot 引擎紧密集成,对 Godot 引擎进行优化,从而为程序内容的创建与继承提供灵活的手段。

GDScript 与 Python 无关,并不是基于 Python 开发的。

历史

备注

关于 GDScript 历史的文档已移至常见问题

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:动态语言入门

标识符

标识符仅限于含字母字符( azAZ )、 数字( 09 )和下划线 _ 的字符串,不能以数字开头,且大小写敏感(如 fooFOO 就是两个不同的标识符)。

标识符现在也允许包含 UAX#31 所提供的部分 Unicode 字符,即现在也可将非英文字符作为标识符使用,而 Unicode 字符中易与 ASCII 字符混淆的字符以及颜文字则无法作为标识符使用。

关键字

下表为该语言所支持的关键字列表。由于关键字是保留字(词法单元),因此不能用作标识符。运算符(如 innotandor)及后文中出现的内置类型名称亦为保留字。

若想深入了解关键字,可在 GDScript 词法分析器中找到对于关键字的定义。

关键字

描述

if

if/else/elif

elif

if/else/elif

else

if/else/elif

for

for

while

while

match

match

when

用于 match 语句中的模式防护

break

退出当前 forwhile 循环的执行。

continue

立即跳到 forwhile 循环的下一个迭代。

pass

语法上要求在不希望执行代码的语句中使用,例如在空函数中使用。

return

从函数当中返回一个值。

class

定义内部类。见内部类

class_name

将脚本定义为具有指定名称的全局可访问类。见注册具名类

extends

定义当前类的父类。

is

检测变量是否继承自给定的类,或检测该变量是否为给定的内置类型。

in

通常情况下用来检测字符串、列表、范围、字典、节点中是否存在某个值,而和 for 关键字连用时,则用于遍历字符串、列表、范围、字典、节点中的内容。

as

尝试将值转换为给定类型的值。

self

引用当前类实例。见 self

super

解析父类作用域内的方法。见继承

signal

定义信号。见信号

func

定义函数。见函数

static

将一个函数声明为静态函数,或将一个成员变量声明为静态成员变量。

const

定义常量。见常量

enum

定义枚举。见枚举

var

定义变量。见变量

breakpoint

用来设置脚本编辑器辅助调试断点的关键字。与在脚本编辑器每行最左侧点击红点所创建的断点不同,breakpoint 关键字可以储存在脚本内部。在不同设备上使用版本工具进行调试时,由 breakpoint 关键字创建的断点仍旧有效。

preload

预加载类或变量。见类作为资源

await

等待信号或协程完成。见等待信号和协程

yield

以前的版本中用于协程,现保留为关键字,以便旧版本迁移至新版本。

assert

断言条件,若断言失败则记录错误。非调试版本中会忽略断言语法。见 Assert 关键字

void

用于代表函数不返回任何值。

PI

PI(π)常数。

TAU

TAU(τ)常数。

INF

无穷常量,用于比较和计算结果。

NAN

NAN(非数)常量,用作计算后不可能得到的结果。

运算符

下列为 GDScript 所支持的运算符及其运算优先级。所有二进制运算符均为左结合运算符,其中就包括 **,即 2 ** 2 ** 3 等价于 (2 ** 2) ** 3。为避免运算歧义,请使用括号来处理该运算的优先级,如 2 ** (2 ** 3)

运算符

描述

( )

分组(优先级最高)

括号其实不是运算符,但是能够让你显式指定运算的优先级。

x[index]

下标

x.attribute

属性引用

foo()

函数调用

await x

等待信号或协程

x is Node
x is not Node

类型检查

另见 is_instance_of() 函数。

x ** y

幂(乘方)

x 与其自身相乘 y 次,类似于调用 pow() 函数。

~x

按位取反

+x
-x

取同 / 取负(相反数)

x * y
x / y
x % y

乘法/除法/余数

% 运算符也用于字符串的格式化

注意:这些运算符的运算机制与其在 C++ 中的运算机制一致,而对于使用 Python、JavaScript 等语言的用户则可能会存在在其意料之外的运算机制,详情见表后。

x + y
x - y

加法(或连接)/减法

x << y
x >> y

位移位

x & y

按位与

x ^ y

按位异或

x | y

按位或

x == y
x != y
x < y
x > y
x <= y
x >= y

比较

详情见表后。

x in y
x not in y

检查包含关系

in 也在 for 关键字的语法中使用。

not x
!x

布尔“非”及其不推荐使用的形式

x and y
x && y

布尔“与”及其不推荐使用的形式

x or y
x || y

布尔“或”及其不推荐使用的形式

真表达式 if 条件 else 假表达式

三元(目)运算符 if/else

x as Node

类型转换

x = y
x += y
x -= y
x *= y
x /= y
x **= y
x %= y
x &= y
x |= y
x ^= y
x <<= y
x >>= y

赋值(优先级最低)

表达式中不能使用赋值运算符。

备注

一些运算符的运算机制可能会与你所预期的运算机制有所不同:

  1. 若运算符 / 两端的数值均为 int,则进行整数除法而非浮点数除法。例如: 5 /2 == 2 中该算式的结果为 2 而非 2.5。若希望进行浮点数运算,请将该运算符两端的其中一个数值的类型改为 float ,如直接使用浮点数( x / 2.0 )、转换类型( float(x) / y )、乘以 1.0x * 1.0 / y )等。

  2. 运算符 % 仅适用于整型数值的取余运算,对于小数的取余运算,请使用 fmod() 方法。

  3. 对于负值,% 运算符和 fmod() 函数会使用 截断算法 进行运算,而非向负无穷大舍入,此时余数会带有符号(即余数可能为负)。如果你需要数学意义上的余数,请改用 posmod()fposmod() 函数。

  4. ==!= 运算符在有些情况下允许比较不同类型的值(例如 1 == 1.0 的结果为真),但在其他情况下可能会发生运行时错误。若你不能确定操作数的类型,可使用 is_same() 函数来进行安全比较(但请注意,该函数对类型和引用更加严格)。要比较浮点数,请改用 is_equal_approx()is_zero_approx() 函数。

字面量

示例

描述

null

空值

falsetrue

布尔值

45

十进制整数

0x8f51

十六进制整数

0b101010

二进制整数

3.1458.1e-10

浮点数(实数)

"Hello""Hi"

常规字符串

"""Hello"""'''Hi'''

常规字符串(用三对引号括住)

r"Hello"r'Hi'

原始字符串

r"""Hello"""r'''Hi'''

原始字符串(用三对引号括住)

&"name"

StringName

^"Node/Label"

NodePath

也有两种长得像字面量,但实际上不是字面量的量:

示例

描述

$NodePath

get_node("NodePath") 的简写

%UniqueNode

get_node("%UniqueNode") 的简写

Integers and floats can have their numbers separated with _ to make them more readable. The following ways to write numbers are all valid:

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.

常规字符串字面量内可包含以下转义序列:

转义序列

转义为

\n

换行符

\t

水平制表符

\r

回车符

\a

警报(蜂鸣/响铃)

\b

退格键

\f

换页符

\v

垂直制表符

\"

双引号

\'

单引号

\\

反斜杠

\uXXXX

Unicode UTF-16 码位 XXXX(16进制,不区分大小写)

\UXXXXXX

Unicode UTF-32 码位 XXXXXX(16进制,不区分大小写)

有两种方法可以表示 0xFFFF 以上的转义 Unicode 字符:

  • 使用 UTF-16 代理对 \uXXXX\uXXXX 表示。

  • 使用单个 UTF-32 码位 \UXXXXXX 表示。

此外,在字符串中使用 \ 后换行可以让斜杠后的文字自动换行,而无需在字符串中插入换行符。

使用某一种引号(如 ")构成的字符串,无需转义即可包含另一种引号(如 '),而三引号字符串在与其他字符串边缘不相邻的情况下,最多可避免连续两个同种引号的转义。

原始字符串字面量始终按照源代码中出现的方式对字符串进行编码,特别适用于正则表达式当中。原始字符串虽不处理转义序列,但可以识别 \\\"\' )等字符,并将其替换为其自身。一个字符串内可以含有一对相匹配的引号,但这些引号前面必须有一个反斜杠才可以让字符串包含它们。

print("\tchar=\"\\t\"")  # Prints `    char="\t"`.
print(r"\tchar=\"\\t\"") # Prints `\tchar=\"\\t\"`.

备注

而有些字符串却不能使用原始字符串字面量来表示:不能在字符串末尾有奇数个反斜杠,不能在字符串内部有未转义的开引号。但在实际应用中,这些问题并不重要,因为你可以通过使用不同类型的引号,或者与普通字符串字面量进行拼接,来解决这个问题。

GDScript 也支持格式字符串

注解

注解是 GDScript 中的一类特殊标记,用来修饰脚本或脚本中的代码,影响 Godot 引擎或编辑器对该脚本或代码所产生的效果。

注解均以 @ 符号开头,加以注解名称而构成。有关注解的详细说明及其使用范例见 GDScript 类参考

For instance, you can use it to export a value to the editor:

@export_range(1, 100, 1, "or_greater")
var ranged_var: int = 50

更多关于导出属性的信息见 GDScript 导出属性

Any constant expression compatible with the required argument type can be passed as an annotation argument value:

const MAX_SPEED = 120.0

@export_range(0.0, 0.5 * MAX_SPEED)
var initial_speed: float = 0.25 * MAX_SPEED

注解既可单行修饰,也可多行修饰,修饰离该注解最近的非注解语句。注解可携带参数,每个参数均在注解名后的括号内,彼此之间用逗号隔开。

Both of these are the same:

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

This can get a little cumbersome, especially when nodes and external references pile up. For this, GDScript has the @onready annotation, that defers initialization of a member variable until _ready() is called. It can replace the above code with a single line:

@onready var my_label = get_node("MyLabel")

警告

Applying @onready and any @export annotation to the same variable doesn't work as you might expect. The @onready annotation will cause the default value to be set after the @export takes effect and will override it:

@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 警告选项,默认将该操作作为编辑器错误进行处理。我们并不推荐关闭或忽略该警告选项。

注释

# 到行尾的内容都会被忽略,视为注释。

# This is a comment.

小技巧

Godot 的脚本编辑器会高亮显示注释中的一些特殊关键字,提醒用户注意某些注释:

  • 关键提示(标红)ALERTATTENTIONCAUTIONCRITICALDANGERSECURITY

  • 警告提示(标黄)BUGDEPRECATEDFIXMEHACKTASKTBDTODOWARNING

  • 一般提示(标绿)INFONOTENOTICETESTTESTING

这些关键字大小写敏感,需要全大写才能保证能被引擎识别:

# In the example below, "TODO" will appear in yellow by default.
# The `:` symbol after the keyword is not required, but it's often used.

# TODO: Add more items for the player to choose from.

可以在编辑器设置的 文本编辑器 > 主题 > 注释标记 部分中更改突出显示的关键字列表及其颜色。

把一个井号(#)换成两个(##)可以添加文档注释,文档注释会在脚本文档和变量的检查器描述中显示。文档注释必须放在可编写文档内容(例如成员变量)的正上方或放在文件的开头。还可以使用专门的格式化选项,详见 GDScript 文档注释

## This comment will appear in the script documentation.
var value

## This comment will appear in the inspector tooltip, and in the documentation.
@export var exported_value

代码区块

代码区块是一种特殊类型的注释,脚本编辑器将其理解为可折叠区块,即在编写代码区块注释后,可以通过点击注释左侧出现的箭头来折叠和展开该区块。该箭头用一个紫色方块包围起来,以区别于标准的代码折叠。

语法如下:

# 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

代码区块可将大块代码组织成更容易理解的部分。但请注意,外部编辑器通常不支持该特性。因此即便不依赖代码区块,也要确保你的代码易于理解。

备注

单独的函数与被缩进的部分(如 iffor始终可以在脚本编辑器中折叠,此时应避免使用代码区块来包含这些可始终折叠起来的部分,执意使用亦可,但也并不会带来太多好处。若要将多个元素分组在一起,使用代码区块效果最佳。

行间语句接续

在 GDScript 中,一行语句可通过反斜杠(\)接续到下一行。将反斜杠加在一行语句末尾可将该行代码与下一行代码相衔接。如:

var a = 1 + \
2

可按以下方式对单个语句行进行多行接续:

var a = 1 + \
4 + \
10 + \
4

内置类型

内置类型分配在栈上、按值传递,即每次赋值或将其作为参数传递给函数时均会复制其值。例外是对象 Object、数组 Array、字典 Dictionary 以及紧缩数组(如PackedByteArray),这些类型的值按引用传递,实例的值相互共享。数组、字典 Dictionary 以及部分对象(NodeResource)均有 duplicate() 方法,能够用来制作副本。

基本内置类型

GDScript 中的变量可赋以不同内置类型的值。

null

null 为空数据类型,既不包含任何信息,也不能赋值为其他任何值。

只有继承自 Object 的类型才能具有 null 值(因此 Object 被称为“可空”类型)。Variant 类型的值必须始终有效,因此不能具有 null 值。

bool

“boolean”(布尔)的缩写,只能包含 truefalse

int

英文“integer”(整数)的缩写,存储整数(正整数和负整数)。存储的是 64 位值,等效于 C++ 中的 int64_t

float

使用浮点值存储实数,包括小数。存储的是 64 位值,等效于 C++ 中的 double。注意:目前 Vector2Vector3PackedFloat32Array 等数据结构存储的是 32 位单精度 float 值。

String

Unicode 格式的字符序列。

StringName

不可变字符串,一个实例仅允许拥有一个名称。该类型的实例创建起来较慢,在多线程环境下可能会导致锁等待。不过,该类型的实例比较起来比字符串快,非常适合在字典中作为键名使用。

NodePath

节点或节点属性的预解析路径,可以轻松地赋值成字符串,亦或从字符串中转换为节点路径。节点路径可用于与节点树交互以获取节点,亦或通过诸如 Tween等方式来影响属性。

内置向量类型

Vector2

2D 向量类型,包含 xy 两个字段,也可像访问数组元素一样访问这两个字段。

Vector2i

同 Vector2,但其分量均为整型数值,非常适用于制作 2D 网格显示物品功能。

Rect2

2D 矩形类型,包含两个向量字段:positionsize。还包含一个 end 字段,即 position + size

Vector3

3D 向量类型,包含 xyz 三个字段,也可以像访问数组元素一样访问这些字段。

Vector3i

同 Vector3 ,但其分量均为整型数值,可用于为 3D 网格中的每个物品编制索引。

Transform2D

用于 2D 变换的 3×2 矩阵。

Plane

3D 平面类型的标准形式,包含一个向量字段 normal 以及一个 标量距离 d

Quaternion

四元数是一种用于表示 3D 旋转的数据类型,对于内插旋转十分有用。

AABB

轴对齐边界框(或 3D 框),包含 2 个向量字段:positionsize。还包含一个 end 字段,即 position + size

Basis

用于 3D 旋转和缩放的 3×3 矩阵,包含 3 个向量字段(xyz),可以以 3D 向量数组的形式访问。

Transform3D

3D 线性变换,包含一个 Basis(基)字段 basis 和一个 Vector3 字段 origin

引擎内置类型

Color

颜色数据类型包含 rgba 四个字段,也可以用 hsv 这三个字段来分别访问色相、饱和度、明度。

RID

资源 ID(RID)。服务器使用通用的 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 会检查每个元素是否与该数组所指定的类型相匹配,因此类型化数组不能含有无效数据。而诸如 front()back() 等方法,虽然 GDScript 静态分析器会将类型化数组考虑在内,却仍会返回 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[Varaint]

备注

数组是按引用传递的,因此数组元素类型也是运行时变量引用的内存结构的一个属性。变量的静态类型限制了它可以引用的结构。因此,你不能为数组内的元素赋予不同的元素类型的值,即使该类型是数组所接受类型的子类型。

If you want to convert a typed array, you can create a new array and use the Array.assign() method:

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)

ArrayArray[Variant] )则是例外,这样做可以保证用户使用的便捷性与与旧版本代码的兼容性。不过,非类型化的数组是不安全的。

紧缩数组

与相同类型的类型化数组 Array 相比,紧缩数组的遍历和修改速度通常更快(例如 PackedInt64Array 与 Array[int] 比较)。即便是最坏的情况,这两种数组也不会比无类型的 Array 慢。不过非紧缩数组(无论是否类型化)都会额外包含类似 Array.map 的便捷方法,紧缩数组则不提供。具体有哪些方法可用见类参考。类型化数组的遍历和修改速度通常比无类型数组更快。

只要足够大,任何 Array 都可能导致内存的碎片化。如果你需要在意内存占用和性能(迭代速度和修改速度),并且存储的数据类型与某种 Packed 数组类型兼容,那么使用这些类型有可能带来改进。当然如果你没有这些顾虑(比如数组中的元素达不到数万个),那么使用常规的 Array 或类型化的 Array 可能更方便,因为它们提供了便捷的方法,可以使你的代码更易于编写和维护(如果你的数据需要大量此类操作,也许还能提升速度)。如果你知道要存储什么类型的数据(包括你自己定义的类),那么建议使用类型化数组,因为与无类型数组相比,类型化数组在迭代和修改时可以提供更好的性能。

Dictionary

关联容器,其内部数值通过与之对应的唯一的键进行引用。

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 风格的 table 语法。Lua 风格的 GDScript 字典语法在标记字符串键时,使用的是 = 而非 :,且不使用引号(这样要写的东西会稍微少一些)。但请注意,以这种形式编写的键和 GDScript 标识符一样不能以数字开头,且必须为字面量。

var d = {
    test22 = "value",
    some_key = 2,
    other_key = [2, 3, 4],
    more_key = "Hello"
}

To add a key to an existing dictionary, access it like an existing key and assign to it:

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() 方法。

Typed dictionaries

Godot 4.4 added support for typed dictionaries. On write operations, Godot checks that element keys and values match the specified type, so the dictionary cannot contain invalid keys or values. The GDScript static analyzer takes typed dictionaries into account. However, dictionary methods that return values still have the Variant return type.

Typed dictionaries have the syntax Dictionary[KeyType, ValueType], where KeyType and ValueType can be any Variant type, native or user class, or enum. Both the key and value type must be specified, but you can use Variant to make either of them untyped. Nested typed collections (like Dictionary[String, Dictionary[String, int]]) are not supported.

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 and Dictionary[Variant, Variant] are the same thing.

Signal

信号由对象发出,并由对象所监听。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()

If the variable is initialized within the declaration, the type can be inferred, so it's possible to omit the type name:

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 等)。

  • 包含脚本资源的常量名(如 MyScript ,前提是声明了 const MyScript = preload("res://my_script.gd") )。

  • 在同一个脚本中的其他内部类,此时需要注意作用域(比如:在相同作用域内,在 class InnerClass 中声明 class NestedClass 则会得到 InnerClass.NestedClass )。

  • 通过 class_name 关键字声明的脚本类。

  • 自动加载的节点——单例节点。

备注

虽然 Variant 类型被引擎视作有效类型,但其并不是一个确切的类型,只是一个“没有固定类型”的代名词。使用 Variant 类型很有可能会导致报错,因此引擎默认不会对该类型进行推断。

你可以在项目设置中将该检查关闭,或将其设为警告。详见 GDScript 警告系统

初始化顺序

成员变量的初始化顺序如下:

  1. 变量根据其静态类型,取值为 null(无类型变量和对象)或类型的默认值(int0boolfalse等)。

  2. 根据脚本中变量的声明顺序,由上至下进行指定值的赋值。

    • (仅适用于派生自 Node 的类)如果变量由 @onready 注解修饰,则会推迟到第 5 步再初始化。

  3. 所有非 @onready 成员变量均完成定义时调用 _init() 方法。

  4. 初始化场景和资源时,赋导出的值。

  5. (仅适用于派生自 Node 的类)初始化 @onready 变量。

  6. (仅适用于派生自 Node 的类)如果定义了 _ready() 方法,则会对其进行调用。

警告

You can specify a complex expression as a variable initializer, including function calls. Make sure the variables are initialized in the correct order, otherwise your values may be overwritten. For example:

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)

Will print:

{ "a": 1 }
{ "a": 1, "b": 2 }
{  }

解决这个问题只需将 _data 变量的定义移动到 a 的定义之前,或者移除空字典的赋值(={})。

静态变量

A class member variable can be declared static:

static var a

静态变量直属于类而非类的实例,即静态变量可以在多个类实例之间共享数据,这一点与一般的成员变量有所区别。

在类内,静态函数和非静态函数都可以访问静态变量。在类外,可以通过使用类名或类的实例来访问静态变量(后者并不推荐,因为可读性较低)。

备注

@export 注解和 @onready 注解不能修饰静态成员变量。局部变量不能声明为静态局部变量。

下面的例子中定义了一个 Person 类,其中有一个静态变量 max_id。我们在 _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

Static variables can have type hints, setters and getters:

static var balance: int = 0

static var debt: int:
    get:
        return -balance
    set(value):
        balance = -value

A base class static variable can also be accessed via a child class:

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

备注

When referencing a static variable from a tool script, the other script containing the static variable must also be a tool script. See Running code in the editor for details.

@static_unload 注解

GDScript 的类均为资源,而静态变量会阻止脚本资源卸载,即便该脚本所对应的类的实例以及对该实例引用并不存在,静态变量依旧会阻止该脚本资源卸载。在静态变量存储大量数据,同时还含有对其他对象的引用(比如场景)的情况下,更需要引起格外重视。你需要手动清理掉这些数据,亦或是使用 @static_unload 注解,让静态变量在不存储重要数据时得到重置。

警告

目前由于某个漏洞导致含静态成员变量的脚本实例即使使用了 @static_unload 注解也无法被清除的问题。

Note that @static_unload applies to the entire script (including inner classes) and must be placed at the top of the script, before class_name and 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.

Casting is also useful to have better type-safe variables when interacting with the scene tree:

# 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).

Although the type of constants is inferred from the assigned value, it's also possible to add explicit type specification:

const A: int = 5
const B: Vector2 = Vector2()

赋予与指定的类型不相容的值将触发报错。

也可以在函数内使用常量来声明一些局部魔法值。

枚举

枚举实质上是常量的简写,适用于为某些常量连续赋整数值。

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

若将名称传递给枚举,则该枚举将会把所有键纳入该名称的 Dictionary 中,即字典中的所有常方法均可用于具名枚举当中。

重要

从 Godot 3.1 开始,不会再将具名枚举的键注册为全局常量,此后,应在枚举常量前缀以枚举名的形式来访问枚举内的枚举常量( 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())

如果没有为枚举中的键赋值,就会自动赋值为前一个值加一,如果是枚举中的第一个条目则赋值为 0。不同的键可以具有相同的值。

函数

函数始终属于某个。查找变量时,作用域的查找顺序是:局部→类成员→全局。始终可以通过 self 变量访问类成员(见 self),但这不是必须的(与 Python 不同,在 GDScript 中不应该将其作为函数的第一个参数传递)。

func my_function(a, b):
    print(a)
    print(b)
    return a + b  # Return is optional; without it 'null' is returned.

函数可以在任何时候用 return 返回,默认的返回值为 null

If a function contains only one line of code, it can be written on one line:

func square(a): return a * a

func hello_world(): print("Hello World")

func empty_function(): pass

Functions can also have type specification for the arguments and for the return value. Types for arguments can be added in a similar way to variables:

func my_function(a: int, b: String):
    pass

If a function argument has a default value, it's possible to infer the type:

func my_function(int_arg := 42, String_arg := "string"):
    pass

The return type of the function can be specified after the arguments list using the arrow token (->):

func my_int_function() -> int:
    return 0

有返回类型的函数必须返回与返回值类型相匹配的值。将返回值类型设置为 void 表示该函数不返回任何东西。这种函数称为 void 函数,可以使用 return 关键字提前返回,但不能返回任何值。

func void_function() -> void:
    return # Can't return a value.

备注

非 void 函数 必须 返回一个值,如果你的代码具有分支语句(例如 if/else 构造),则所有可能的路径都必须有返回值。例如,如果在 if 块内有一个 return,但在其后没有,则编辑器将抛出一个错误,因为如果该代码块未执行,那么该函数将没有值进行有效返回。

引用函数

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]`.

备注

可调用体必须使用 call() 方法进行调用,不能直接使用 () 运算符。实现这种行为是为了避免影响直接调用函数的性能问题。

Lambda 函数

Lambda 函数允许声明不属于类的函数,会直接创建 Callable 对象并将其赋值给变量。Lambda 函数可以创建可传递的可调用体,同时又不会污染该类的作用范围,非常有用。

var lambda = func (x):
    print(x)

To call the created lambda you can use the call() method:

lambda.call(42) # Prints `42`.

Lambda functions can be named for debugging purposes (the name is displayed in the Debugger):

var lambda = func my_lambda(x):
    print(x)

You can specify type hints for lambda functions in the same way as for regular ones:

var lambda := func (x: int) -> void:
    print(x)

Note that if you want to return a value from a lambda function, an explicit return is required (you can't omit return):

var lambda = func (x): return x ** 2
print(lambda.call(2)) # Prints `4`.

Lambda functions capture the local environment:

var x = 42
var lambda = func ():
    print(x) # Prints `42`.
lambda.call()

警告

Local variables are captured by value once, when the lambda is created. So they won't be updated in the lambda if reassigned in the outer function:

var x = 42
var lambda = func (): print(x)
lambda.call() # Prints `42`.
x = "Hello"
lambda.call() # Prints `42`.

Also, a lambda cannot reassign an outer local variable. After exiting the lambda, the variable will be unchanged, because the lambda capture implicitly shadows it:

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`.

However, if you use pass-by-reference data types (arrays, dictionaries, and objects), then the content changes are shared until you reassign the variable:

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]`.

静态函数

A function can be declared static. When a function is static, it has no access to the instance member variables or self. A static function has access to static variables. Also static functions are useful to make libraries of helper functions:

static func sum2(a, b):
    return a + b

Lambda 函数不可声明为静态函数。

静态变量静态构造函数

Variadic functions

A variadic function is a function that can take a variable number of arguments. Since Godot 4.5, GDScript supports variadic functions. To declare a variadic function, you need to use the rest parameter, which collects all the excess arguments into an array.

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]

A function can have at most one rest parameter, which must be the last one in the parameter list. The rest parameter cannot have a default value. Static and lambda functions can also be variadic.

Static typing works for variadic functions too. However, typed arrays are currently not supported as a static type of the rest parameter:

# 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

备注

Although you can declare functions as variadic using the rest parameter, unpacking parameters when calling a function using spread syntax that exists in some languages ​​(JavaScript, PHP) is currently not supported in GDScript. However, you can use callv() to call a function with an array of arguments:

func log_data(...values):
    # ...

func other_func(...args):
    #log_data(...args) # This won't work.
    log_data.callv(args) # This will work.

Abstract functions

See Abstract classes and methods.

语句与流程控制

标准的语句可以是赋值、函数调用以及流程控制结构等(见下方)。; 为语句分隔符,在使用时可写可略。

表达式

表达式是运算符和操作数的有序排列,尽管表达式本身可以构成一个语句,但仅函数调用才适合作为语句使用,因为其他类型的表达式通常不会产生副作用。

表达式返回的数值可赋值给有效目标,而某些运算符的操作数也可以变成一条表达式。赋值语句因无返回值而不能作为表达式使用。

Here are some examples of expressions:

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 语法创建。条件中的括号可写可不写。考虑到基于制表符缩进的性质,可以使用 elif 而非 else/if 来保持缩进级别相同。

if (expression):
    statement(s)
elif (expression):
    statement(s)
else:
    statement(s)

Short statements can be written on the same line as the condition:

if 1 + 1 == 2: return 2 + 2
else:
    var x = 3 + 3
    return x

Sometimes, you might want to assign a different initial value based on a boolean expression. In this case, ternary-if expressions come in handy:

var x = (value) if (expression) else (value)
y += 3 if y < 10 else -1

Ternary-if expressions can be nested to handle more than 2 cases. When nesting ternary-if expressions, it is recommended to wrap the complete expression over multiple lines to preserve readability:

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

You may also wish to check if a value is contained within something. You can use an if statement combined with the in operator to accomplish this:

# 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 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 对类型的要求比 == 运算符更严格。例如 11.0不匹配的。唯一的例外是 StringStringName 的匹配:例如会认为字符串 "hello" 和 StringName &"hello" 相等。

基本语法

match <test value>:
    <pattern(s)>:
        <block>
    <pattern(s)> when <pattern guard>:
        <block>
    <...>

给熟悉 switch 语句的人提供的速成课程

  1. switch 替换为 match

  2. 删除 case

  3. 删除 break

  4. default 替换为单个下划线。

流程控制

按照从上到下的顺序进行模式匹配。匹配成功时,会执行第一个对应的代码块。执行完成后,会继续执行 match 语句后的内容。

备注

3.x 版本支持在 match 中使用 continue 执行特殊行为,此行为已在 Godot 4.0 中移除。

可以使用以下模式类型:

  • 字面量模式

    Matches a literal:

    match x:
        1:
            print("We are number one!")
        2:
            print("Two are better than one!")
        "test":
            print("Oh snap! It's a string!")
    
  • 表达式模式

    Matches a constant expression, an identifier, or an attribute access (A.B):

    match typeof(x):
        TYPE_FLOAT:
            print("float")
        TYPE_STRING:
            print("text")
        TYPE_ARRAY:
            print("array")
    
  • 通配符模式

    匹配所有内容,用一个下划线来表示通配内容。

    It can be used as the equivalent of the default in a switch statement in other languages:

    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.")
    
  • 绑定模式

    A binding pattern introduces a new variable. Like the wildcard pattern, it matches everything - and also gives that value a name. It's especially useful in array and dictionary patterns:

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

模式防护

模式防护(Pattern Guard)是一个跟在模式列表后面的可选条件,可以用来在选择 match 分支之前进行额外的检查。与模式不同,模式防护可以是任意表达式。

Only one branch can be executed per match. Once a branch is chosen, the rest are not checked. If you want to use the same pattern for multiple branches or to prevent choosing a branch with too general pattern, you can specify a pattern guard after the list of patterns with the when keyword:

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 时,就会继续检查下一个分支的模式。

By default, all script files are unnamed classes. In this case, you can only reference them using the file's path, using either a relative or an absolute path. For example, if you name a script file 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()

注册具名类

You can give your class a name to register it as a new type in Godot's editor. For that, you use the class_name keyword. You can optionally use the @icon annotation with a path to an image, to use it as an icon. Your class will then appear with its new icon in the editor:

# item.gd

@icon("res://interface/icons/item.png")
class_name Item
extends Node
../../../_images/class_name_editor_register_example.png

小技巧

SVG 图片在用作自定义节点图标时,需要在该图片的 导入选项 中将 编辑器 > 依照编辑器比例缩放编辑器 > 依照编辑器主题转换颜色 勾选,这样才能让有跟 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)

If you want to use extends too, you can keep both on the same line:

class_name MyNode extends Node

Named classes are globally registered, which means they become available to use in other scripts without the need to load or preload them:

var player

func _ready():
    player = Character.new()

备注

由于脚本可以在用户不知情的情况下在单独的线程中初始化,出于线程安全考虑,Godot 在每次创建实例时,引擎都会初始化非静态变量,其中就包括数组和字典。

警告

Godot 编辑器会在“新建节点”和“新建场景”对话框窗口中隐藏名称以“Editor”开头的自定义类。这些类可以在运行时通过类名进行实例化,但会与 Godot 编辑器使用的内置编辑器节点一起被编辑器窗口自动隐藏。

Abstract classes and methods

Since Godot 4.5, you can define abstract classes and methods using the @abstract annotation.

An abstract class is a class that cannot be instantiated directly. Instead, it is meant to be inherited by other classes. Attempting to instantiate an abstract class will result in an error.

An abstract method is a method that has no implementation. Therefore, a newline or a semicolon is expected after the function header. This defines a contract that inheriting classes must conform to, because the method signature must be compatible when overriding.

Inheriting classes must either provide implementations for all abstract methods, or the inheriting class must be marked as abstract. If a class has at least one abstract method (either its own or an unimplemented inherited one), then it must also be marked as abstract. However, the reverse is not true: an abstract class is allowed to have no abstract methods.

小技巧

If you want to declare a method as optional to be overridden, you should use a non-abstract method and provide a default implementation.

For example, you could have an abstract class called Shape that defines an abstract method called draw(). You can then create subclasses like Circle and Square that implement the draw() method in their own way. This allows you to define a common interface for all shapes without having to implement all the details in the abstract class itself:

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

Both inner classes and classes created using class_name can be abstract. This example creates two abstract classes, one of which is a subclass of another abstract class:

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

警告

Since an abstract class cannot be instantiated, it is not possible to attach an abstract class to a node. If you attempt to do so, the engine will print an error when running the scene:

Cannot set object script. Script '<path to script>' should not be abstract.

Unnamed classes can also be defined as abstract, the @abstract annotation must precede extends:

@abstract
extends Node

继承

类(以文件形式保存)可以继承自:

  • 全局类。

  • 另一个类文件。

  • 另一个类文件中的内部类。

不允许多重继承。

Inheritance uses the extends keyword:

# 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

To check if a given instance inherits from a given class, the is keyword can be used:

# Cache the enemy class.
const Enemy = preload("enemy.gd")

# [...]

# Use 'is' to check inheritance.
if entity is Enemy:
    entity.apply_damage()

To call a function in a super class (i.e. one extend-ed in your current class), use the super keyword:

super(args)

This is especially useful because functions in extending classes replace functions with the same name in their super classes. If you still want to call them, you can use super:

func some_func(x):
    super(x) # Calls the same function on the super class.

If you need to call a different function from the super class, you can specify the function name with the attribute operator:

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 中覆盖。信号、通知也可用于自定义引擎行为。

类的构造函数

The class constructor, called on class instantiation, is named _init. If you want to call the base class constructor, you can also use the super syntax. Note that every class has an implicit constructor that is always called (defining the default values of class variables). super is used to call the explicit constructor:

func _init(arg):
   super("some_default", arg) # Call the custom base constructor.

This is better explained through examples. Consider this scenario:

# 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

这里有几点需要牢记:

  1. If the inherited class (state.gd) defines an _init constructor that takes arguments (e in this case), then the inheriting class (idle.gd) must define _init as well and pass appropriate parameters to _init from state.gd.

  2. Idle.gd 的构造函数的参数数量可以与基类 State.gd 的构造函数的参数数量有所不同。

  3. 在上面的示例中,传递给 State.gd 构造函数的 e 与传递给 Idle.gde 是相同的。

  4. If idle.gd's _init constructor takes 0 arguments, it still needs to pass some value to the state.gd base class, even if it does nothing. This brings us to the fact that you can pass expressions to the base constructor as well, not just variables, e.g.:

    # idle.gd
    
    func _init():

    super(5)

静态构造函数

A static constructor is a static function _static_init that is called automatically when the class is loaded, after the static variables have been initialized:

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

类作为资源

Classes stored as files are treated as GDScripts. They must be loaded from disk to access them in other classes. This is done using either the load or preload functions (see below). Instancing of a loaded class resource is done by calling the new function on the class object:

# 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 导出属性

属性(setter 与 getter)

有时,你可能不止希望对类成员进行数据存储操作,甚至想要在更改成员值的时候对其进行有效性检查操作或运算操作。你也可能希望以某种方式对该类成员的访问进行封装。

鉴于此,GDScript 提供了一套特别的语法,通过在变量定义后使用 setget 关键字来对类成员属性的读写进行封装。这样一来,你就可以在 set(setter 函数)、get(getter 函数)语句块里定义代码,在该成员被读写时执行之。

示例:

var milliseconds: int = 0
var seconds: int:
    get:
        return milliseconds / 1000
    set(value):
        milliseconds = value * 1000

备注

与之前的 Godot 版本中的 setget 不同,即使在同一个类中进行访问(不管是否添加 self. 前缀),也始终会调用属性的 set 方法和 get 方法(例外见下文) 。这样访问属性时的行为就一致了。如果你需要直接访问实际的值,请再添加一个变量用于直接访问,然后在属性相关的代码中使用这个变量的变量名。

替代语法

Also there is another notation to use existing class functions if you want to split the code from the variable declaration or you need to reuse the code across multiple properties (but you can't distinguish which property the setter/getter is being called for):

var my_prop:
    get = get_my_prop, set = set_my_prop

This can also be done in the same line:

var my_prop: get = get_my_prop, set = set_my_prop

Setter 函数和 Getter 函数在给一个变量定义时必须使用相同的定义格式,不允许混合使用这两种定义格式。

备注

不允许对 匿名setter 函数和 getter 函数进行类型指定,以减少代码的重复抄写量。若变量含有指定的类型,则其 setter 函数的参数会自动转换到相同的类型,同时其 getter 函数的返回值类型也必须与该类型相配。具名 setter/getter 函数允许指定类型提示,但这些函数的设值/返回类型必须与该属性的类型或该类型的广义类型相配。

Setter/getter 函数不会被调用的情况

变量在进行初始化时,其初始值会直接赋予给该变量,包括 @onready 注解所修饰的变量也是如此。

Using the variable's name to set it inside its own setter or to get it inside its own getter will directly access the underlying member, so it won't generate infinite recursion and saves you from explicitly declaring another variable:

signal changed(new_value)
var warns_when_changed = "some value":
    get:
        return warns_when_changed
    set(value):
        changed.emit(value)
        warns_when_changed = value

This also applies to the alternative syntax:

var my_prop: set = set_my_prop

func set_my_prop(value):
    my_prop = value # No infinite recursion.

警告

The exception does not propagate to other functions called in the setter/getter. For example, the following code will cause an infinite recursion:

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.

工具模式

By default, scripts don't run inside the editor and only the exported properties can be changed. In some cases, it is desired that they do run inside the editor (as long as they don't execute game code or manually avoid doing so). For this, the @tool annotation exists and must be placed at the top of the file:

@tool
extends Button

func _ready():
    print("Hello")

详情见 在编辑器中运行代码

警告

由于工具脚本是在编辑器中运行代码的,故在工具脚本中使用 queue_free()free() 释放节点时需要谨慎(尤其是对脚本所有者本身使用的时候更是如此)。对工具脚本滥用释放节点代码可能会导致编辑器崩溃。

内存管理

Godot 通过实现引用计数来释放某些不再使用的实例,而非通过垃圾收集器(GC),或者需要纯手动管理内存释放来实现这一操作。RefCounted 类(或继承该类的任何类,例如 Resource)的任何实例在不再使用时将自动释放。对于非 RefCounted 类(例如 Node 或基本 Object 类型)的实例,这些实例将保留在内存中,直到使用 free() (或用于节点的 queue_free())才会从内存中删除。

备注

如果通过 free()queue_free() 删除 Node,则它的所有子节点也将会被递归删除。

为了避免造成无法释放的循环引用,Godot 提供了用于创建弱引用的 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(instance) 来检查对象是否已被释放。

信号

信号是从对象中发出消息的工具,其他对象可以对该信号做出反应。要为一个类创建自定义信号,请使用 signal 关键字。

extends Node


# A signal named health_depleted.
signal health_depleted

备注

信号是一种回调机制,同时还充当观察者的角色,这是一种常见的编程模式。有关更多信息,请阅读《游戏编程模式》电子书中的观察者教程

你可以将这些信号连接到方法,就像连接 ButtonRigidBody3D 等节点的内置信号一样。

In the example below, we connect the health_depleted signal from a Character node to a Game node. When the Character node emits the signal, the game node's _on_character_health_depleted is called:

# 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()

可以在发出一个信号时给该信号附带任意数量的参数。

下面这个示例就是该特性的一个不错的实现。假设我们希望屏幕上的生命条能够通过动画对生命值做出反应,但我们希望在场景树中让用户界面与游戏角色保持独立。

In our character.gd script, we define a health_changed signal and emit it with Signal.emit(), and from a Game node higher up our scene tree, we connect it to the Lifebar using the Signal.connect() method:

# 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 节点中,我们同时获得 CharacterLifebar 节点,然后将发出信号的 Character 连接到接收者节点上,在本例中 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 节点内。

You can write optional argument names in parentheses after the signal's definition:

# Defining a signal that forwards two arguments.
signal health_changed(old_value, new_value)

这些参数会显示在编辑器的节点面板中,Godot 会在生成回调函数时自动为你添加这些参数。但是,在发出信号时仍然可以发出任意数量的参数,需要由你来确定该信号需要准确发出的值。

../../../_images/gdscript_basics_signals_node_tab_1.png

You can also create copies of GDScript Callable objects which accept additional arguments using Callable.bind(). This allows you to add extra information to the connection if the emitted signal itself doesn't give you access to all the data that you need.

When the signal is emitted, the callback method receives the bound values, in addition to those provided by the signal.

Building on the example above, let's say we want to display a log of the damage taken by each character on the screen, like Player1 took 22 damage.. The health_changed signal doesn't give us the name of the character that took damage. So when we connect the signal to the in-game console, we can add the character's name using the bind method:

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

Our BattleLog node receives each bound element as an extra argument:

# 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 关键字会立即将控制权返回给调用方。发出信号时(或者调用的协程函数完成时),就会从停止的地方继续往下执行代码。

For example, to stop execution until the user presses a button, you can do something like this:

func wait_confirmation():
    print("Prompting user")
    await $Button.button_up # Waits for the button_up signal from Button node.
    print("User confirmed")
    return true

In this case, the wait_confirmation becomes a coroutine, which means that the caller also needs to await it:

func request_confirmation():
    print("Will ask the user")
    var confirmed = await wait_confirmation()
    if confirmed:
        print("User confirmed")
    else:
        print("User cancelled")

Note that requesting a coroutine's return value without await will trigger an error:

func wrong():
    var confirmed = wait_confirmation() # Will give an error.

However, if you don't depend on the result, you can just call it asynchronously, which won't stop execution and won't make the current function a coroutine:

func okay():
    wait_confirmation()
    print("This will be printed immediately, before the user press the button.")

If you use await with an expression that isn't a signal nor a coroutine, the value will be returned immediately and the function won't give the control back to the caller:

func no_wait():
    var x = await get_five()
    print("This doesn't make this function a coroutine.")

func get_five():
    return 5

This also means that returning a signal from a function that isn't a coroutine will make the caller await that signal:

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)

在编辑器中运行项目时,如果发生断言错误,则会暂停该项目的运行。

You can optionally pass a custom error message to be shown if the assertion fails:

assert(enemy_power < 256, "Enemy is too powerful!")