用途
本技能提供 Godot 引擎中信号驱动架构的专家级蓝图,围绕“Signal Up, Call Down”(信号向上、调用向下)模式实现节点间的松耦合。适用于实现事件系统或解耦节点,关键词包括 signal、emit、connect、CONNECT_ONE_SHOT、CONNECT_REFERENCE_COUNTED、event bus、AutoLoad、decoupling。
核心原则
- 子节点 → 父节点:使用过去时信号(如 health_changed、died)。
- 父节点 → 子节点:使用直接调用或属性(如 apply_damage、play_anim)。
- 兄弟节点:通过父级中介或谨慎限定范围的 Autoload 总线,绝不使用兄弟硬引用。
信号适用于:UI 按下、死亡 → 游戏结束、拾取 → 库存、跨场景总线事件。直接调用适用于:父级命令子级、本地属性访问。
禁止事项(NEVER Do)
- 绝不使用遗留的基于字符串的 Object.connect()——拼写错误会导致静默失败。始终使用 signal.connect(_callback) 以获得编译期校验。
- 绝不使用信号自上而下地规定行为——信号是过去时事件(如“died”)。命令(如“kill”)应使用直接方法调用。
- 绝不将同一信号连接到同一 Callable 两次——除非使用 Object.CONNECT_REFERENCE_COUNTED 标志堆叠连接,否则运行时会抛出 ERR_INVALID_PARAMETER。
- 绝不为本地数据使用全局信号总线——会污染全局状态并增加调试难度。场景特定逻辑应使用本地连接。
- 绝不假设回调必须接受所有信号参数——使用 unbind() 丢弃不需要的参数以保持 API 整洁。
- 绝不创建循环信号依赖——A 信号 B,B 又信号回 A?使用中介(父级或 AutoLoad)打破循环。
- 绝不跳过信号类型标注——signal moved 不带类型会缺少编辑器支持。始终使用 signal moved(dir: Vector2)。
- 绝不忘记断开动态信号——幽灵连接会导致“call on null instance”错误。在 _exit_tree() 中或重定向时断开。
- 绝不在发射器上发射带即时副作用的信号——如果 died.emit() 调用 queue_free(),监听者可能无法响应。应先发射。
- 绝不为高频数据流使用信号——每秒发送 1000+ 信号(如逐粒子更新)效率低下。应使用共享数组或直接缓冲区。
连接位置决策树
| 范围 | 模式 | 强制脚本 |
|---|---|---|
| 子节点通知父节点 / UI | 父节点 _ready 中的本地 signal.connect | signal_up_call_down_pattern.gd |
| 父节点编排子节点 | 向下方法调用(非信号) | 同上 |
| 跨场景 / 系统(成就、存档) | Autoload 总线 | global_signal_bus_router.gd / global_event_bus.gd |
| 线性异步步骤(加载 → 淡出 → 生成) | await 信号序列 | await_signal_sequencing.gd / complex_signal_sequencer.gd |
| 重定向跟踪(新敌人) | 先断开旧的 | disconnect_ghost_signals.gd |
| 一次性 / 物理安全 | CONNECT_ONE_SHOT / CONNECT_DEFERRED | one_shot_deferred_connections.gd |
| 额外上下文 / 丢弃参数 | Callable.bind / unbind | callable_bind_context.gd / unbind_unwanted_args.gd |
可用脚本
- signal_up_call_down_pattern.gd——层级接线前必读。
- global_signal_bus_router.gd / global_event_bus.gd——Autoload 总线前必读。
- disconnect_ghost_signals.gd——切换被跟踪发射器时必读。
- await_signal_sequencing.gd / complex_signal_sequencer.gd——多步 await 时必读。
- safe_dynamic_connections.gd——is_connected 守卫。
- one_shot_deferred_connections.gd——一次性 / 延迟标志。
- callable_bind_context.gd / unbind_unwanted_args.gd——bind/unbind。
- track_signal_emitter_source.gd——CONNECT_APPEND_SOURCE_OBJECT。
- signal_debugger.gd / signal_spy.gd——调试 / 测试间谍。
Lambda 捕获清理
Godot 在节点释放时会自动断开大多数连接。例外:捕获局部变量的 lambda——必须手动断开。
var my_lambda: Callable
func _ready() -> void:
var x := 10
my_lambda = func(): print(x)
player.died.connect(my_lambda)
func _exit_tree() -> void:
if player and player.died.is_connected(my_lambda):
player.died.disconnect(my_lambda)
重定向时优先使用命名方法或 disconnect_ghost_signals.gd。
CONNECT_REFERENCE_COUNTED 的正确语义
CONNECT_REFERENCE_COUNTED 表示多次相同连接共享一个带引用计数的连接(连接 N 次 / 断开 N 次)。它并非“发射器释放时自动清理”,也不能修复捕获 lambda 的泄漏。
- 释放时自动清理:对 Object 方法的普通连接(非捕获)在任一方释放时被清除。
- 捕获 lambda:始终手动 disconnect(见上)。
- 发射后自动移除:CONNECT_ONE_SHOT。
深入配方(按需)
模式 1–7 及注意事项见 references/implementation-patterns.md。
参考
官方文档
- Using signals——核心 emit/connect 模型,以及信号为何能在无硬引用下解耦节点。
- Scene organization——规范的“signal up, call down”所有权规则,保持父→子命令流明确。
- Instancing with signals——从生成的场景发射,使父级/管理器无需固定节点路径即可接收子弹、战利品等产物。
- Autoloads versus regular nodes——何时全局 EventBus 合理,何时场景本地信号接线更安全。
- Singletons (Autoload)——如何注册跨场景存活的类型化信号总线。
- Signal——类型化 Signal API:emit、connect、is_connected 及断开辅助方法。
- Callable——bind() / unbind() 用于注入或丢弃回调上下文,无需包装 lambda。
- Object——CONNECT_ONE_SHOT、CONNECT_DEFERRED、CONNECT_REFERENCE_COUNTED 和 CONNECT_APPEND_SOURCE_OBJECT 标志。
- GDScript basics——类型化 signal 声明及对信号 await 实现线性异步序列。
- Using SceneTree——连接在进入/退出树时的生命周期,以及动态监听器重定向时为何必须断开。
- Godot notifications——相对于 _ready、父级缓存和用户信号的安全连接时机。
- Idle and Physics Processing——当回调在物理步中修改物理体时,延迟信号处理器为何重要。
相关技能
前置:godot-project-foundations、godot-gdscript-mastery、godot-autoload-architecture。
互补:godot-composition、godot-scene-management、godot-state-machine-advanced、godot-resource-data-patterns、godot-testing-patterns、godot-ui-containers。
下游/消费者:godot-dialogue-system、godot-ability-system、godot-combat-system、godot-performance-optimization。
大师:godot-master。