GDScript 基础

前言

GDScript 是一种用于创建内容的高级, 动态类型的编程语言. 它使用类似于 Python 的语法(块基于缩进, 许多关键字相似). 其目标是针对Godot引擎进行优化并与之紧密集成, 从而为内容创建和集成提供极大的灵活性.

历史

注解 常见问题 中.

GDScript的示例

有些人可以通过查看语法来更好地学习, 因此, 这有GDScript外观的简单示例.

  1. # A file is a class!# Inheritanceextends BaseClass# (optional) class definition with a custom iconclass_name MyClass, "res://path/to/optional/icon.svg"# Member variablesvar a = 5var s = "Hello"var arr = [1, 2, 3]var dict = {"key": "value", 2: 3}var typed_var: intvar inferred_type := "String"# Constantsconst ANSWER = 42const THE_NAME = "Charly"# Enumsenum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}enum Named {THING_1, THING_2, ANOTHER_THING = -1}# Built-in vector typesvar v2 = Vector2(1, 2)var v3 = Vector3(1, 2, 3)# Functionfunc some_function(param1, param2): var local_var = 5 if param1 < local_var: print(param1) elif param2 > 5: print(param2) else: print("Fail!") for i in range(20): print(i) while param2 != 0: param2 -= 1 var local_var2 = param1 + 3 return local_var2# Functions override functions with the same name on the base/parent class.# If you still want to call them, use '.' (like 'super' in other languages).func something(p1, p2): .something(p1, p2)# Inner classclass Something: var a = 10# Constructorfunc _init(): print("Constructed!") var lv = Something.new() print(lv.a)

GDScript:动态语言简介.

语言

在下面, 概述了GDScript. 详细信息, 例如哪些方法可用于数组或其他对象, 可以在链接的类描述中查找到这些方法.

标识符(Identifiers)

azAZ ), 数字( 09 )和 _ 的字符串都可以作为标识符. 此外, 标识符不能以数字开头. 标识符区分大小写( fooFOO 是不同的).

关键字(Keywords)

in, not, and, 或 or )以及下面列出的内置类型的名称也是保留的. GDScript tokenizer 中, 如果你想深入了解。

运算符

下面是支持的运算符列表及其优先级(越上面越高).

字面量(Literals)

_ 来分隔,使其更易读。以下表示数字的方式都是有效的:

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

注释

# 开始到行尾的内容都会被忽略, 并被视为注释.

  1. # This is a comment.

内置类型

Array 和字典 Dictionary ,它们是共享的,按引用传递。(类似 PoolByteArray 的池数组还是按值传递的。)

基本内置类型

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

null

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

bool

truefalse

int

int64_t

float

double。注意:目前类似 Vector2、Vector3、PoolRealArray 的数据结构存储的是 32 位单精度“float”值。

String

Unicode 格式 的字符序列。字符串可以包含以下转义序列: GDScript 格式字符串.

内置向量类型

Vector2

xy 字段,也可以像数组一样访问。

Rect2

positionsize。还包含一个 end 字段,即 position + size

Vector3

xyz 字段,也可以像数组一样访问。

Transform2D

用于2D变换的3x2矩阵.

Plane

normal 向量字段以及一个 d 标量距离.

Quat

四元数是一种用于表示3D旋转的数据类型. 它对于内插旋转很有用.

AABB

positionsize. 还包含一个 end 字段, 即 position + size.

Basis

x, yz) 并且可以像3D向量数组那样访问.

Transform

Basis 字段 basis 和一个 Vector3 字段 origin.

引擎内置类型

Color

r, g, b, 和 a 字段. 它也可以作为 h, s, 和 v 来访问色相/饱和度/值.

NodePath

编译路径, 到一个主要用在场景系统中的节点. 它可以很容易地从一个字符串获得, 或获得一个字符串.

RID

资源ID(RID). 服务使用通用的RID来引用不透明数据.

Object

任何非内置类型的基类.

容器内置类型

Array

0 开始。负索引表示从尾部开始计数.

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

