游戏开发分享
事件总线与对象池
2026-05-24
•2 分钟•245 字前言
最近事情有点多,很久没更新了,有很多东西可以讲,这里给大家挑出来讲一下项目里面常用的东西——事件总线和对象池
事件总线(EventBus)
介绍
大家应该使用过Godot的信号系统,我除了必要的交互(比如按钮的控制等),其他的我都会用事件总线
简而言之,就是使用者需要订阅,这样有对象发出广播后,他就会接收到并执行方法,注意退出场景需要取消订阅,不然会发生泄露,我这里没用static的原因也是因为这个,我给他绑在了我的GameManager上,我游戏结束后移除游戏场景后,他也会自然消失
或者也可以在切换场景的时候
ClearAll
代码
public class EventBus
{
//事件中转站
private readonly Dictionary<Type, List<Delegate>> _subscribers = new(); //事件组
public void Subscriber<T>(Action<T> handler) //订阅事件
{
var type = typeof(T);
if (!_subscribers.ContainsKey(type))
_subscribers[type] = new List<Delegate>();
_subscribers[type].Add(handler);
}
public void Publish<T>(T evt) //广播事件
{
var type = typeof(T);
if (!_subscribers.ContainsKey(type)) return;
foreach (var b in _subscribers[type])
(b as Action<T>)?.Invoke(evt);
}
public void UnsubScriber<T>(Action<T> handler) //取消订阅
{
var type = typeof(T);
if (_subscribers.ContainsKey(type))
_subscribers[type].Remove(handler);
}
public void ClaerAll() //清空事件组
{
_subscribers.Clear();
}
}
核心就是一个Dictionary<Type, List<Delegate>>,用事件的类型当 Key,把所有订阅者存进去。用Type而不是字符串的好处是编译期就能确定,改名不会出运行时Bug
使用方式
这里我用选择建筑代码给大家举例 先定义一个事件,可以用类或者结构体:
public class Event_UI_SelectBuild //选择建筑
{
public BaseBuild chooseBuild { get; set; } //这里是传了我的建筑对象
}
GameController鼠标点中建筑后发出事件,UI订阅后自己去更新面板,两边互不认识
//GameController 发布,选中时带上建筑数据,取消选中传空
eventBus.Publish(new Event_UI_SelectBuild { chooseBuild = build });
eventBus.Publish(new Event_UI_SelectBuild() { });
//UI订阅,判断 chooseBuild是否为空决定显示还是隐藏面板
eventBus.Subscriber<Event_UI_SelectBuild>(OnSelectBuild);
private void OnSelectBuild(Event_UI_SelectBuild evt)
{
if (evt.chooseBuild == null) { /*隐藏面板*/ }
else { /*显示面板填入建筑数据*/ }
}
如果是static,场景切换时调一下 ClaerAll 清空就好,我绑游戏场景了,场景没了事件总线也就没了
对象池(ObjectPool)
介绍
对象池的思路是:节点不销毁,隐藏起来,下次直接取出来复用,用来搞投掷物或者重复的实例比较好
这里我也是直接绑在了我的游戏场景上,因为还需要用场景树添加场景
public partial class ObjectPool : Node2D
{
private Dictionary<string, Queue<Node>> pools = new();
public T Get<T>(string key, PackedScene scene) where T : Node //获取对象
{
Node obj = null;
if (pools.ContainsKey(key) && pools[key].Count > 0)
obj = pools[key].Dequeue();
else
obj = scene.Instantiate();
//激活
if (!obj.IsInsideTree())
AddChild(obj);
obj.ProcessMode = ProcessModeEnum.Inherit;
obj.Set("visible", true);
return obj as T;
}
public void Return(string key, Node obj) //返回对象
{
if (obj == null || !obj.IsInsideTree()) return;
obj.Set("visible", false);
obj.ProcessMode = ProcessModeEnum.Disabled;
if (!pools.ContainsKey(key))
pools[key] = new Queue<Node>();
pools[key].Enqueue(obj);
}
}
一个Get一个Return,代码很简单
使用方式
//取出
var bullet = ObjectPool.Get<Bullet>("bullet", bulletScene);
bullet.Position = muzzlePosition;
//归还,不要QueueFree移除
ObjectPool.Return("bullet", bullet);
结尾
大家可以根据自己需要进行修改,我这里还有很多可以完善的,比如可以给对象池进行预热,让他提前生成一些,不会因为忽然大量生成导致卡顿之类的,这里就是给大家分享一些游戏基础,其实有很多知识可以讲的,之后有时间我再写个文章整理一下,感谢观看