【Godot】【入门】信号系统从 0 到 1(UI/玩法彻底解耦的通用写法)

信号是 Godot 的"事件总线",用好它可以让 UI 与玩法解耦、模块复用。本文讲清概念、图形化连接 vs 代码连接、全局信号封装,并附常见坑与最佳实践(精力有限,附件后面慢慢补)。

信号是什么

  • 类似 C# 事件/UnityEvent:发送者发射,接收者监听,双方无需相互引用具体类型。
  • 任意节点都可以定义/发射信号,参数自定义。

定义与发射

gdscript 复制代码
signal hit(damage: int, from)

func _on_body_entered(body):
    emit_signal("hit", 10, body)
  • 信号定义放在脚本顶部,emit_signal 传参顺序与定义一致。

两种连接方式

图形化(适合固定场景)

  1. 选中信号源节点 → Node 面板 → Signals → 双击信号。
  2. 选择接收节点(通常场景根),确认后 Godot 生成回调函数并连接。
  • 优点:快、可视化;缺点:动态实例化时容易忘记保存连接。

代码连接(推荐通用写法)

gdscript 复制代码
func _ready():
    $Button.pressed.connect(_on_btn_pressed)

func _on_btn_pressed():
    print("clicked")
  • 动态实例:
gdscript 复制代码
var enemy = EnemyScene.instantiate()
enemy.hit.connect(_on_enemy_hit)
add_child(enemy)
  • 优点:连接逻辑集中、可条件化,便于重构。

全局信号总线(解耦模块)

  • 在 Autoload 添加 SignalsBus.gd
gdscript 复制代码
extends Node
signal player_died
signal coin_collected(amount)
  • 发射:SignalsBus.emit_signal("coin_collected", 1)
  • 监听:SignalsBus.coin_collected.connect(_on_coin)
  • 用途:UI/玩法互不引用,依靠总线通信;多人项目可统一管理事件名。

典型场景示例

UI 按钮 → 游戏逻辑

gdscript 复制代码
# Menu.gd
func _ready():
    $PlayButton.pressed.connect(_on_play)
func _on_play():
    SignalsBus.emit_signal("start_game")
gdscript 复制代码
# GameController.gd
func _ready():
    SignalsBus.start_game.connect(_start)

区域触发器 → 计分

gdscript 复制代码
# Coin.gd
signal collected(value)
func _on_body_entered(body):
    if body.is_in_group("player"):
        emit_signal("collected", 1)
        queue_free()
gdscript 复制代码
# Level.gd
func _ready():
    for coin in $Coins.get_children():
        coin.collected.connect(_on_coin)

信号管理的最佳实践

  • 命名 :用动词过去式或事件名:hit, died, collected, finished
  • 参数少而准:只传接收方需要的数据,避免传整个节点引用(或传引用但注明只读)。
  • 连接位置统一 :集中在 _ready 或初始化函数,便于搜索和断开。
  • 断开连接 :长生命周期节点监听短生命周期对象时,记得在对象销毁前 disconnect 或在 _exit_tree 断开。
  • 避免多次连接 :先检查 is_connected 或确保连接代码只执行一次。

常见坑

  • 未保存连接:编辑器连完信号没保存场景,运行时无回调;改用脚本连接或养成保存习惯。
  • 重复回调 :同一信号多次连接同一函数导致执行多次,排查 connect 是否在循环内。
  • 跨场景引用 null :监听的节点被销毁,回调报 null。使用 weakref 或在 disconnect 后再 free。
  • Autoload 信号未注册:忘记在项目设置里勾选 Autoload,导致发射无监听。

调试技巧

  • 临时打印:在回调里 print("signal hit", value) 确认触发。
  • is_connected 检查:
gdscript 复制代码
if not source.hit.is_connected(_on_hit):
    source.hit.connect(_on_hit)
  • 编辑器 Signal 面板有"Go to Function"可快速跳到生成的回调。

进阶:一次性/延迟信号

  • 一次性:
gdscript 复制代码
var conn = button.pressed.connect(func():
    print("once")
    button.pressed.disconnect(conn)
)
  • 延迟发射:call_deferred("emit_signal", "hit", 10) 避免当前帧状态冲突。

总结复盘

  • 信号=解耦:源的一端发射、接收一端监听,互不依赖具体实现。
  • 固定场景可用图形化连接,动态/可复用模块优先代码连接;全局事件用 Autoload 总线。
  • 关注连接时机、重复连接与断开,避免"回调触发两次"和 null 报错。掌握这些,UI/玩法/系统间通信会更清晰。
相关推荐
子春一8 小时前
Flutter for OpenHarmony: 从颜色模型到可访问性:一个 Flutter 高对比度 UI 的完整实践
flutter·ui
Howrun7778 小时前
虚幻引擎_核心框架
游戏引擎·虚幻
雨季66610 小时前
Flutter 三端应用实战:OpenHarmony 简易数字累加器开发指南
开发语言·flutter·ui·ecmascript
●VON10 小时前
Flutter for OpenHarmony:基于 SharedPreferences 的本地化笔记应用架构与实现
笔记·学习·flutter·ui·架构·openharmony·von
ujainu11 小时前
Flutter + OpenHarmony实现高保真闹钟 App:从 UI 设计到实时触发机制全解析
flutter·ui
晚霞的不甘12 小时前
Flutter for OpenHarmony《智慧字典》 App 底部导航栏深度解析:构建多页面应用的核心骨架
前端·经验分享·flutter·ui·前端框架·知识图谱
子春一12 小时前
Flutter for OpenHarmony:构建一个交互式 Flutter RGB 颜色选择器,深入解析状态驱动 UI、HEX 转换与无障碍色彩对比
flutter·ui
GLDbalala12 小时前
Unity 实现一个简单的构建机
unity·游戏引擎
雨季66612 小时前
Flutter 三端应用实战:OpenHarmony 简易文本首字母提取器开发指南
flutter·ui·dart
集成显卡21 小时前
Lucide Icons:一套现代、轻量且可定制的 SVG 图标库
前端·ui·图标库·lucide