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 节点,可用于实现技能冷却时间、武器重装等。

Head back to the 2D workspace. You can either click the "2D" text at the top of the window or press Ctrl + F1 (Ctrl + Cmd + 1 on macOS).

在“场景”面板中,右键点击 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”信号。

We want to connect the signal when the scene is instantiated, and we can do that using the Node._ready() built-in function, which is called automatically by the engine when a node is fully instantiated.

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

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

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

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