信号的定义与发射
完整示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| using Godot;
public partial class Player : Node2D { // 1. 定义信号 [Signal] public delegate void HealthChangedEventHandler(int newHealth); private int _health = 100; // 2. 发射信号 public void TakeDamage(int damage) { _health -= damage; EmitSignal(SignalName.HealthChanged, _health); } }
|
命名规则
| 你定义的委托名 |
Godot 生成的信号名 |
使用方式 |
HealthChangedEventHandler |
HealthChanged |
player.HealthChanged += ... |
DiedEventHandler |
Died |
player.Died += ... |
ScoreUpdatedEventHandler |
ScoreUpdated |
player.ScoreUpdated += ... |
规则:Godot 自动去掉 EventHandler 后缀生成信号名
信号的接收(订阅)
完整工作流程
1 2 3 4 5 6 7 8 9 10 11 12 13
| Player.TakeDamage(20) 被调用 ↓ _health 变为 80 ↓ EmitSignal(SignalName.HealthChanged, 80) ↓ Godot 查找所有连接到这个信号的方法 ↓ 自动调用 UI.OnHealthChanged(80) ↓ 自动调用 SoundManager.PlayHurtSound(80) ↓ 自动调用 其他订阅者的方法(80)
|
订阅示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| // ============ UI.cs ============ public partial class UI : Control { public override void _Ready() { // 1. 获取 Player 节点引用 var player = GetNode<Player>("../Player"); // 2. 订阅信号 player.HealthChanged += OnHealthChanged; } // 3. 接收方法(签名必须匹配委托定义) private void OnHealthChanged(int newHealth) { GD.Print($"UI 收到通知:生命值变为 {newHealth}"); } }
// ============ SoundManager.cs ============ public override void _Ready() { var player = GetNode<Player>("../Player"); player.HealthChanged += PlayHurtSound; // 多个订阅者 }
private void PlayHurtSound(int newHealth) { // 播放受伤音效 }
|
关键要点
- ✅ 必须先获取节点引用才能订阅
- ✅ 多个节点可以订阅同一个信号
- ✅ 接收方法签名必须匹配委托定义
- ✅ 使用
**-=** 取消订阅
两种订阅方式对比
方式一:+= 语法(推荐用于 C# 信号)
1
| player.HealthChanged += OnHealthChanged;
|
优点
- 语法简洁清晰
- 编译时类型检查(参数错误会报编译错误)
- IDE 自动补全支持
- 符合 C# 习惯
缺点
- 只能连接 C# 定义的信号
- 不能连接 Godot 引擎内置信号
- 不支持高级连接选项
方式二:Connect() 方法(万能但繁琐)
1 2
| player.Connect(Player.SignalName.HealthChanged, Callable.From<int>(OnHealthChanged));
|
优点
- 可以连接 Godot 引擎信号(必须用这个)
- 可以连接 GDScript 节点的信号
- 支持高级选项(OneShot、Deferred 等)
- 支持动态信号名
缺点
- 语法繁琐
- 无编译时检查(拼写错误、类型错误运行时才知道)
- 需要手动指定参数类型
对比表格
| 特性 |
+= 方式 |
Connect() 方式 |
| 语法简洁度 |
⭐⭐⭐⭐⭐ |
⭐⭐ |
| 类型安全 |
⭐⭐⭐⭐⭐ 编译时检查 |
⭐⭐⭐ 运行时检查 |
| 连接 GDScript 信号 |
❌ 不支持 |
✅ 支持 |
| 连接引擎信号 |
❌ 不支持 |
✅ 支持 |
| 高级选项 |
❌ 无 |
✅ 有 |
| 跨语言 |
❌ 仅 C# |
✅ C#/GDScript 通用 |
Callable.From 详解
泛型参数含义
泛型参数 = 信号传递的参数类型(按顺序)
1 2 3 4 5 6 7 8 9 10
| // Area2D 的 body_entered 信号会传递一个 Node2D 参数 var area = GetNode<Area2D>("Area");
// ✅ 正确:告诉 Callable 这个方法接收 Node2D 参数 area.Connect("body_entered", Callable.From<Node2D>(OnBodyEntered));
private void OnBodyEntered(Node2D body) // 参数类型匹配 { GD.Print($"进入的物体: {body.Name}"); }
|
不同参数数量写法
| 信号类型 |
泛型写法 |
方法签名 |
| 无参数 |
Callable.From(方法) |
void Method() |
| 1 个参数 |
Callable.From<T>(方法) |
void Method(T arg) |
| 2 个参数 |
Callable.From<T1, T2>(方法) |
void Method(T1 arg1, T2 arg2) |
| 3 个参数 |
Callable.From<T1, T2, T3>(方法) |
void Method(T1 a1, T2 a2, T3 a3) |
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // 无参数 var timer = GetNode<Timer>("Timer"); timer.Connect("timeout", Callable.From(OnTimeout));
private void OnTimeout() { }
// 单参数 player.Connect(Player.SignalName.HealthChanged, Callable.From<int>(OnHealthChanged));
private void OnHealthChanged(int newHealth) { }
// 多参数 [Signal] public delegate void PositionChangedEventHandler(Vector2 pos, float speed);
player.Connect(Player.SignalName.PositionChanged, Callable.From<Vector2, float>(OnPositionChanged));
private void OnPositionChanged(Vector2 pos, float speed) { }
|
添加额外参数
方法 A:Lambda 表达式(推荐)
1 2 3 4 5 6 7 8 9 10 11 12
| // 场景:多个按钮,想知道是哪个被点击 var button1 = GetNode<Button>("Button1"); var button2 = GetNode<Button>("Button2");
// 使用 lambda 传递额外参数 button1.Connect("pressed", Callable.From(() => OnButtonPressed("Button1"))); button2.Connect("pressed", Callable.From(() => OnButtonPressed("Button2")));
private void OnButtonPressed(string buttonName) { GD.Print($"{buttonName} 被点击了"); }
|
1 2 3 4 5 6 7 8 9 10 11
| // 带原始信号参数 + 额外参数 var area = GetNode<Area2D>("Area");
area.Connect("body_entered", Callable.From<Node2D>(body => OnBodyEntered(body, "主区域"))); // ↑ 原始参数 ↑ 你的方法 ↑ 额外参数
private void OnBodyEntered(Node2D body, string areaName) { GD.Print($"{body.Name} 进入了 {areaName}"); }
|
方法 B:Bind() 方法
1 2 3 4 5 6 7 8 9
| // 单个额外参数 var button = GetNode<Button>("Button"); button.Connect("pressed", Callable.From(OnButtonPressed).Bind("确认按钮"));
private void OnButtonPressed(string buttonName) { GD.Print($"{buttonName} 被点击"); }
|
1 2 3 4 5 6 7 8 9 10
| // 多个额外参数 var area = GetNode<Area2D>("Area"); area.Connect("body_entered", Callable.From<Node2D>(OnBodyEntered).Bind("危险区域", 10));
private void OnBodyEntered(Node2D body, string areaName, int damage) { // 参数顺序:原始信号参数 → 绑定的额外参数 GD.Print($"{body.Name} 进入 {areaName},受到 {damage} 伤害"); }
|
取消订阅
使用 -=
1 2 3 4 5
| // 订阅 player.HealthChanged += OnHealthChanged;
// 取消订阅 player.HealthChanged -= OnHealthChanged;
|
使用 Disconnect()
1 2 3 4 5 6 7
| // 订阅 player.Connect(Player.SignalName.HealthChanged, Callable.From<int>(OnHealthChanged));
// 取消订阅 player.Disconnect(Player.SignalName.HealthChanged, Callable.From<int>(OnHealthChanged));
|
⚠️常见陷阱与注意事项
参数类型必须精确匹配
1 2 3 4 5 6 7
| // ❌ 错误 player.HealthChanged += WrongMethod; private void WrongMethod(float health) { } // float 不匹配 int
// ✅ 正确 player.HealthChanged += OnHealthChanged; private void OnHealthChanged(int health) { }
|
Connect() 拼写错误运行时才报错
1 2 3 4 5
| // ❌ 拼写错误,运行时崩溃 player.Connect("HelthChanged", Callable.From<int>(OnHealthChanged));
// ✅ 正确 player.Connect(Player.SignalName.HealthChanged, Callable.From<int>(OnHealthChanged));
|
记得取消订阅
1 2 3 4 5 6 7 8 9 10 11 12
| // ❌ 可能导致内存泄漏 public override void _Ready() { player.HealthChanged += OnHealthChanged; } // 节点销毁后信号仍然连接
// ✅ 正确 public override void _ExitTree() { player.HealthChanged -= OnHealthChanged; }
|
引擎信号必须用 Connect()
1 2 3 4 5 6
| // ❌ 不起作用 var button = GetNode<Button>("Button"); button.Pressed += OnPressed; // 引擎信号不支持 +=
// ✅ 正确 button.Connect("pressed", Callable.From(OnPressed));
|
快速参考
核心语法速查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // 定义信号 [Signal] public delegate void MySignalEventHandler(参数类型 参数名);
// 发射信号 EmitSignal(SignalName.MySignal, 参数值);
// 订阅 C# 信号 object.MySignal += 方法名;
// 订阅引擎/GDScript 信号 object.Connect("信号名", Callable.From<参数类型>(方法名));
// 添加额外参数 object.Connect("信号名", Callable.From(() => 方法名(额外参数))); object.Connect("信号名", Callable.From<T>(方法名).Bind(额外参数));
// 取消订阅 object.MySignal -= 方法名; object.Disconnect("信号名", Callable.From<T>(方法名));
|