GDScript 数组在内存中线性分配以提高速度。但是,大型数组(包含数万个元素)可能会导致内存碎片。如果在意这个问题,可以使用特定类型的数组。它们只接受单个数据类型。它们避免了内存碎片并使用更少的内存,但是它们是原子的,运行起来容易比通用数组要慢。因此,建议仅将它们用于大型数据集:

  • PoolByteArray: 一个字节(从0到255的整数)数组.
  • PoolIntArray: 一个整数数组.
  • PoolRealArray: 一个浮点数数组.
  • PoolStringArray: 一个字符串数组.
  • PoolVector2Array: 一个 Vector2 对象的数组.
  • PoolVector3Array: 一个 Vector3 对象数组.
  • PoolColorArray: 一个 Color 对象的数组.

    Dictionary

    关联容器, 其中包含唯一键引用的值.
    1. var d = {4: 5, "A key": "A value", 28: [1, 2, 3]}d["Hi!"] = 0d = { 22: "value", "some_key": 2, "other_key": [2, 3, 4], "more_key": "Hello"}
    = 而不是 : , 并且不使用引号来标记字符串键(这样写起来会稍微少一些). 但是请注意, 与任何GDScript标识符一样, 以这种形式编写的键不能以数字开头.
    1. var d = { test22 = "value", some_key = 2, other_key = [2, 3, 4], more_key = "Hello"}
    若要向现有字典添加键,请像访问现有键一样访问它,并给它赋值:
    1. 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])
    注解 Object 的属性. 记住, 当尝试读取一个不存在的属性时, 会引发脚本错误. 为了避免这一点, 用 Object.get() 和 Object.set() 方法作为替代.

    数据

    变量

    var 关键字创建的, 并且可以在初始化时指定一个值.
    1. var a # Data type is 'null' by default.var b = 5var c = 3.8var d = b + c # Variables are always initialized in order.
    变量可以选择具有类型声明. 指定类型时, 变量将强制始终具有相同的类型, 并且试图分配不兼容的值将引发错误. : (冒号)符号在变量名后面指定, 后面是类型.
    1. var my_vector2: Vector2var my_node: Node = Sprite.new()
    如果在声明中初始化变量,则可以推断类型,因此可以省略类型名称:
    1. var my_vector2 := Vector2() # 'my_vector2' is of type 'Vector2'.var my_node := Sprite.new() # 'my_node' is of type 'Sprite'.
    类型推断只有在指定的值具有定义的类型时才可能, 否则将引发错误. 有效的类型有:
  • 内置类型(Array, Vector2, int, String, 等).
  • 引擎类(Node, Resource, Reference, 等).
  • MyScript 如果声明 const MyScript = preload("res://my_script.gd")).
  • class InnerClass 中声明 class NestedClass 得到 InnerClass.NestedClass ).
  • class_name 关键字声明.

    转换

    as. 如果值是相同类型或转换类型的子类型, 则在对象类型之间进行转换会导致相同的对象.
    1. var my_node2D: Node2Dmy_node2D = $Sprite as Node2D # Works since Sprite is a subtype of Node2D.
    null 值.
    1. var my_node2D: Node2Dmy_node2D = $Button as Node2D # Results in 'null' since a Button is not a subtype of Node2D.
    对于内置类型, 如果可能, 将对其进行强制转换, 否则引擎将引发错误.
    1. var my_int: intmy_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.
    与场景树进行交互时,强制转换对于获得更好的类型安全变量也很有用:
    1. # Will infer the variable to be of type Sprite.var my_sprite := $Character as Sprite# Will fail if $AnimPlayer is not an AnimationPlayer, even if it has the method 'play()'.($AnimPlayer as AnimationPlayer).play("walk")

    常量

    const 关键字即可为常量值赋予名称. 尝试为常量重写赋值会引发错误. 我们建议使用常量来储存不应当更改的值.
    1. const A = 5const 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).
    尽管可以从分配的值中推断出常量的类型,但是也可以添加显式的类型说明:
    1. const A: int = 5const B: Vector2 = Vector2()
    分配不兼容类型的值将引发错误. 注解 由于数组和字典是通过引用的方式传递,常数是 “浅的”。这代表,如果你声明了一个常数数组或字典类型的变量名,依然可以增删数组或字典内部元素,但你不能将常数变量名重新赋予一个新的数组或字典。

    枚举

    枚举基本上是常量的简写, 如果你想为某些常量分配连续整数, 那么枚举非常有用. 如果将名称传递给枚举, 它将把所有键放入该名称的常量字典中. 重要 Name.KEY);见后面的例子.
    1. enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}# Is the same as:const TILE_BRICK = 0const TILE_FLOOR = 1const TILE_SPIKE = 2const TILE_TELEPORT = 3enum 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.

    函数

    类 . 变量查找的作用域的优先级是:局部 → 类成员 → 全局. self 变量总是可用的, 并作为访问类成员的选项提供, 但并不总是必需的(与Python不同, 应该将其作为函数的第一个参数传递).
    1. func my_function(a, b): print(a) print(b) return a + b # Return is optional; without it 'null' is returned.
    return . 默认返回值是 null. 函数也可以具有参数和返回值的类型声明。可以使用与变量类似的方式添加参数的类型:
    1. func my_function(a: int, b: String): pass
    如果函数参数具有默认值,则可以推断类型:
    1. func my_function(int_arg := 42, String_arg := "string"): pass
    ->)指定函数的返回类型:
    1. func my_int_function() -> int: return 0
    必须 返回正确的值. 将类型设置为 void 意味着函数不返回任何内容.Void函数可以使用 return 关键字提前返回, 但不能返回任何值.
    1. func void_function() -> void: return # Can't return a value
    注解 总是 返回一个值, 所以如果您的代码有分支语句(例如 if/else 构造), 那么所有可能的路径都必须返回一个值. 例如, 如果在 if 块中有一个 return , 但在 if 块之后没有, 编辑器就会抛出一个错误, 因为如果没有执行这个块, 该函数将没有有效值返回.

    引用函数

    不是 GDScript中的第一类对象. 这意味着它们不能存储在变量中, 不能作为参数传递给另一个函数, 也不能从其他函数返回. 这是出于性能原因. callfuncref 帮助函数:
    1. # Call a function by name in one step.my_node.call("my_function", args)# Store a function reference.var my_func = funcref(my_node, "my_function")# Call stored function reference.my_func.call_func(args)

    静态函数

    self。这对于创建辅助函数库非常有用:
    1. static func sum2(a, b): return a + b

    语句和控制流程

    ; 作为语句分隔符是完全可选的.

    if/else/elif

    if/else/elif 语法创建的. 条件的括号是允许的, 但不是必需的. 考虑到基于制表符的缩进的性质, 可以使用 elif 而不是 else/if 来维持缩进的级别.
    1. if [expression]: statement(s)elif [expression]: statement(s)else: statement(s)
    短语句可以写在与条件相同的行上:
    1. if 1 + 1 == 2: return 2 + 2else: var x = 3 + 3 return x
    有时您可能希望基于布尔表达式分配不同的初始值。在这种情况下,三元表达式将派上用场:
    1. var x = [value] if [expression] else [value]y += 3 if y < 10 else -1
    可以通过嵌套三元 if 表达式来处理的超过两种可能性的情况。嵌套时,推荐把三元 if 表达式拆分到多行以保持可读性:
    1. var count = 0var 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

    while

    while 语法创建的. 可以使用 break 来中断循环, 或者使用 continue 来继续:
    1. while [expression]: statement(s)

    for

    for 循环. 在数组上迭代时, 当前数组元素存储在循环变量中. 在遍历字典时, 键(key) 存储在循环变量中.
    1. for x in [5, 7, 11]: statement # Loop iterates 3 times with 'x' as 5, then 7 and finally 11.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 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))

    match

    match 语句用于分支程序的执行. 它相当于在许多其他语言中出现的 switch 语句, 但提供了一些附加功能. 基本语法:
    1. match [expression]: [pattern](s): [block] [pattern](s): [block] [pattern](s): [block]
    熟悉switch语句的人的速成课程 :
  1. switch 替换为 match.
  2. case.
  3. breaks . 如果不想使用默认的 break(停止向下匹配) , 可以使用 continue 作向下穿透匹配(fallthrough).
  4. default 替换为单个下划线. 控制流 : match 语句下面的内容. 你可以使用 continue 来停止执行当前的块, 并检查它下面的模式是否有额外的匹配. 有6种模式类型:
  • 常量模式 常量原语,例如数字和字符串:
  1. match x: 1: print("We are number one!") 2: print("Two are better than one!") "test": print("Oh snap! It's a string!")
  • 变量模式 匹配变量/枚举的内容:
  1. match typeof(x): TYPE_REAL: print("float") TYPE_STRING: print("text") TYPE_ARRAY: print("array")
  • 通配符模式 这个模式匹配所有内容. 它被写成一个下划线. switch 语句中的 default 等效:
  1. 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.")
  • 绑定模式 绑定模式引入了一个新变量。与通配符模式类似,它匹配所有内容——并为该值提供一个名称。它在数组和字典模式中特别有用:
  1. 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)
  • 数组模式 匹配一个数组. 数组模式的每个元素本身都是模式, 因此您可以嵌套它们. 首先测试数组的长度, 它的大小必须与模式相同, 否则模式不匹配. 开放式数组 : 通过使最后一个子模式为 .. , 可以使数组大于模式. 每个子模式都必须用逗号分隔.
  1. 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")
  • 字典模式 工作方式与数组模式相同. 每个键必须是一个常量模式. 首先要测试字典的大小, 它的大小必须与模式相同, 否则模式不匹配. 开放式字典 : 通过将最后一个子字样改为 .. , 使字典可以比模式大. 每个子模式都必须用逗号分隔. 如果不指定值, 则仅检查键的存在. : 分隔.
  1. 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")
  • 多重模式 您还可以指定由逗号分隔的多重模式. 这些模式不允许包含任何绑定.
  1. match x: 1, 2, 3: print("It's 1 - 3") "Sword", "Splash potion", "Fist": print("Yep, you've taken damage")

