You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:14:14