如何避免为SkillEvent各子类编写单独事件处理方法?
解决SkillEvent子类统一处理的几种实用方案
这问题我做游戏技能系统的时候也碰到过——一堆参数各异的SkillEvent子类,要是每个都写单独的发送、接收、处理逻辑,代码冗余得离谱。分享几个我亲测好用的方案,能帮你避免重复造轮子:
方案1:泛型+字典映射(灵活易扩展)
核心思路是用泛型注册处理器,把事件类型和对应的处理逻辑绑定到字典里,接收事件时自动匹配类型并转换。
代码示例
首先改造你的EventManager,添加泛型注册和统一执行逻辑:
public class EventManager { // 存储「事件类型 → 对应处理委托」的映射 private Dictionary<Type, Action<SkillEvent>> _eventHandlers = new(); // 给指定事件类型注册处理器 public void RegisterSkillHandler<T>(Action<T> handler) where T : SkillEvent { _eventHandlers[typeof(T)] = skillEvent => handler((T)skillEvent); } public void ExecuteEvent(SkillEvent skillEvent) { if (_eventHandlers.TryGetValue(skillEvent.GetType(), out var handler)) { handler(skillEvent); // 自动转换为子类类型并执行 } else { Debug.LogWarning($"未找到{skillEvent.GetType().Name}对应的处理器"); } } }
使用方式
注册每个子类的处理逻辑,之后不管新增多少子类,只需要加一行注册代码:
// 注册手雷技能事件的处理逻辑 eventManager.RegisterSkillHandler<GrenadeSkillEvent>(grenadeEvent => { Debug.Log($"收到手雷技能事件,目标点:{grenadeEvent.target_point}"); // 这里写具体的技能触发逻辑 }); // 新增其他技能时,直接注册就行 // eventManager.RegisterSkillHandler<FireballSkillEvent>(fireballEvent => { ... });
你的RpcSendSkillEvent完全不用改,依旧传递SkillEvent基类实例,ExecuteEvent会自动完成类型转换和逻辑调用。
方案2:访问者模式(类型安全,无反射)
如果你追求编译时类型安全,不想用反射,可以用访问者模式。它强制每个子类实现接受访问的方法,确保所有事件类型都被覆盖。
代码示例
首先修改基类和子类,添加访问者接口:
// 基类新增Accept方法 public abstract class SkillEvent { public float timestamp; public EventType type; public abstract void Accept(ISkillEventVisitor visitor); } // 访问者接口,每个子类对应一个Visit方法 public interface ISkillEventVisitor { void Visit(GrenadeSkillEvent grenadeEvent); // 新增子类时,在这里添加对应的Visit方法 // void Visit(FireballSkillEvent fireballEvent); } // 手雷事件子类实现Accept public class GrenadeSkillEvent : SkillEvent { public Vector3 target_point; public override void Accept(ISkillEventVisitor visitor) { visitor.Visit(this); // 把自身传递给访问者 } }
然后实现具体的处理器:
public class SkillEventHandler : ISkillEventVisitor { public void Visit(GrenadeSkillEvent grenadeEvent) { Debug.Log($"处理手雷技能:目标点{grenadeEvent.target_point}"); // 具体逻辑 } // 新增子类时,在这里实现对应的Visit方法 } // EventManager调用逻辑 public class EventManager { private SkillEventHandler _handler = new SkillEventHandler(); public void ExecuteEvent(SkillEvent skillEvent) { skillEvent.Accept(_handler); // 自动匹配对应的Visit方法 } }
这个方案的优势是不会出现类型转换错误,但缺点是每次新增子类都要修改访问者接口和处理器类,适合子类数量相对稳定的场景。
方案3:C#模式匹配(简洁直观)
如果用的是C# 7.0及以上版本,模式匹配能让处理逻辑变得非常简洁,不用额外注册或接口,直接通过switch判断类型:
public void ExecuteEvent(SkillEvent skillEvent) { switch (skillEvent) { case GrenadeSkillEvent grenadeEvent: HandleGrenadeEvent(grenadeEvent); break; case FireballSkillEvent fireballEvent: HandleFireballEvent(fireballEvent); break; // 新增子类时,加一个case就行 default: Debug.LogWarning($"未处理的事件类型:{skillEvent.GetType().Name}"); break; } } private void HandleGrenadeEvent(GrenadeSkillEvent e) { Debug.Log($"手雷技能目标:{e.target_point}"); }
这种写法直观易懂,性能也比反射好,新增子类只需要加case和对应的处理方法,非常适合快速迭代的项目。
额外注意点:网络序列化
因为你用了ClientRpc发送事件,要确保子类的自定义参数能被正确序列化(比如在Unity Netcode中,需要给子类字段加[NetworkSerialize]属性),不然接收端会丢失参数:
public class GrenadeSkillEvent : SkillEvent { [NetworkSerialize] // 确保网络传输时字段被序列化 public Vector3 target_point; }
内容的提问来源于stack exchange,提问作者FLX
相关产品推荐
相关产品推荐