character.gd

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

Registering named classes

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:

  1. # Item.gdextends Nodeclass_name Item, "res://interface/icons/item.png"

警告 res://addons/ 目录下, class_name 只有当脚本是一个 enabled 编辑器插件的一部分时, 才会使节点出现在 Create New Node 对话框中. 更多信息请参见 制作插件 . 这是一个类文件示例:

  1. # Saved as a file named 'character.gd'.class_name Charactervar health = 5func print_health(): print(health)func print_this_script_three_times(): print(get_script()) print(ResourceLoader.load("res://character.gd")) print(Character)

注解 Godot的类语法很紧凑: 它只能包含成员变量或函数. 可以使用静态函数, 但不能使用静态成员变量. 同样, 每次创建实例时, 引擎都会初始化变量, 这包括数组和字典. 这是线程安全的精神, 因为脚本可以在用户不知情的情况下在单独的线程中初始化.

继承

一个类(存储为文件)可以继承自:

  • 一个全局的类.
  • 另一个类文件.
  • 另一个类文件中的内部类. 不允许多重继承. extends 关键字:
    1. # 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
    is 关键字:
    1. # Cache the enemy class.const Enemy = preload("enemy.gd")# [...]# Use 'is' to check inheritance.if entity is Enemy: entity.apply_damage()
    基类(即当前类 extends 的类)中的函数,请在函数名前面加上 .
    1. .base_func(args)
    .(这就像其他语言中的 super 关键字一样):
    1. func some_func(x): .some_func(x) # Calls the same function on the parent class.
    注解 _init 和大多数通知像 _enter_tree, _exit_tree, _process, _physics_process 等, 将自动调用在所有父类中的函数. 重载它们时无需显式调用它们.

    类的构造函数

    _init. 如前所述, 父类的构造函数在继承类时被自动调用. 所以通常不需要显式调用 ._init(). .some_func 那样,如果被继承的类的构造函数接受参数,则将它们传递为:
    1. func _init(args).(parent_args): pass
    通过示例可以更好地说明这一点。考虑这种情况:
    1. # State.gd (inherited class)var entity = nullvar message = nullfunc _init(e=null): entity = efunc enter(m): message = m# Idle.gd (inheriting class)extends "State.gd"func _init(e=null, m=null).(e): # Do something with 'e'. message = m
    这里有几件事要记住:
  1. State.gd)定义了一个带有参数(在这种情况下为 e)的 _init 构造函数, 然后, 继承的类(Idle.gd)也 必须 定义 _init 并将适当的参数从 State.gd 传递给 _init.
  2. Idle.gd 可以有与基类 State.gd 不同数量的参数.
  3. State.gd 的构造函数的 e 与传递给 Idle.gde 是相同的.
  4. Idle.gd_init 构造函数接受0个参数,即使它什么也不做也仍然需要将一些值传递给 State.gd 父类。当然,我们除了可以给基类构造函数传变量之外,也可以传字面量,例如:
  1. # Idle.gdfunc _init().(5): pass

