unityUnity3D 成神之路csharp

Unity 3D 教你打造自己的EventSystem

2018-04-13  本文已影响408人  雨落随风

开发一个游戏,事件系统无疑是一个神经中枢般的存在,对解耦工程大有裨益。
接下来,笔者将教大家写一个属于自己的事件系统

需求:

事件系统一般有这几个必要功能:

本文完成的事件系统,在实际使用画风是这样的:


添加事件监听的画风是这样的:
移除事件监听是这样的:
分发事件是这样的:实例化并传入特定事件信息类
事件类型的枚举是事件系统内定的,乱写是会这样子报错的

实现

1、首先定义事件的信息类的基类,利用里式转换实现事件传递信息的多样性:

namespace FrameWork.Event
{
    /// <summary>
    /// 事件信息类基类
    /// </summary>
    public abstract class BaseEventArgs
    {
        public readonly Enum m_Type;
        public readonly GameObject sender;
        public BaseEventArgs(Enum _t, GameObject _sender)
        {
            m_Type = _t;
            sender = _sender;
        }
    }
}

2、然后,我们继承基类并完善特定的事件信息类,示例展示的是鼠标事件信息类:

/// <summary>
    /// 鼠标事件参数类
    /// </summary>
    public class StylusEventArgs : BaseEventArgs
    {
        /// <summary>
        /// 光标下的游戏对象
        /// </summary>
        public readonly GameObject selected;
        /// <summary>
        /// 光标与游戏对象的触碰点
        /// </summary>
        public readonly Vector3 position = default(Vector3);
        /// <summary>
        /// 鼠标或触笔的按键ID
        /// </summary>
        public readonly int buttonID;
        /// <summary>
        /// 鼠标或者触笔事件
        /// </summary>
        /// <param name="_t">事件类型</param>
        /// <param name="_sender">事件发送者</param>
        /// <param name="_selected"></param>
        /// <param name="_position"></param>
        /// <param name="_buttonID"></param>
        public StylusEventArgs(StylusEvent _t, GameObject _sender, GameObject _selected, int _buttonID = -1, Vector3 _position = default(Vector3)) : base(_t, _sender)
        {
            selected = _selected;
            buttonID = _buttonID;
            position = _position;
        }
    }

3、接着,使用枚举列举光标事件项:

namespace FrameWork.Event
{
   /// <summary>
    /// 笔或者鼠标事件类型
    /// </summary>
    public enum StylusEvent
    {
        Enter,
        Press,
        Release,
        Exit
    }
}

4、重点来了,咱们实现一下事件链以及事件链相关功能(添加,执行,移除事件):

using System;
using System.Collections.Generic;
namespace FrameWork.Event
{
    public class EventEntity
    {
        /// <summary>
        /// 事件链
        /// </summary>
        private static Dictionary<Enum, List<Action<BaseEventArgs>>> eventEntitys = null;
        /// <summary>
        /// 当前事件系统的实例
        /// </summary>
        private static EventEntity entity = null;

        private EventEntity()
        {
            InitEvent();
        }

        /// <summary>
        /// 得到指定枚举项的所有事件链
        /// </summary>
        /// <param name="_type">指定枚举项</param>
        /// <returns>事件链</returns>
        private List<Action<BaseEventArgs>> GetEventList(Enum _type)
        {
            if (!eventEntitys.ContainsKey(_type))
            {
                eventEntitys.Add(_type, new List<Action<BaseEventArgs>>());
            }
            return eventEntitys[_type];
        }
        /// <summary>
        /// 添加事件
        /// </summary>
        /// <param name="_type">指定类型</param>
        /// <param name="action">指定事件</param>
        private void AddEvent(Enum _type, Action<BaseEventArgs> action)
        {
            List<Action<BaseEventArgs>> actions = GetEventList(_type);
            if (!actions.Contains(action))
            {
                actions.Add(action);
            }
        }
        /// <summary>
        /// 执行事件
        /// </summary>
        /// <param name="_type">指定事件类型</param>
        /// <param name="args">事件参数</param>
        private void CallEvent(BaseEventArgs args)
        {
            List<Action<BaseEventArgs>> actions = GetEventList(args.m_Type);
            for (int i = actions.Count - 1; i >= 0; --i)
            {
                if (null != actions[i])
                {
                    actions[i](args);
                }
            }
        }
        /// <summary>
        /// 删除指定的事件
        /// </summary>
        /// <param name="_type">指定类型</param>
        /// <param name="action">指定的事件</param>
        private void DelEvent(Enum _type, Action<BaseEventArgs> action)
        {
            List<Action<BaseEventArgs>> actions = GetEventList(_type);
            if (actions.Contains(action))
            {
                actions.Remove(action);
            }
        }
        /// <summary>
        /// 删除指定的事件
        /// </summary>
        /// <param name="action">指定的事件</param>
        private void DelEvent(Action<BaseEventArgs> action)
        {
            if (eventEntitys.Count>0)
            {
                foreach (List<Action<BaseEventArgs>> actions in eventEntitys.Values)
                {
                    if (actions.Contains(action))
                    {
                        actions.Remove(action);
                    }
                }
            }
        }
        /// <summary>
        /// 初始化事件链
        /// </summary>
        private void InitEvent()
        {
            eventEntitys = new Dictionary<Enum, List<Action<BaseEventArgs>>>();
        }
    }  
}

