Community程式設計與開發github.com

ziva-sh/godot-signals

Godot 4 信號模式與其失敗案例 - 宣告型別信號、使用 Callable 進行 emit()、connect() 和 disconnect()、bind() 和 unbind()、lambda 表達式、CONNECT_ONE_SHOT 和 CONNECT_DEFERRED、await 信號與計時器、儲存在 .tscn 檔案中的編輯器連線,以及「向下呼叫、向上發訊」規則。適用於連接 UI 按鈕、計時器、Area2D 或 body 事件與自訂遊戲事件,取代 Godot 3 的 connect("signal", self, "method") 或 yield 程式碼,或除錯「already connected」錯誤時使用。

godot-signals 是什麼?

這個技能詳細說明了 Godot 4 中信號系統的正確使用方式與常見陷阱。Godot 4 將信號提升為一級公民 `Signal` 值,並以 `Callable` 取代舊有的方法字串參考,因此技能強調在所有情況下都應使用物件語法(例如 `button.pressed.connect(_on_pressed)`),讓解析器能及早發現拼寫錯誤。技能涵蓋了信號的宣告、發射與連接,以及進階功能如 `bind()` 與 `unbind()` 的參數綁定機制、`CONNECT_ONE_SHOT` 一次性連接與 `CONNECT_DEFERRED` 延遲連接的應用場景。此外,技能也詳細解釋了 `await` 關鍵字如何暫停協程直到信號觸發,並提醒開發者注意 `await` 不會超時的特性。最後,技能介紹了編輯器連線與程式碼連線的差異,並透過「向下呼叫、向上發訊」的模式,指導開發者如何建構可維護的節點通訊架構。此技能適用於任何 Godot 4 專案,特別是當需要理解信號底層機制、從 Godot 3 遷移程式碼或除錯連線問題時。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ziva-sh/godot-skills/tree/HEAD/skills/godot-signals

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Godot 4 中的信號

在 Godot 4 中,信號是第一級 Signal 值,而方法參考是 Callable。請全面使用物件語法(button.pressed.connect(_on_pressed))。這樣解析器會在編譯時拒絕拼錯的處理器、以及 self 上拼錯的信號;如果是其他型別節點上拼錯的信號,則在 unsafe_property_access 設定為錯誤時(參見 godot-4-api-pitfalls 技能)會被捕捉。字串形式(connect("pressed", ...)、emit_signal("died"))僅在執行階段檢查。

宣告、發射、連接

extends Node

signal health_changed(old_value: int, new_value: int)
signal died

var health := 100


func take_damage(amount: int) -> void:
	var old := health
	health = maxi(health - amount, 0)
	health_changed.emit(old, health)
	if health == 0:
		died.emit()
extends Control

@onready var start_button: Button = %StartButton
@onready var quit_button: Button = %QuitButton


func _ready() -> void:
	start_button.pressed.connect(_on_start_pressed)
	quit_button.pressed.connect(get_tree().quit)
	start_button.mouse_entered.connect(func() -> void: start_button.grab_focus())


func _on_start_pressed() -> void:
	get_tree().change_scene_to_file("res://level_1.tscn")

處理器必須接受信號發射的確切參數數量。參數不匹配不是解析錯誤;在發射時,Godot 會記錄 Error calling from signal 'x' to callable: '...': Method expected 0 argument(s), but called with 1. 並跳過該處理器。

bind() 與 unbind()

  • bind(a, b) 在信號自身參數之後附加額外參數。對於綁定了一個值的 pressed 處理器,它會接受一個參數;對於綁定了一個值的 health_changed(old, new) 處理器,它則接受三個參數,順序為 old, new, bound。
  • unbind(n) 會丟棄信號的最後 n 個參數,因此一個無參數的函式可以處理發送某些參數的信號。
extends HBoxContainer

signal slot_chosen(index: int)

var slot_buttons: Array[Button] = []


func _ready() -> void:
	for i in 4:
		var button := Button.new()
		button.text = "Slot %d" % (i + 1)
		add_child(button)
		slot_buttons.append(button)
		button.pressed.connect(_on_slot_pressed.bind(i))
	var timer := Timer.new()
	add_child(timer)
	timer.timeout.connect(_refresh)
	slot_chosen.connect(_refresh.unbind(1))
	timer.start(1.0)


func _on_slot_pressed(index: int) -> void:
	slot_chosen.emit(index)


func _refresh() -> void:
	for button in slot_buttons:
		button.disabled = false

斷開連線、重複連線與一次性連線

  • disconnect() 需要一個與已連線的 Callable 相等的 Callable。重新構建的 _handler.bind(1) 會相等並能正常工作。但 lambda 表達式不行:請將其保存在一個變數中,然後斷開該變數的連線。
  • 連接相同的 Callable 兩次會記錄 Signal 'x' is already connected to given callable ... 並保留一個連線。當程式碼可能執行兩次時(例如在重新加入場景樹的節點的 _ready() 中),請使用 is_connected() 進行防護。
  • connect(callable, CONNECT_ONE_SHOT) 在第一次發射後自動斷開連線。CONNECT_DEFERRED 會讓處理器在空閒時間執行。當物理回呼(例如 body_entered)改變物理狀態時,請使用它(或 set_deferred());直接改變會記錄 Can't change this state while flushing queries.。
  • 當物件被釋放時,對該物件的連線也會被移除。
extends Area2D

@onready var shape: CollisionShape2D = $CollisionShape2D
var _on_first_touch := func(body: Node2D) -> void: print(body.name, " touched first")


func _ready() -> void:
	body_entered.connect(_on_first_touch, CONNECT_ONE_SHOT)
	body_entered.connect(_close_after_entry, CONNECT_DEFERRED)
	if not body_entered.is_connected(_on_body_entered):
		body_entered.connect(_on_body_entered)