内部类

class 关键字定义. 它们使用 ClassName.new() 函数实例化.

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

类作为资源

Resource。必须从磁盘加载它们,才能在其他类中访问它们。这可以使用 loadpreload 函数来完成(后述)。一个加载的类资源的实例化是通过调用类对象上的 new 函数来完成的:

  1. # 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 导出.

Setters/getters

知道类的成员变量何时出于任何原因更改通常是很有用的. 也可能需要以某种方式封装其访问. setget 关键字提供了一个 setter/getter 语法. 在变量定义后可直接使用:

  1. var variable = value setget setterfunc, getterfunc

variable 的值被外部代码(即不是来自该类中的本地使用)修改时,setter 函数(上面的 setterfunc)就会被调用。这发生在值改变之前setter 必须决定如何处理新值。反之亦然,当 variable 被访问时,getter 函数(上面的 getterfunc)必须 return 所需的值。示例如下:

  1. var my_var setget my_var_set, my_var_getfunc my_var_set(new_value): my_var = new_valuefunc my_var_get(): return my_var # Getter must return a value.

setter 或者 getter 函数都可省略:

  1. # Only a setter.var my_var = 5 setget my_var_set# Only a getter (note the comma).var my_var = 5 setget ,my_var_get

导出变量 到编辑器时,Getters/Setters格外好用. 本地 访问 触发setter和getter. 这里有个说明:

  1. func _init(): # Does not trigger setter/getter. my_integer = 5 print(my_integer) # Does trigger setter/getter. self.my_integer = 5 print(self.my_integer)