5、为了让这个事件系统好用,我们添加上几个静态方法。

 #region//--------------------------StaticFunction-------------------------------
        /// <summary>
        /// 添加事件监听
        /// </summary>
        /// <param name="_type">事件类型</param>
        /// <param name="action">事件</param>
        public static void AddListener(Enum _type, Action<BaseEventArgs> action)
        {
            ValidCheck(_type);
            if (null != entity)
            {
                entity.AddEvent(_type, action);
            }
            else
            {
                entity = new EventEntity();
                entity.AddEvent(_type, action);
            }
        }
        /// <summary>
        /// 事件分发
        /// </summary>
        /// <param name="_type">事件类型</param>
        /// <param name="args">事件参数</param>
        public static void EventDispatch(BaseEventArgs args)
        {
            if (null != entity)
            {
                entity.CallEvent(args);
            }
        }
        /// <summary>
        /// 移除事件监听
        /// </summary>
        /// <param name="_type">事件类型</param>
        /// <param name="action">事件</param>
        public static void DelListener(Enum _type, Action<BaseEventArgs> action)
        {
            if (null != entity)
            {
                entity.DelEvent(_type, action);
            }
        }
        /// <summary>
        /// 移除事件监听
        /// </summary>
        /// <param name="_type">事件类型</param>
        /// <param name="action">事件</param>
        public static void DelListener(Action<BaseEventArgs> action)
        {
            if (null != entity)
            {
                entity.DelEvent(action);
            }
        }
        /// <summary>
        /// 移除所有事件
        /// </summary>
        public static void RemoveAllListener()
        {
            if (null != entity)
            {
                entity.InitEvent();
            }
        }
        #endregion//--------------------------StaticFunction-------------------------------

当然,为了避免用户使用未定义的事件类型,我们再封装一个简单的有效性校验方法:

        /// <summary>
        /// 事件类型参数(枚举)有效性检查
        /// </summary>
        public static void ValidCheck(Enum _type)
        {
            if (_type.GetType().Namespace != "FrameWork.Event")
            {
                string msg = _type.GetType().Namespace;
                msg = "命名空间:" + (string.IsNullOrEmpty(msg) ? "无" : msg);
                throw new ArgumentException(string.Format("事件系统(纠错):事件类型必须在事件系统中有定义!Tips【{0}】", msg));
            }
        }

测试

为了测试这个事件系统,笔者还写了2个脚本,一个用来分发事件,一个用来订阅和响应,篇幅有限,就截两个图意思意思:


图一、测试用的脚本 图二、动画演示

补充

笔者的事件系统上一个版本是引入了泛型T,代码风格是这样的:
public class EventEntity<T> where T : struct, IConvertible, IComparable, IFormattable
而使用风格则是这样的:

引入了泛型

很显然,在这个事件系统里面,泛型的引入并没有展现泛型的优势:不同类型的逻辑复用;
而且,为了限制用户输入,笔者也是不得已用where对泛型加了诸多限制(如上)。
so,在朋友建议指导下,写成了现在这个样子:Enum类来指向我们的枚举,于是,事件类型的枚举便一样可以多样化了

写在最后

经过一段时间的实际开发中使用,发现了一个容易被忽视的问题,那就是这个事件管理器是静态的,跳场景时如果没有主动清空事件链,就会出现一些诡异的事情。
所以笔者建议如果频繁切换场景呢,事件监听要么写在dontDestory的游戏对象上统一管理,要么呢每个游戏对象监听后在OnDestory里面主动取消监听。
最后呢,笔者决定让这个事件系统能够在转换场景时自动刷新事件管理器,那就是将当前的场景名字作为flag标志位,一旦换场景,下次订阅事件时,就会判断场景是否换了,如果换了就重新new一个事件管理器。当然,旧的管理器里面的事件链也要clear一下。不过这里有个bug,那就是当前场景加载到这个场景本身时候,事件还是会累加一次。(那就要想其他方案,譬如场景卸载事件啥的?)

Demo百度云下载

上一篇 下一篇

猜你喜欢

热点阅读