Community编程与开发github.com

ziva-sh/godot-signals

Godot 4 信号模式及其失败场景——声明类型化信号、emit()、connect() 和 disconnect() 配合 Callable、bind() 和 unbind()、lambda、CONNECT_ONE_SHOT 和 CONNECT_DEFERRED、对信号和计时器的 await、编辑器连接(存储在 .tscn 文件中)以及“向下调用,向上信号”规则。适用于连接 UI 按钮、计时器、Area2D 或 body 事件及自定义游戏事件;替换 Godot 3 的 connect("signal", self, "method") 或 yield 代码;或调试“已连接”、

godot-signals 是什么?

该技能(Roo Code / Cline 兼容)详细介绍了 Godot 4 中信号系统的正确使用方法和常见陷阱。它涵盖了从声明和发射类型化信号,到使用 Callable 对象进行连接和断开连接的全流程,并深入讲解了 bind()、unbind()、lambda、CONNECT_ONE_SHOT 和 CONNECT_DEFERRED 等高级功能。此外,技能还包含 await 在协程和计时器中的最佳实践、编辑器连接与代码连接的差异(及其在 .tscn 文件中的存储方式),以及“向下调用,向上信号”的架构规则,并提供了 Godot 3 到 Godot 4 的迁移对照表。对于正在将项目从 Godot 3 迁移到 Godot 4 的开发者,或希望全面掌握 Godot 4 信号系统以避免常见运行时错误的开发者来说,这是非常实用的参考。

兼容平台~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" 并更新它们,或者在节点面板中重新连接。当脚本应保持自包含时,在代码中连接;当设计师连接场景时,在编辑器中连接。避免对同一个信号和方法同时进行两种操作:第二次连接会记录“已连接”。

向下调用,向上信号

节点调用其子节点的方法(它拥有它们并且知道它们存在),并发射信号供其父节点或兄弟节点响应(它不应知道谁在监听)。父节点,或组合两者的节点,进行连接:

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

相关技能