信号

简介

信号是Godot的 观察者 模式的版本.它们允许一个节点发出其他节点可以监听和响应的消息.例如,与其持续检查按钮是否被按下,不如在按下按钮时发出信号.

注解

您可以在这里http://gameprogrammingpatterns.com/observer.html阅读有关观察者模式的更多信息

信号是一种使游戏对象 解耦 的方法,从而可以使代码组织得更好,更易于管理.游戏对象可以发出所有感兴趣的对象可以订阅并响应的信号,而非强制游戏对象期望其他对象始终存在.

接下来,您可以看到一些有关如何在自己的项目中使用信号的示例.

计时器示例

为了看看信号是如何工作的,让我们尝试使用一个 Timer 节点.创建一个新的场景,有一个Node2D和两个子节点:一个定时器和一个 Sprite. 在Scene dock中,将Node2D重命名为TimerExample.

对于 Sprite 的纹理,可以使用Godot图标,或您喜欢的任何其他图像.为此,请在 SpriteTexture 属性下拉菜单中选择 Load.将脚本附加到根节点,但尚未添加任何代码.

您的场景树应该是这样的:

../../_images/signals_node_setup.png

Timer 节点的属性中,勾选 自动启动 旁边的选框.这会令计时器在您运行场景时自动启动.您可以将 等待时间 保留为1秒.

在 “属性检查器” 选项卡旁边是一个标记为 “节点” 的选项卡.单击此选项卡,您将看到所选节点可以发出的所有信号.对于 Timer 节点,我们关注的是”timeout()”.每当计时器到达 0 时,就会发出这个信号.

../../_images/signals_node_tab_timer.png

点击”timeout()”信号,然后点击界面底部的”连接…”按钮.您将看到如下窗口,您可以在其中定义如何连接信号:

../../_images/signals_connect_dialog_timer.png

在左侧,你将看到场景中的节点,并可以选择要”监听”信号的节点.请注意,Timer 节点为蓝色——意思是它是发出信号的节点.选择根节点.

警告

目标节点 必须 附加一个脚本,否则您将收到一条错误消息.

如果你切换高级菜单,你会在右侧看到,你可以绑定任意数量的不同类型的参数.当你有多个信号连接到同一个方法时,这将很有用,因为每个信号的传播,可能会附加不同值的调用参数.

窗口底部是一个标有”Receiver Method”的字段.这是您要使用的目标节点脚本中的函数名称.默认情况下,Godot将使用命名约定 _on_<node_name>_<signal_name> 创建此函数,但如果你想要修改它也可以.

单击 连接(Connect),您将看到该函数已在脚本中创建:

GDScript

C#

  1. extends Node2D
  2. func _on_Timer_timeout():
  3. pass # Replace with function body.
  1. public class TimerExample : Node2D
  2. {
  3. public void _on_Timer_timeout()
  4. {
  5. // Replace with function body.
  6. }
  7. }

现在,我们可以用接收信号时要运行的任何代码替换占位符代码.让我们让 Sprite 闪烁一下:

GDScript

C#

  1. extends Node2D
  2. func _on_Timer_timeout():
  3. # Note: the `$` operator is a shorthand for `get_node()`,
  4. # so `$Sprite` is equivalent to `get_node("Sprite")`.
  5. $Sprite.visible = !$Sprite.visible
  1. public class TimerExample : Node2D
  2. {
  3. public void _on_Timer_timeout()
  4. {
  5. var sprite = GetNode<Sprite>("Sprite");
  6. sprite.Visible = !sprite.Visible;
  7. }
  8. }

运行场景,您将看到 Sprite 每秒闪烁一次.您可以更改计时器的 等待时间 属性来更改此设置.

用代码连接信号

您还可以用在代码进行信号连接而不是用编辑器.当您通过代码实例化节点时,通常这是必需的,因为您无法使用编辑器进行连接.

首先,通过在 计时器 的”节点”选项卡中选择连接并点击断开来断开信号.

../../_images/signals_disconnect_timer.png

要使用代码进行连接,我们可以使用 connect 函数.将把它放在 _ready() 函数中,这样连接就会在运行时创建.函数的语法是 <source_node>.connect(<signal_name>, <target_node>, <target_function_name>).下面是我们的计时器连接的代码:

GDScript

C#

  1. extends Node2D
  2. func _ready():
  3. $Timer.connect("timeout", self, "_on_Timer_timeout")
  4. func _on_Timer_timeout():
  5. $Sprite.visible = !$Sprite.visible
  1. public class TimerExample : Node2D
  2. {
  3. public override void _Ready()
  4. {
  5. GetNode("Timer").Connect("timeout", this, nameof(_on_Timer_timeout));
  6. }
  7. public void _on_Timer_timeout()
  8. {
  9. var sprite = GetNode<Sprite>("Sprite");
  10. sprite.Visible = !sprite.Visible;
  11. }
  12. }

自定义信号

您还可以在Godot中声明自己的自定义信号:

GDScript

C#

  1. extends Node2D
  2. signal my_signal
  1. public class Main : Node2D
  2. {
  3. [Signal]
  4. public delegate void MySignal();
  5. }

声明后,您的自定义信号将出现在检查器中,并且可以按照与节点的内置信号相同的方式进行连接.

要通过代码发出信号,使用 emit_signal 函数:

GDScript

C#

  1. extends Node2D
  2. signal my_signal
  3. func _ready():
  4. emit_signal("my_signal")
  1. public class Main : Node2D
  2. {
  3. [Signal]
  4. public delegate void MySignal();
  5. public override void _Ready()
  6. {
  7. EmitSignal(nameof(MySignal));
  8. }
  9. }

信号还可以选择声明一个或多个参数.在括号之间指定参数名称:

GDScript

C#

  1. extends Node
  2. signal my_signal(value, other_value)
  1. public class Main : Node
  2. {
  3. [Signal]
  4. public delegate void MySignal(bool value, int other_value);
  5. }

注解

这些信号参数显示在编辑器的节点停靠面板中,Godot可以使用它们为您生成回调函数.但是,发出信号时仍然可以发出任意数量的参数;所以由你来决定是否发出正确的值.

要传递数值,请将数值作为第二个参数添加到 emit_signal 函数中:

GDScript

C#

  1. extends Node
  2. signal my_signal(value, other_value)
  3. func _ready():
  4. emit_signal("my_signal", true, 42)
  1. public class Main : Node
  2. {
  3. [Signal]
  4. public delegate void MySignal(bool value, int other_value);
  5. public override void _Ready()
  6. {
  7. EmitSignal(nameof(MySignal), true, 42);
  8. }
  9. }

总结

Godot的许多内置节点类型提供了可用于检测事件的信号.例如,代表硬币的 Area2D 在游戏角色的物理体进入碰撞形状时发出 body_entered 信号,让您知道游戏角色何时收集了它.

在下一节 您的第一个游戏 中,您将构建一个完整的游戏,其中包括使用多种信号来连接不同的游戏组件.