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 中,你會使用到訊號。

備註

欲瞭解更多有關觀察者模式的資訊,請參考: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 ,命名為 StartButton

../../_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。

func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)

該行讀起來是這樣的:我們將計時器的“timeout”訊號連接到腳本附加到的節點上。當計時器發出“timeout”時,去呼叫我們需要定義的函式``_on_timer_timeout()``。讓我們將其定義新增到腳本的底部,並使用它來切換 sprite 的可見性。

備註

按照慣例,我們將這些回呼函式方法在 GDScript 中命名為“_on_node_name_signal_name”,在 C# 中命名為“OnNodeNameSignalName”。故此處的GDScript 為“_on_timer_timeout”,C# 為“OnTimerTimeout()”。

func _on_timer_timeout():
    visible = not visible

visible 屬性是一個布林值,用於控制節點的可見性。 visible = not visible 行切換該值。如果 visibletrue ,它就會變成 false ,反之亦然。

如果你現在運作場景,就會看到精靈在閃啊閃的,間隔為一秒。

GDScript 範例

這就是我們小小的 Godot 圖示移動閃爍演示了!這是完整的 sprite_2d.gd 檔案,僅供參考。

extends Sprite2D

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)


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())


func _on_timer_timeout():
    visible = not visible

自定訊號

備註

本節介紹的是如何定義並使用你自己的訊號,不依賴之前課程所建立的專案。

您可以在腳本中定義自訂訊號。例如,假設您希望在玩家的生命值為零時通過螢幕顯示遊戲結束。為此,當他們的生命值達到 0 時,您可以定義一個名為“died”或“health_depleted”的訊號。

extends Node2D

signal health_depleted

var health = 10

備註

由於訊號表示剛剛發生的事件,我們通常在其名稱中使用過去時態的動作動詞。

自訂訊號的工作方式與內建訊號相同:它們顯示在“節點”分頁中,您可以像連接其他訊號一樣連接到它們。

../../_images/signals_17_custom_signal.png

要在腳本中發出信號,需呼叫信號的 emit() 方法。

func take_damage(amount):
    health -= amount
    if health <= 0:
        health_depleted.emit()

訊號也可以宣告一個或多個參數。在括號中指定參數的名稱:

extends Node

signal health_changed(old_value, new_value)

var health = 10

備註

訊號的參數會顯示在編輯器的節點 Dock 中。Godot 會使用訊號參數來產生回呼函式。但送出訊號時一樣可以送出任意數量的參數,可自行決定是否要送出正確的數量。

若需要傳遞數值,可將數值放在 emit_signal 函式的第二個參數內:

func take_damage(amount):
    var old_health = health
    health -= amount
    health_changed.emit(old_health, health)

總結

Godot 中的任何節點都會在發生特定事件時發出訊號,例如按下按鈕。其他節點可以連接到單個訊號並對所選事件做出反應。

訊號有很多用途。有了它們,你可以對進入或退出遊戲世界的節點、碰撞、角色進入或離開某個區域、介面元素的大小變化等等做出反應。

許多 Godot 內建的型別都提供各種訊號可用來偵測事件。例如,當金幣 Area2D 物件送出 body_entered 訊號,就表示玩家物理形體進入了金幣的碰撞區域,代表玩家蒐集到金幣。

在下一個章節 doc_your_first_game 中,我們將製作一個完整的遊戲,其中會用到訊號來連接各個不同的遊戲元件。