【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/玩法/系统间通信会更清晰。
相关推荐
李高钢1 天前
【WPF】高级 UI 与性能优化实战:从卡顿到丝滑
ui·性能优化·wpf
MOONICK1 天前
QT的UI开发框架
qt·ui
郝学胜-神的一滴1 天前
Horse3D 游戏引擎研发笔记(七):Clydesdale——从流式日志到多输出订阅
c++·qt·unity·游戏引擎·图形渲染·unreal engine·opengl
江上闲人.2 天前
Wardogs战犬\战狗Beta测试启动报错UE崩溃卡顿?解决方法来了
游戏·游戏引擎·虚幻·战犬·战狗·wardogs
界面开发小八哥2 天前
界面控件DevExpress Blazor v26.1新版亮点 - Filter Builder等功能升级
ui·界面控件·blazor·devexpress·ui开发
兰亭妙微UI设计公司2 天前
兰亭妙微UI设计:Neemo Project 企业AI项目管理后台全案运营价值解析
人工智能·ui
杨丰玮4182 天前
从零手写Java飞机躲障碍游戏|Swing绘图、鼠标跟随、计时器碰撞检测实战(五)
java·python·游戏·游戏引擎·图形渲染·动画·贴图
鼎艺创新科技2 天前
不依赖 UE/Unity:我们如何从零搭建一套国产三维 GIS 渲染引擎
人工智能·算法·unity·游戏引擎·三维电子沙盘
huihui3613 天前
校友网管理软件技术选型框架:基于功能核验与系统架构的量化评估
unity·系统架构·游戏引擎