工具模式

tool 关键字并将它放在文件的顶部:

  1. toolextends Buttonfunc _ready(): print("Hello")

在编辑器中运行代码. 警告 queue_free()free() 释放节点时要谨慎. 工具脚本在编辑器中运行代码时, 滥用它们可能导致编辑器崩溃.

内存管理

Reference,那么当不再使用时,该实例将被释放。不存在垃圾回收器,只有引用计数。所有没有定义继承的类默认扩展的都是 Reference 类。如果不希望这样,那么这个类必须手动继承 Object,并且必须调用 instance.free()。为了避免因造成循环引用而导致无法释放,我们提供了 WeakRef 函数用于创建弱引用。示例如下:

  1. extends Nodevar my_node_reffunc _ready(): my_node_ref = weakref(get_node("MyNode"))func _this_is_called_later(): var my_node = my_node_ref.get_ref() if my_node: my_node.do_something()

is_instance_valid(instance) 来检查对象是否已被释放.

信号

signal 关键字.

  1. extends Node# A signal named health_depleted.signal health_depleted

注解 回调) 机制。它们还充当观察者(一种常见的编程模式)的角色。有关更多信息, 请阅读《游戏编程模式》电子书中的 观察者教程 (中文版 ) 。 Button 或 RigidBody)的方式相同. Character 节点的 health_depleted 信号连接到 Game 节点。当 Character 节点发出信号时,游戏节点的 _on_Character_health_depleted 会被调用:

  1. # Game.gdfunc _ready(): var character_node = get_node('Character') character_node.connect("health_depleted", self, "_on_Character_health_depleted")func _on_Character_health_depleted(): get_tree().reload_current_scene()

您可以发出任意数量的参数附带一个信号. 这是一个有用的示例. 假设我们希望屏幕上的生命条能够通过动画对健康值做出反应, 但我们希望在场景树中将用户界面与游戏角色保持独立. Character.gd 脚本中,我们定义一个 health_changed 信号并使用 Object.emit_signal() 发出该信号,然后从场景树上方的 Game 节点,使用 Object.connect() 方法将其连接到 Lifebar

  1. # Character.gd...signal health_changedfunc take_damage(amount): var old_health = health health -= amount # We emit the health_changed signal every time the # character takes damage. emit_signal("health_changed", old_health, health)...
  1. # 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)...

注解 Object 类或任何扩展它的类型, 例如 Node, KinematicBody, ControlGame 节点中, 我们同时获得 CharacterLifebar 节点, 然后将发出信号的 Character 连接到接收器, 在本例中为 Lifebar 节点.

  1. # Game.gdfunc _ready(): var character_node = get_node('Character') var lifebar_node = get_node('UserInterface/Lifebar') character_node.connect("health_changed", lifebar_node, "_on_Character_health_changed")

Lifebar 能够对健康值做出反应, 而无需将其耦合到 Character 节点. 您可以在信号的定义后的括号中写上可选的参数名称:

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

