接触Unity一段时间了,发现有时候处理角色某个属性变化后需要更新到各个界面上会很麻烦,这时候用到事件通知就很方便,谁关心就订阅这个事件。
这里用一些类工具类:单例模板、自定义Event类
单例模板类,很多地方需要
namespace Util { public class Singleton<T> where T : Singleton<T>, new() { private static T mInstance; private static readonly object syslock = new object(); public static T GetInstance() { if (mInstance == null) { lock (syslock) { if (mInstance == null) { mInstance = new T(); } } } return mInstance; } } }
事件类
namespace Util { public delegate void EventHandler<IEventArgs>(EVENT eventKey, IEventArgs e); class Event : Singleton<Event> { private readonly int mMaxKey = 1 << 31; private Dictionary<EVENT, Dictionary<EventKey, EventHandler<EventArgs>>> mDict = new Dictionary<EVENT, Dictionary<EventKey, EventHandler<EventArgs>>>(); private EventKey mKey = 0; //同一个事件可能被多个地方关心 则通过Key来区分 private EventKey GetKey() { mKey++; if (mKey == mMaxKey) { mKey = 0; } return mKey; } public EventKey AddListener(EVENT ev, EventHandler<EventArgs> handler) { if (!mDict.ContainsKey(ev)) { mDict[ev] = new Dictionary<uint, EventHandler<EventArgs>>(); } var key = GetKey(); mDict[ev].Add(key, handler); return key; } public void RemoveListener(EVENT ev, uint key) { if (mDict.ContainsKey(ev)) { mDict[ev].Remove(key); } } public void Notify(EVENT ev, EventArgs e) { if (mDict.ContainsKey(ev)) { ///防止事中执行 RemoveListener 致使迭代失效 Dictionary<EventKey, EventHandler<EventArgs>> eventTmp = new Dictionary<uint, EventHandler<EventArgs>>(); foreach (var kv in mDict[ev]) { eventTmp[kv.Key] = kv.Value; } foreach (var kv in eventTmp) { kv.Value(ev, e); } } } } }
事件枚举以及参数
namespace Util { public enum EVENT { /// <summary> 测试 EventTestArgs </summary> EVENT_TEST = 1, } public class EventTestArgs : EventArgs { public int a { get; set; } public EventTestArgs(int a) { this.a = a; } } }
//发布和订阅事件 var key = Util.Event.GetInstance().AddListener(Util.EVENT.EVENT_TEST, (Util.EVENT eventId, System.EventArgs args) => { var testArgs = args as Util.EventTestArgs; }); Util.Event.GetInstance().Notify(Util.EVENT.EVENT_TEST, new Util.EventTestArgs(111));
其实想了想这样使用参数优点麻烦就是事件类继承 System.EventArgs 类,那就需要事先申明,可能有些人会觉得很麻烦“比如,lua的话就很方便,传递任意table,这里可以使用可变参数”。但是想过没这样做是没有规范化,后期很难维护,因为没有约束所有多个地方发布同一个事件可能会用了不同的参数,会出现莫名其妙的BUG。
优点:
订阅者不需要关系可变参数个数以及类型,方便后期维护
缺点:
需要事先声明
在事件通知在项目中具体使用则还需要数据类 Model,当数据改变的时候就发布事件
//事件注册封装 ///所有UI的基类 Scene(场景) Panel(弹出面板,比如背包) Tip(tips 比如点击查看装备) public class UIBase : MonoBehaviour { protected Dictionary<EventKey, Util.EVENT> mEventKeyDict = new Dictionary<EventKey, Util.EVENT>(); ... //UI销毁调用 protected virtual void OnDestroyBegin() { foreach (var it in mEventKeyDict) { Util.Event.GetInstance().RemoveListener(it.Value, it.Key); } } /// <summary> /// 监听 Event 事件,界面销毁会自动移除所有事件 /// </summary> /// <param name="ev"></param> /// <param name="handler"></param> /// <returns></returns> protected EventKey AddEventListener(Util.EVENT ev, Util.EventHandler<EventArgs> handler) { var key = Util.Event.GetInstance().AddListener(ev, handler); mEventKeyDict[key] = ev; return key; } /// <summary> /// 移除监听 Event 事件 /// </summary> /// <param name="ev"></param> /// <param name="key"></param> protected void RemoveEventListener(Util.EVENT ev, EventKey key) { mEventKeyDict.Remove(key); Util.Event.GetInstance().RemoveListener(ev, key); } }
//数据Model基类 public class ModelBase { } //用户数据类 public class UserModel : ModelBase { #region 用户ID private uint userId; public uint UserId { set { userId = value; Util.Event.GetInstance().Notify(Util.EVENT.EVENT_CHANGE_NAME, new Util.EventChangeUserIdArgs(userId)); } get { return userId; } } #endregion }
//Model管理类 public class ModelMgr : Util.Singleton<ModelMgr> { private Dictionary<string, ModelBase> mMondeDict = new Dictionary<string, ModelBase>(); public T GetModel<T>() where T:ModelBase { Type type = typeof(T); T model; string name = type.Name; if (!mMondeDict.ContainsKey(name)) { model = System.Activator.CreateInstance(type) as T; mMondeDict[name] = model; } else { model = mMondeDict[name] as T; } return model; } }
#region 事件使用 AddEventListener(Util.EVENT.EVENT_TEST, (Util.EVENT eventId, System.EventArgs args) => { ModelMgr.GetInstance().GetModel<UserModel>().UserId = 123456; }); #endregion
只要接收到Test事件就将 UserModel 的 UserId改变,UserModel再将UserId发布出去,谁关心这个UserId就监听 EVENT_CHANGE_NAME 事件,收到后再渲染到界面上