Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

使用信号

在本课中,我们将介绍信号。它们是节点在发生特定事件时发出的消息,例如按下按钮。其他节点可以连接到该信号,并在事件发生时调用函数。

信号是 Godot 内置的委派机制,允许一个游戏对象对另一个游戏对象的变化做出反应,而无需相互引用。使用信号可以限制耦合,并保持代码的灵活性。

例如,你可能在屏幕上有一个代表玩家生命值的生命条。当玩家受到伤害或使用治疗药水时,你希望生命条反映变化。要做到这一点,在 Godot 中,你会使用到信号。

备注

正如引言中提到的,信号是 Godot 版本的观察者模式。你可以在此处了解有关它的更多信息:https://gameprogrammingpatterns.com/observer.html

现在,我们将使用信号来使上一节课(监听玩家的输入)中的 Godot 图标移动,并通过按下按钮来停止。

场景设置

要为我们的游戏添加按钮,我们需要新建一个“主”场景,包含一个按钮以及之前课程 创建第一个脚本 编写的 sprite_2d.tscn 场景。

通过转到菜单“场景 -> 新建场景”来创建新场景。

../../_images/signals_01_new_scene.webp

在场景面板中,单击“2D 场景”按钮。这样就会添加一个 Node2D 作为我们的根节点。

../../_images/signals_02_2d_scene.webp

在文件系统面板中,单击之前保存的 sprite_2d.tscn 文件并将其拖动到 Node2D 上,对其进行实例化。

../../_images/signals_03_dragging_scene.png

我们想要添加另一个节点作为 Sprite2D 的同级节点。为此,请右键单击 Node2D,然后选择“添加子节点”。

../../_images/signals_04_add_child_node.webp

寻找并添加 Button 节点。

../../_images/signals_05_add_button.webp

该节点默认比较小。在视口中,点击并拖拽该按钮右下角的手柄来调整大小。

../../_images/signals_06_drag_button.png

如果看不到手柄,请确保工具栏中的选择工具处于活动状态。

../../_images/signals_07_select_tool.webp

点击并拖拽按钮使其更接近精灵。

你可以通过修改检查器中的 Text 属性来给 Button 上写一个标签。请输入Toggle motion

../../_images/signals_08_toggle_motion_text.webp

你的场景树和视口应该是类似这样的。

../../_images/signals_09_scene_setup.png

如果你还没保存场景的话,保存新建的场景为 node_2d.tscn。然后你就可以使用 F6`(macOS 则为 :kbd:`Cmd + R)来运行。此时,你可以看到按钮,但是按下之后不会有任何反应。

在编辑器中连接信号

然后,我们希望将按钮的“pressed”信号连接到我们的 Sprite2D,并且我们想要调用一个新函数来打开和关闭其运动。我们需要像我们在上一课中所做的操作一样,将一个脚本附加到 Sprite2D 节点。

你可以在“节点”面板中连接信号。选择 Button 节点,然后在编辑器的右侧,单击检查器旁边名为“节点”的选项卡。

../../_images/signals_10_node_dock.webp

停靠栏显示所选节点上可用的信号列表。

../../_images/signals_11_pressed_signals.webp

双击“pressed”信号,打开节点连接窗口。

../../_images/signals_12_node_connection.png

然后,你可以将信号连接到 Sprite2D 节点。该节点需要一个用于接收按钮信号的函数,当按钮发出信号时,Godot 将调用该函数。编辑器会为你生成一个。按照规范,我们将这些回调方法命名为"_on_node_name_signal_name"。在这里,它被命名为"_on_button_pressed"。

备注

通过编辑器的节点面板连接信号时,可以使用两种模式。简单的一个只允许你连接到附加了脚本的节点,并在它们上面创建一个新的回调函数。

../../_images/signals_advanced_connection_window.png

你可以在高级视图中连接到任何节点和任何内置函数、向回调添加参数、设置选项。你可以单击窗口右下角的“高级”按钮来切换模式。

单击“连接”按钮以完成信号连接并跳转到脚本工作区。你应该会看到新方法,并在左边距中带有连接图标。

../../_images/signals_13_signals_connection_icon.webp

如果单击该图标,将弹出一个窗口并显示有关连接的信息。此功能仅在编辑器中连接节点时可用。

../../_images/signals_14_signals_connection_info.webp

让我们用代码替换带有 pass 关键字的一行,以切换节点的运动。

我们的 Sprite2D 由于 _process() 函数中的代码而移动。Godot 提供了一种打开和关闭处理的方法:Node.set_process() 。Node 的另一个方法 is_processing() ,如果空闲处理处于活动状态,则返回 true。我们可以使用 not 关键字来反转该值。

func _on_button_pressed():
    set_process(not is_processing())

此函数将切换处理,进而切换按下按钮时图标的移动。

在尝试游戏之前,我们需要简化 _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 上则是 Ctrl + Cmd + 1)。

在“场景”面板中,右键点击 Sprite2D 节点并添加新的子节点。搜索 Timer 并添加对应节点。你的场景现在应该类似这样。

../../_images/signals_15_scene_tree.png

选中 Timer 节点,在“检查器”中勾选 Autostart 属性。

../../_images/signals_18_timer_autostart.png

点击 Sprite2D 旁的脚本图标,返回脚本工作区。

../../_images/signals_16_click_script.png

我们需要执行两个操作来通过代码连接节点:

  1. 从 Sprite2D 获取 Timer 的引用。

  2. 通过 Timer 的“timeout”信号调用 connect() 方法。

备注

要使用代码来连接信号,你需要调用所需监听节点信号的 connect() 方法。这里我们要监听的是 Timer 的“timeout”信号。

我们想要在场景实例化时连接信号,我们可以使用 Node._ready() 内置函数来实现这一点,当节点完全实例化时,引擎会自动调用该函数。

为了获取相对于当前节点的引用,我们使用方法 Node.get_node()。我们可以将引用存储在变量中。

func _ready():
    var timer = get_node("Timer")

get_node() 函数会查看 Sprite2D 的子节点,并按节点的名称获取节点。例如,如果在编辑器中将 Timer 节点重命名为“BlinkingTimer”,则必须将调用更改为 get_node("BlinkingTimer")

现在,我们可以在 _ready() 函数中将Timer连接到Sprite2D。

</