Using signals — Godot Engine (stable) documentation in English
在本课中,我们将研究信号。它们是节点在发生特定事件时发出的消息,例如按下按钮。其他节点可以连接到该信号并在事件发生时调用函数。
信号是 Godot 中内置的一种委托机制,它允许一个游戏对象对另一个游戏对象的变化做出反应,而无需它们相互引用。使用信号限制耦合并保持代码的灵活性。
例如,您可能在屏幕上有一个代表玩家生命值的生命条。当玩家受到伤害或使用治疗药水时,您希望条形图反映这种变化。为此,在 Godot 中,您将使用信号。
注:正如介绍中提到的,信号是 Godot 版本的观察者模式。您可以在这里了解更多信息: https: //gameprogrammingpatterns.com/observer.html
现在,我们将使用一个信号让上一课(监听玩家输入)中的 Godot 图标通过按下按钮移动和停止。
为了向我们的游戏添加一个按钮,我们将创建一个新的主场景,其中将包含一个按钮和我们在之前课程中编写的场景Sprite2D.tscn
。
通过转到菜单 Scene -> New Scene 创建一个新场景。
在场景停靠栏中,单击 2D 场景按钮。这将添加一个 Node2D 作为我们的根。
在 FileSystem 停靠栏中,单击您之前保存的文件Sprite2D.tscn
并将其拖动到 Node2D 上以对其进行实例化。
我们想添加另一个节点作为 Sprite2D 的兄弟节点。为此,右键单击 Node2D 并选择添加子节点。
搜索 Button 节点类型并添加它。
默认情况下,该节点很小。单击并拖动视口中按钮右下角的手柄以调整其大小。
如果您没有看到手柄,请确保选择工具在工具栏中处于活动状态。
单击并拖动按钮本身,使其更靠近精灵。
您还可以通过在 Inspector 中编辑其 Text 属性来在 Button 上写一个标签。输入“切换动作”【Toggle motion】。
您的场景树和视口应如下所示。
保存新创建的场景。然后您可以使用F6(在 macOS 上Cmd + R) 运行它。此刻,该按钮将可见,但如果您按下它,目前不会发生任何事情。
在这里,我们想要将按钮的“按下”信号连接到我们的 Sprite2D,并且我们想要调用一个新函数来打开和关闭它的运动。我们需要将脚本附加到 Sprite2D 节点,这是我们在上一课中所做的。
您可以在 Node dock 中连接信号。选择 Button 节点,然后在编辑器的右侧,单击 Inspector 旁边名为“Node”的选项卡。
停靠栏显示所选节点上可用的信号列表。
双击“按下”【pressed】信号打开节点连接窗口。
在那里,您可以将信号连接到 Sprite2D 节点。该节点需要一个接收方方法,即当 Button 发出信号时 Godot 将调用的函数。编辑器为您生成一个。按照惯例,我们将这些回调方法命名为“_on_node_name_signal_name”。在这里,【可以命名为】“_on_button_pressed”。
注:当通过编辑器的 Node dock 连接信号时,您可以使用两种模式。简单的只允许您连接到附加有脚本的节点,并在它们上创建一个新的回调函数【上图】。
高级视图允许您连接到任何节点和任何内置函数,向回调添加参数,并设置选项。您可以通过单击在窗口右下角“高级【Advanced】”按钮切换模式。
点击Connect按钮完成信号连接,跳转到Script工作区。您应该会在左边距中看到带有连接图标的新方法。
如果单击该图标,将弹出一个窗口并显示有关连接的信息。此功能仅在编辑器中连接节点时可用。
让我们用切换节点运动的代码替换带有关键字pass
的行。
我们的 Sprite2D 在_process()
函数中移动了。Godot 提供了一种打开和关闭【_process函数】处理的方法:Node.set_process()。Node 类的另一个方法, 如果当前处于空闲状态,is_processing()
则返回true
。我们可以使用not
关键字来取得其反转值。
func _on_button_pressed(): set_process(not is_processing())
此功能将切换处理【__process】,进而在按下按钮时打开和关闭图标的动作。
在尝试游戏之前,我们需要简化我们的_process()
功能以自动移动节点而不是等待用户输入。将其替换为我们在两节课前看到的以下代码:
func _process(delta): rotation += angular_speed * delta var velocity = Vector2.UP.rotated(rotation) * speed position += velocity * delta
您的完整sprite_2d.gd
代码应如下所示。
extends Sprite2D var speed = 400 var angular_speed = PI func _process(delta): rotation += angular_speed * delta var velocity = Vector2.UP.rotated(rotation) * speed position += velocity * delta func _on_button_pressed(): set_process(not is_processing())
现在运行场景并单击按钮以查看精灵开始和停止。
您可以通过代码而不是使用编辑器连接信号。当您在脚本中创建节点或实例化场景时,这是必需的。
让我们在这里使用不同的节点。Godot 有一个Timer节点,可用于实现技能冷却时间、武器重新加载等。
返回 2D 工作区。您可以单击窗口顶部的“2D”文本或按Ctrl + F1(在 macOS 上Alt + 1)。
在场景停靠栏中,右键单击 Sprite2D 节点并添加一个新的子节点。搜索 Timer 并添加相应的节点。您的场景现在应该如下所示。
选择 Timer 节点后,转到 Inspector 并启用Autostart 属性。
单击 Sprite2D 旁边的脚本图标以跳回脚本工作区。
我们需要做两个操作来通过代码连接节点:
从 Sprite2D 获取对计时器的引用。
connect()
在定时器的“超时”信号上调用该方法。
注:要通过代码连接到信号,您需要调用connect()
要收听的信号的方法。在这种情况下,我们要监听定时器的“超时”信号。
我们希望在实例化场景时连接信号,我们可以使用Node._ready()内置函数来实现,该函数在节点完全实例化时由引擎自动调用。
要获取相对于当前节点的节点引用,我们使用方法 Node.get_node()。我们可以将引用存储在变量中。
func _ready(): var timer = get_node("Timer")
该函数get_node()
查看 Sprite2D 的子节点并通过名称获取节点。例如,如果您在编辑器中将 Timer 节点重命名为“BlinkingTimer”,则必须将调用更改为get_node("BlinkingTimer")
.
我们现在可以在函数中将 Timer 连接到 Sprite2D _ready()
。
func _ready(): var timer = get_node("Timer") timer.timeout.connect(_on_timer_timeout)
该行内容如下:我们将定时器的“超时”信号连接到脚本所附加的节点。当 Timer 发出时timeout
,我们要调用函数_on_timer_timeout(),该函数是
我们目前需要定义的函数。让我们将它添加到脚本的底部并使用它来切换精灵的可见性。
注:按照惯例,我们将这些回调方法在 GDScript 中命名为“_on_node_name_signal_name”,在 C# 中命名为“OnNodeNameSignalName”。在这里,GDScript 为“_on_timer_timeout”,C# 为 OnTimerTimeout()。
func _on_timer_timeout(): visible = not visible
该visible
属性是一个布尔值,用于控制节点的可见性。该行visible = not visible
切换值visible
。如果是true
,则变为false
,反之亦然。
如果现在运行场景,您会看到精灵以一秒的间隔闪烁。
这就是我们移动和闪烁的 Godot 图标小演示!这是完整的sprite_2d.gd
文件以供参考。
extends Sprite2D var speed = 400 var angular_speed = PI func _ready(): var timer = get_node("Timer") timer.timeout.connect(_on_timer_timeout) func _process(delta): rotation += angular_speed * delta var velocity = Vector2.UP.rotated(rotation) * speed position += velocity * delta func _on_button_pressed(): set_process(not is_processing()) func _on_timer_timeout(): visible = not visible
注:本节是关于如何定义和使用您自己的信号的参考,而不是建立在之前课程中创建的项目的基础上。
您可以在脚本中定义自定义信号。例如,当玩家的生命值达到零时,您想要显示游戏结束屏幕。为此,您可以定义一个名为“死亡”或“健康耗尽”的信号,当它们的健康状况达到 0 时。
extends Node2D signal health_depleted var health = 10
注:由于信号代表刚刚发生的事件,我们通常在其名称中使用过去式的动作动词。
您的信号与内置信号的工作方式相同:它们出现在“节点”选项卡中,您可以像连接任何其他信号一样连接到它们。
要在脚本中发出信号,请调用emit()
信号。
func take_damage(amount): health -= amount if health <= 0: health_depleted.emit()
信号可以选择性地声明一个或多个参数。在括号之间指定参数名称:
extends Node signal health_changed(old_value, new_value) var health = 10
注:信号参数显示在编辑器的节点停靠栏中,Godot 可以使用它们为您生成回调函数。但是,您仍然可以在发出信号时发出任意数量的参数。所以由你来发出正确的值。
要随信号发出值,请将它们作为额外参数添加到函数中 emit()
:
func take_damage(amount): var old_health = health health -= amount health_changed.emit(old_health, health)
Godot 中的任何节点都会在特定事件发生时发出信号,例如按下按钮。其他节点可以连接到单独的信号并对选定的事件出反应 。
信号有很多用途。有了它们,您可以对进入或退出游戏世界的节点、碰撞、角色进入或离开区域、界面元素大小变化等等做出反应。
例如,每当玩家的物理身体进入其碰撞形状时,表示硬币 的Area2Dbody_entered
就会发出信号,让您知道玩家何时收集了它。
在下一节“您的第一个 2D 游戏”中,您将创建一个完整的 2D 游戏并将到目前为止所学的一切付诸实践。
【DrGraph】:信号类似于Windows应用程序中的消息,而且更为简单。从Godot的设计思路来看,信号只是将本对象(节点、脚本、场景...)的某些消息暴露给本对象的父对象调用,这些消息是本对象的一些业务逻辑时机(时刻),若父对象绑定了这些信号,则在本对象的相应业务逻辑时刻会调用父对象的相应函数。因为是父对象内部调用,如果修改了父对象的一些属性等,就可以影响父对象的业务逻辑。