func _on_body_entered(body: Node2D) -> void:
	print(body.name, " entered")


func _close_after_entry(_body: Node2D) -> void:
	shape.disabled = true


func stop_listening() -> void:
	body_entered.disconnect(_on_body_entered)

await

await signal 會暫停函式,直到信號觸發並返回其參數:無參數時返回 null,一個參數時返回該值,兩個或更多參數時返回 Array。包含 await 的函式是協程;需要其結果的呼叫者也必須 await 它,否則它們會立即繼續執行。

extends Node2D

signal door_opened(door_name: String)


func _ready() -> void:
	await get_tree().create_timer(0.5).timeout
	var opened_by: String = await door_opened
	print("Door opened: ", opened_by)
	await play_intro()
	print("Intro finished")


func play_intro() -> void:
	var tween := create_tween()
	tween.tween_property(self, "modulate:a", 1.0, 1.0).from(0.0)
	await tween.finished

await 永遠不會超時。如果信號從未被發射(或其發射者先被釋放),函式只會停在那裡,不會產生任何錯誤。當需要備用路徑時,請使用 Timer 或一次性連線。

編輯器連線

在節點面板中建立的連線會儲存在場景檔案中,而非腳本中:

[connection signal="pressed" from="StartButton" to="." method="_on_start_button_pressed"]

如果方法在腳本中被重新命名或刪除,該連線仍會留在 .tscn 檔案中。在 Godot 4.7.2 中,發射一個連線方法不存在的信號不會列印任何錯誤,因此按鈕會直接失效。重新命名處理器後,請搜尋場景中的 method="old_name" 並更新它們,或在節點面板中重新連線。當腳本應該自包含時,請在程式碼中連線;當設計師需要配置場景時,請在編輯器中連線。避免對同一個信號和方法同時使用兩種方式:第二次連線會記錄「already connected」。

向下呼叫,向上發訊

節點對其子節點呼叫方法(因為它擁有它們且知道它們的存在),並發射信號讓其父節點或兄弟節點做出反應(它不應該知道誰在監聽)。父節點,或組合兩者的節點,負責進行連線:

extends Node2D

@onready var player: Node2D = $Player
@onready var health_bar: ProgressBar = $HUD/HealthBar


func _ready() -> void:
	player.connect(&"health_changed", _on_player_health_changed)


func _on_player_health_changed(_old_value: int, new_value: int) -> void:
	health_bar.value = new_value

這個父節點只知道 Player 是一個 Node2D,因此它將自訂信號名稱作為字串。為玩家腳本賦予一個 class_name Player,將變數型別設為 Player,並寫成 player.health_changed.connect(_on_player_health_changed),這樣解析器也會檢查名稱。

對於許多不相關場景都關心的事件(玩家死亡、關卡完成),常見的模式是使用一個僅宣告信號的自動載入腳本作為事件匯流排:Events.player_died.emit() 和 Events.player_died.connect(_on_player_died)。

要取代的 Godot 3 形式

Godot 3Godot 4
connect("pressed", self, "_on_pressed")pressed.connect(_on_pressed)
connect("pressed", self, "_on_pressed", [id])pressed.connect(_on_pressed.bind(id))
connect("timeout", self, "_on_timeout", [], CONNECT_ONESHOT)timeout.connect(_on_timeout, CONNECT_ONE_SHOT)
disconnect("pressed", self, "_on_pressed")pressed.disconnect(_on_pressed)
is_connected("pressed", self, "_on_pressed")pressed.is_connected(_on_pressed)
emit_signal("died")died.emit()
yield(obj, "signal_name")await obj.signal_name
funcref(obj, "method")Callable(obj, "method") 或 obj.method

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

ziva-sh/godot-4-api-pitfalls

Fixes Godot 3 APIs and GDScript syntax that AI assistants write into Godot 4 projects, such as KinematicBody2D, move_and_slide(velocity), export, onready, setget, yield, string connect(), instance(), rand_range, the Tween node, File, change_scene, Spatial, rect_* properties, Pool*Array, OS time functions and the old TileMap API. Use when writing, reviewing or porting GDScript for Godot 4.x, when a script fails with "not declared", "not found in base", "was removed in Godot 4" or "is not present

ziva-sh/godot-control-layout

Builds Godot 4 Control UIs that resize instead of clipping or overlapping - containers versus anchors, anchor presets from code, size flags, custom_minimum_size, MarginContainer margins, Label wrapping, mouse_filter, CanvasLayer HUDs, Godot 4 theme override names (add_theme_color_override and friends) and the display/window/stretch settings for multiple resolutions. Use when creating or fixing menus, HUDs, dialogs and inventory screens, when UI elements are cut off, zero-sized, overlapping, igno

ziva-sh/godot-scene-organization

Structures Godot 4 projects so scenes stay reusable and references do not break - when to make a scene, a script or a Resource, instancing with @export PackedScene, preload and load, @onready and %UniqueName node access, _ready order, groups, autoloads, class_name, and resource UIDs with the .uid files added in Godot 4.4, including how to move or rename files safely. Use when creating new scenes or scripts, deciding how nodes find each other, adding global state or an event bus, moving or renami

ziva-sh/godot-tilemaplayer

Correct Godot 4.3+ TileMapLayer code - set_cell(coords, source_id, atlas_coords), reading cells and custom data, erasing, coordinate conversion, terrains, tile collision through TileSet physics layers, finding the tile a body hit, and migrating from the deprecated TileMap node and its layer-index API. Use when placing, reading or erasing tiles from GDScript, building a TileSet in code, generating levels procedurally, handling tile collisions, or porting TileMap code from Godot 3 or Godot 4.0 to

相關技能