信号:让对象通知,而不是接管
按钮只报告“被按下”,由接收者决定接下来发生什么。
01直接调用与信号的区别
直接调用适合“我明确要求你做某件事”;信号适合“我发生了一件事,关心它的对象可以响应”。一个调查物件可以发出 inspected,UI 显示文字,房间流程记录一次调查。物件本身不必知道所有接收者的节点路径。信号不是后台线程,也不会自动保存状态。
02交互图解:发出与接收
点击按钮观察计数和日志。这个模拟器用网页脚本解释信息流,没有执行 GDScript。真正的项目里还要保证连接存在、签名匹配,并防止重复连接。
03最小可运行练习
extends Control
@onready var test_button: Button = $TestButton
@onready var result_label: Label = $ResultLabel
var press_count: int = 0
func _ready() -> void:
test_button.pressed.connect(_on_test_pressed)
result_label.text = "还没有按下"
func _on_test_pressed() -> void:
press_count += 1
result_label.text = "已按下 %d 次" % press_count- 建立 Control 根节点,添加名为 TestButton 的 Button 和 ResultLabel 的 Label。
- 分别设置位置,避免按钮与标签重叠;给按钮填写文字。
- 把脚本挂在根节点,只用代码连接,不再在编辑器重复连接同一回调。
- F6 后按三次按钮,确认计数恰好加三。
04如何排查“按了没有反应”
先确认按钮实际接收到输入,再检查信号是否连接,随后在回调中打断点。若一个点击加了两次,查重复连接或多份 UI 实例。若回调已经进入但画面没变,检查你更新的是不是屏幕上那一个 Label。把整条链分段检查,比整份重写快。
05给一条通知找两个不同的听众
沿用按钮计数实验,再添加一个 Label 显示最近操作说明。点击同一个按钮后,一个回调更新次数,另一个回调更新文字。先画两条箭头,再写连接代码:按钮发出 pressed,计数显示和操作说明分别接收。这样做的重点不是让一个按钮拥有更多效果,而是看见发出方并不需要亲自操作所有界面。
接着暂时移除第二条连接,观察次数是否仍然增加。能够独立去掉一个听众,而不修改按钮本身,是通知式协作的直观收益。不要把这解释成“任何调用都要改成信号”;若一个对象明确要求另一个对象打开面板,直接调用含义清楚的方法也可能更合适。选择依据是双方的关系,不是信号听起来更高级。
06参数应该携带事实,不让听众再猜一次
如果以后有多个可调查物件,仅发送“有人调查了”可能不够。接收者需要知道是哪一个物件,才能显示正确说明。设计信号时先写一个自然语言句子:“物件发生调查,携带稳定标识和需要展示的内容。”再决定参数的名称与类型。不要为了省一个参数,让每个接收者重新扫描整个房间寻找“刚刚变化的东西”。
同时注意参数不等于永久所有权。把一个场景节点传给长寿命对象后,该节点仍可能随切换释放。如果接收方只需要一段文字或标识,就传所需数据;确实需要节点行为时才传节点引用。练习时让两个按钮发送不同标签,接收端打印标签,确认每次来源明确。把打印文本和画面标签对应起来,比只看“回调进来了”更容易发现串线。
07把没有反应和反应两次分开排查
没有反应时,按“输入到达—信号发出—回调进入—界面更新”四段检查,在每个边界只放一条临时日志。若前两段通过,就不要继续改按键绑定;若回调已经执行,重点检查被修改的 Label 是否是当前可见实例。反应两次则先数场景里有几份按钮或面板,再检查编辑器连接与代码连接是否重复,以及重开面板时有没有重新连接。
一个实用的验收是:启动后点击三次,关闭面板再打开,再点击一次,总数只能增加四。随后移除一个听众,其余听众仍按预期响应。记录连接发生的位置和发生次数,结束实验后清理临时日志。不要为了压住重复响应添加一个随意的延迟;若根因是重复连接,延迟只会把同一个问题藏得更难观察。
动手后,再勾选
不是“我看懂了”,而是你在独立练习里做出并检查过。
换一个情景,你会怎么判断?
信号最合适表达哪种语义?
用自己的话,留一条笔记
可以写“我之前以为……,现在发现……”,或者记录还没解决的问题。
↗回到资料核对
本课是解释与练习,不替代对应版本的官方手册。网页实验不运行 Godot;未标明实机验证的代码,仍需在独立工程中检查。
标记已读不会自动勾选实作,也不代表游戏功能已经完成。