Godot使用CSharp中的delegate(委托)和event(事件)进行消息传递

前言

在 Godot 中,引擎已提供信号系统(Signals),不需要自己写 delegate/event

但是

通过信号的话方法的连接没法校验参数,如果脚本也只考虑全局都使用C#,可以通过delegate/event就可以解决这个问题。

在 C# 中,delegate(委托)和 event(事件)是实现 发布-订阅模式(观察者模式)的核心机制。

它们常用于解耦代码,比如 UI 响应、游戏逻辑通知、异步回调等。

下面通过一个 简单而完整的示例 来说明 delegate + event 的使用方式。

场景:玩家生命值变化时触发“受伤”或“死亡”事件

我们将创建:

  • 一个 Player 类,内部定义事件;
  • 一个 UIManager 类,订阅该事件并做出响应。

发布者

定义委托(Delegate)

1
2
// 自定义委托:表示“生命值变化”的通知
public delegate void HealthChangedHandler(float currentHealth);

也可以使用内置泛型委托如 Action<float>,但自定义委托语义更清晰(尤其在需要多个参数或文档说明时)。

在发布者类中声明事件(Event)

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
31
32
33
34
35
using System;

public class Player
{
// 声明事件(基于上面的委托)
public event HealthChangedandler HealthChanged;

private float health = 100f;
public float Health
{
get => health;
set
{
health = Math.Max(0, value); // 不低于0
// 触发事件
OnHealthChanged();
}
}

// 受保护的虚方法:用于触发事件(良好实践)
protected virtual void OnHealthChanged()
{
// 安全调用(C# 6+ 可简写为 HealthChanged?.Invoke(health);)
HealthChanged?.Invoke(health);
}

public void TakeDamage(float damage)
{
Health -= damage;
if (health <= 0)
{
GD.Print("玩家死亡!");
}
}
}

💡 注意:event 关键字限制了外部只能 +=-= 订阅/取消订阅,不能直接调用或赋值(如 HealthChanged = null 是非法的),保证了封装性。

订阅者监听事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UIManager
{
public void Subscribe(Player player)
{
// 订阅事件
player.HealthChanged += OnPlayerHealthChanged;
}

public void Unsubscribe(Player player)
{
// 取消订阅(避免内存泄漏)
player.HealthChanged -= OnPlayerHealthChanged;
}

// 事件处理方法(签名必须匹配委托)
private void OnPlayerHealthChanged(float currentHealth)
{
Console.WriteLine($"【UI】玩家当前生命值: {currentHealth}");
// 这里可以更新血条、显示警告等
}
}

使用示例(主程序)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Program
{
static void Main()
{
var player = new Player();
var ui = new UIManager();

// 订阅
ui.Subscribe(player);

// 模拟受伤
player.TakeDamage(20); // 输出: 【UI】玩家当前生命值: 80
player.TakeDamage(90); // 输出: 【UI】玩家当前生命值: 0 和 "玩家死亡!"

// 取消订阅(可选,但推荐在对象销毁时做)
ui.Unsubscribe(player);
}
}

使用 Action<T> 简化(可选)

如果你不想自定义委托,可以用内置的 Action

1
2
3
4
5
6
public class Player
{
public event Action<float> HealthChanged; // 替代自定义委托

// ...其余代码相同
}

这样更简洁,适合简单场景。

总结

概念 作用
delegate 定义方法签名(函数类型)
event 基于委托的特殊字段,提供发布-订阅机制
+= / -= 订阅 / 取消订阅事件
?.Invoke() 安全触发事件(防止空引用)