信号

简介

信号是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), 您将看到该函数已在脚本中创建:

extends Node2D


func _on_Timer_timeout():
    pass # Replace with function body.
public class TimerExample : Node2D
{
    public 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));
    }
}

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

extends Node


signal my_signal(value, other_value)
public class Main : Node
{
    [Signal]
    public delegate void MySignal(bool value, int other_value);
}

注解

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

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

extends Node


signal my_signal(value, other_value)


func _ready():
    emit_signal("my_signal", true, 42)
public class Main : Node
{
    [Signal]
    public delegate void MySignal(bool value, int other_value);

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

总结

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

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