这些参数显示在编辑器的节点停靠面板中,Godot可以使用它们为您生成回调函数. 但是, 发出信号时仍然可以发出任意数量的参数;由您来发出正确的值. GDScript可以将值数组绑定到信号和方法之间的连接. 发出信号时, 回调方法将接收绑定值. 这些绑定参数对于每个连接都是唯一的, 并且值将保持不变. 如果发出的信号本身不能使您访问所需的所有数据, 则可以使用此值数组将额外的常量信息添加到连接. Player1 遭受了 22 伤害。health_changed 信号没有给我们提供受到伤害的角色的名称。因此,当我们将信号连接到游戏终端中时,可以在绑定数组参数中添加角色的名称:

  1. # Game.gdfunc _ready(): var character_node = get_node('Character') var battle_log_node = get_node('UserInterface/BattleLog') character_node.connect("health_changed", battle_log_node, "_on_Character_health_changed", [character_node.name])

BattleLog 节点接收绑定数组中的每个元素作为一个额外的参数:

  1. # BattleLog.gdfunc _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."

协程使用yield

yield 提供对 协程 的支持。调用 yield() 将立即从当前函数返回,并且使用该函数的当前冻结状态作为返回值。在此结果对象上调用 resume() 将继续执行并返回函数返回的任何内容。恢复后,该状态对象将失效。这是一个例子:

  1. func my_func(): print("Hello") yield() print("world")func _ready(): var y = my_func() # Function state saved in 'y'. print("my dear") y.resume() # 'y' resumed and is now an invalid state.

将打印:

  1. Hellomy dearworld

yield()resume() 之间传递值,例如:

  1. func my_func(): print("Hello") print(yield()) return "cheers!"func _ready(): var y = my_func() # Function state saved in 'y'. print(y.resume("world")) # 'y' resumed and is now an invalid state.

将打印:

  1. Helloworldcheers!

yield 时,记住保存新的函数状态:

  1. func co_func(): for i in range(1, 5): print("Turn %d" % i) yield();func _ready(): var co = co_func(); while co is GDScriptFunctionState && co.is_valid(): co = co.resume();

协程&信号

yield 的真正优势在于与信号结合使用。yield 可以接受两个参数,一个对象和一个信号。收到信号后,将重新开始执行。这里有些例子:

  1. # Resume execution the next frame.yield(get_tree(), "idle_frame")# Resume execution when animation is done playing.yield(get_node("AnimationPlayer"), "animation_finished")# Wait 5 seconds, then resume execution.yield(get_tree().create_timer(5.0), "timeout")

completed 信号,例如:

  1. func my_func(): yield(button_func(), "completed") print("All buttons were pressed, hurray!")func button_func(): yield($Button0, "pressed") yield($Button1, "pressed")

my_func 仅在按下两个按钮后继续执行. 一旦一个信号被某个对象发出, 你还可以获取该信号的参数:

  1. # Wait for when any node is added to the scene tree.var node = yield(get_tree(), "node_added")

yield 返回含有这些参数的数组:

  1. signal done(input, processed)func process_input(input): print("Processing initialized") yield(get_tree(), "idle_frame") print("Waiting") yield(get_tree(), "idle_frame") emit_signal("done", input, "Processed " + input)func _ready(): process_input("Test") # Prints: Processing initialized var data = yield(self, "done") # Prints: waiting print(data[1]) # Prints: Processed Test

如果你不确定一个函数是否还会继续yield, 可以使用信号量completed, 来作为判断依据:

  1. func generate(): var result = rand_range(-1.0, 1.0) if result < 0.0: yield(get_tree(), "idle_frame") return resultfunc make(): var result = generate() if result is GDScriptFunctionState: # Still working. result = yield(result, "completed") return result

while 在这里是多余的, 因为仅当函数不再yield时才会发出 completed 信号.

Onready 关键字

Node._ready() 时才能获得子节点.

  1. var my_labelfunc _ready(): my_label = get_node("MyLabel")

onready,将成员变量的初始化推迟到调用 _ready()。它可以用一行替换上面的代码:

  1. onready var my_label = get_node("MyLabel")

Assert关键字

assert 关键字可用于检查调试版本中的条件. 在非调试版本中, 这些断言将被忽略. 这意味着在发布模式下导出的项目中不会评估作为参数传递的表达式. 因此, 断言必须 不能 包含具有副作用的表达式. 否则, 脚本的行为将取决于项目是否在调试版本中运行.

  1. # Check that 'i' is 0. If 'i' is not 0, an assertion error will occur.assert(i == 0)

从编辑器运行项目时, 如果发生断言错误, 该项目将被暂停.