信号

简介

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

注解

您可以在 这里 阅读有关观察者模式的更多信息

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

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

计时器示例

To see how signals work, let’s try using a Timer node. Create a new scene with a Node2D and two children: a Timer and a Sprite. In the Scene dock, rename Node2D to TimerExample.

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

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

../../_images/signals_node_setup.png

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

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

../../_images/signals_node_tab_timer.png

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

../../_images/signals_connect_dialog_timer.png

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

警告

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

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

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

extends Node2D

func _on_Timer_timeout():
    pass # replace with function body
public class TimerExample : Node2D
{
    private void _on_Timer_timeout()
    {
        // Replace with function body.
    }
}

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

extends Node2D

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

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

用代码连接信号

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

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

../../_images/signals_disconnect_timer.png

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

extends Node2D

func _ready():
    $Timer.connect("timeout", self, "_on_Timer_timeout")

func _on_Timer_timeout():
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node2D
{
    public override void _Ready()
    {
        GetNode("Timer").Connect("timeout", this, nameof(_on_Timer_timeout));
    }

    public void _on_Timer_timeout()
    {
        var sprite = GetNode<Sprite>("Sprite");
        sprite.Visible = !sprite.Visible;
    }
}

自定义信号

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

extends Node2D

signal my_signal
public class Main : Node2D
{
    [Signal]
    public delegate void MySignal();
}

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

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

extends Node2D

signal my_signal

func _ready():
    emit_signal("my_signal")
public class Main : Node2D
{
    [Signal]
    public delegate void MySignal();

    public override void _Ready()
    {
        EmitSignal(nameof(MySignal));
    }
}

总结

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

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