本指南基于 Godot4.x 编写。

信号的定义与发射

完整示例

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>(方法名));