【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/玩法/系统间通信会更清晰。
相关推荐
心之所向,自强不息21 小时前
# Unity MCP + Codex CLI 完整教程(Windows)
windows·unity·游戏引擎
KillJUMP1 天前
GODOT SHADER关键函数
游戏引擎·godot
Oiiouui1 天前
Godot(4.x): Python处理转换Excel为注入Json
游戏引擎·godot
追光者♂1 天前
【测评系列3】CSDN AI数字营销实测体验官:测试 开源项目——Superpowers 游戏引擎从零开发实战指南
人工智能·深度学习·机器学习·typescript·开源·游戏引擎·superpowers
UnicornDev1 天前
【Flutter x HarmonyOS 6】设置页面的UI设计
flutter·ui·华为·harmonyos·鸿蒙
元气少女小圆丶1 天前
SenseGlove Nova 2+Unity开发笔记3
笔记·unity·游戏引擎
Oiiouui1 天前
Godot(4.x): 游戏管理器: Excel 动态依赖注入实现
游戏·游戏引擎·godot
ZC跨境爬虫1 天前
跟着 MDN 学CSS day_31:(精通链接样式,从伪类到导航菜单)
前端·javascript·css·ui·交互
WMX10121 天前
Unity-shader学习记录
学习·unity·游戏引擎
WMX10121 天前
Hololens 2 上部署 Unity+MRTK 项目_模型着色
unity·游戏引擎·hololens