Unity UI事件管理系统设计

来源:互联网 发布:中化江苏公司 知乎 编辑:程序博客网 时间:2024/05/23 01:56

       UI框架的设计是任何游戏都要做的事情,其中事件管理器(EventManager)是比较常用的UI与逻辑分离的方法,通过注册、绑定、分发事件来控制UI界面或者游戏场景的逻辑处理。之前做cocos游戏写过c++版本、lua版本的事件管理器,Unity大同小异,但是也有很多特殊的地方,这边我记录下设计过程。

       特别提醒,如果习惯使用当前比较成熟的Unity MVC、SingleIoc、UIFrame等UI框架的同学就不要探究这篇笔记了,所有的UI框架都是为了让项目开发高内聚,低耦合,维护和迭代效率高为目的的,习惯用一个就可以了,如果有跟我一样非得自己写框架用的才爽的同学可以来了解下,可能里面的想法对您有用。

首先我的UI事件管理系统必须满足以下需求:

1、UI与逻辑分离


这是最基本需求,我不希望以后游戏更换UI要到处修改逻辑,我想要的是一个UI界面负责展示,一个UIManager来定义一些UI变化的逻辑,UI的变化由事件驱动,所以一个UI界面+一个UIManager+一个事件分发器就可以了,以后换UI只要改UI界面就可以了,这对比较复杂的项目非常重要,千万不要UI和逻辑揉到一起,尤其是Unity这种组件化的开发引擎,有时喜欢偷个懒,比如一个按钮Button点击触发界面A的变化,然后就把A拖拽到Button所在对象中作为组件,然后调用A的方法,这种做法一定不能有,后期维护成本会很高。

UI与逻辑分离的完美设计模式可能就是MVC了,但是要严格按照MVC设计模式做游戏我感觉不是很合适,游戏的模型太灵活多变,版本变动也很大,如果有MVC严格设计游戏UI,可能简单问题复杂化了(之前确实有同事用MVC做游戏,代码量巨大,看着头疼脑热),总之,我不选择MVC,但是上面我说的UI+UiManager+EventManager基本上也满足了UI与逻辑的分离,够用就可以了。

2、所有按钮的回调入口要统一


一个按钮写一个脚本肯定不是正常人的思路,怎么让按钮的回调入口统一呢,我写了一个TriggerEventManager模块作为EventManager的附属模块,就是一个分发事件的脚本,所有按钮只要是用到事件系统的(有些按钮可能很简单,功能是点一下隐去一个界面,直接在Inspector面板的onClik中把这个界面的Alpha改为0就好了,不需要事件系统)全部调用这个脚本接口即可,从此不需要到处找Button脚本了。

public class _TriggerEventManager : MonoBehaviour {private _EventManager _eventManager;void Start(){_eventManager = GetComponent<_EventManager>() as _EventManager;}/// <summary>/// 无参事件/// </summary>/// <param name="eventKey">Event key.</param>public void TriggerEventFromNoParam(string eventKey){_eventManager.__TriggerEvent(eventKey);}

3、对某一个事件可以多个目标响应(类似于观察者模式)


界面A和界面B可能在我按下Button的时候都有相应,那我的EventManager中对事件的要求就是“一对多”的关系,一个事件会注册N个回调代理函数

        /// <summary>/// 定义代理方法,接受层,接到消息后的回调函数/// 回调函数的参数,可为空/// </summary>public delegate void EventHandle(object __PARAM = null);/// <summary>/// 定义事件字典,记录所有分发中的事件/// 支持一个事件多个目标响应/// </summary>public Dictionary<string,List<EventHandle>> __EVENTDIC;

        4、支持UGUI系统的onClick Inspector界面动态绑定,同时也支持代码直接调用

Unity是高度组件化的游戏编程引擎,无论我们习惯用NGUI、UGUI甚至GUI做界面,按钮的回调设计方法有很多种,直接在Inspector中绑定onClick脚本、使用sendMessage、EventLisener等等,我的想法是我的UI管理模块既要可以拖拽绑定onClick,也要可以代码直接调用,这个地方开始是比较纠结,EventManager做成单例?无法拖拽到onClick上面了,而且事件管理器我不希望一个游戏工程只有一个(原因是如果所有的界面事件都要注册到一个地方,不方便多人开发,每个人都要动这个文 件,没办法做版本控制),做成MonoBehivour挂载到场景中,还得先find它再调用它的接口,麻烦一些也耗性能。总之权衡了一下,还是选择了后者,心中还是会默念"后面如果能不继承MonoBehavior就不继承它,保证最后一次"。

综上,我做了一个UIEventSystem预设体

将UIEventSystem拖入场景负责当前场景的所有UI事件管理,也可以拖动多个,多人开发各自定义事件,也不容易冲突掉。

下面就是怎么注册和事件消息和分发消息了,比如我注册一个"startgame"的消息

随便点击一个按钮,触发开始游戏

整个注册和分发流程结束,下面我们来响应这个事件,上面说过我习惯每个UI界面写一个UIManager,用来控制UI界面或者跟UI界面相关的逻辑,那么UiManager同样也是事件的观察者(它只负责观察和响应,不能分发事件,这个很重要,各司其职才能让你的事件系统不会乱成一张网)

[MonoHeader("装备的基础界面,控制界面UI的变化")]public class EquipBasePanelManager : MonoBehaviour {private _EventManager _eventManager;// Use this for initializationvoid Start () {_eventManager = GameObject.Find("UIEventSystem").GetComponent<_EventManager>() as _EventManager;//绑定事件_eventManager.__AttachEvent("startgame",startGameEventHandle);}/// <summary>/// 开始游戏/// </summary>/// <param name="o">O.</param>public void startGameEventHandle(object o){SceneManager.LoadScene("running");}}


上面是无参回调方法,后面的代码也支持有参回调,奉上所有代码

////  _EventManager.cs//  EndlessRunner////  Created by jiabl on 09/27/2017.////using System.Collections;using System.Collections.Generic;using UnityEngine;using MiniJSONV;/// <summary>/// 事件基类管理器/// 所有的事件(尤其是UI交互事件都必须用管理器写事件,防止后期游戏复杂度增加维护混乱)/// 目的:所有的界面与逻辑分离/// 基类负责所有的事件分发处理逻辑/// </summary>[MonoHeader("事件管理器,在当前场景生命周期有效,和_ButtonEventManager脚本一起构成由按键触发的事件控制器 的预设体")]public class _EventManager : MonoBehaviour {/// <summary>/// 消息列表,每一个事件对应一个枚举类型的消息,枚举类型设计为泛型,方便每个场景都有单独的事件消息层,防止多人开发冲突/// </summary>[Header("消息类型列表,请定义不能重复的消息key")]public string[] __EVENTMSG;/// <summary>/// 定义代理方法,接受层,接到消息后的回调函数/// 保留第一个消息参数,为了更方便的注册到统一入口函数中,第二个参数是回调函数的参数,可为空/// </summary>public delegate void EventHandle(object __PARAM = null);/// <summary>/// 定义事件字典,记录所有分发中的事件/// 支持一个事件多个目标响应/// </summary>public Dictionary<string,List<EventHandle>> __EVENTDIC;void Awake(){__EVENTDIC = new Dictionary<string,List<EventHandle>>();//将事件放入字典中管理__AddEvent();}/// <summary>/// 注册事件/// </summary>private void __AddEvent(){if(0 == __EVENTMSG.Length)Debug.LogWarning("_EVENTMSG is empty,please define add msg key as a array!!");foreach(string __MSG in __EVENTMSG){__AddDelegate(__MSG);}}/// <summary>/// 将事件放入字典中管理/// </summary>/// <param name="_eventKey">Event key.</param>private void __AddDelegate(string __MSG){if(__EVENTDIC.ContainsKey(__MSG)){}else{__EVENTDIC.Add(__MSG,new List<EventHandle>());}}/// <summary>/// 触发一个事件/// </summary>/// <param name="_eventKey">Event key.</param>/// <param name="param">Parameter.</param>public void __TriggerEvent(string __MSG,object __param = null){if(__EVENTDIC.ContainsKey(__MSG)){foreach(EventHandle __handle in __EVENTDIC[__MSG]){__handle(__param);}}else{Debug.LogError(__MSG + " is undefine from function: _EventBaseManager::_TriigerEvent");}}/// <summary>/// 事件绑定/// 事件的接收层要调用这个方法,当然也可以使用下面的批量绑定方法,不需要每一个事件写一个函数/// </summary>public void __AttachEvent(string __MSG, EventHandle __eventHandle){if (__EVENTDIC.ContainsKey(__MSG)){__EVENTDIC[__MSG].Add(__eventHandle);}else{Debug.LogError(__MSG + " is undefine from function: _EventBaseManager::_AttachEvent");}}/// <summary>/// 批量导入,所有的事件处理函数入口是唯一的/// </summary>/// <param name="__eventHandle">Event handle.</param>public void __BatchAttachEvent(EventHandle __eventHandle){foreach(string __MSG in __EVENTMSG){__AttachEvent(__MSG,__eventHandle);}}/// <summary>/// 去除事件绑定/// </summary>public void __DetachEvent(string __MSG, EventHandle __eventHandle){if (__EVENTDIC.ContainsKey(__MSG)){__EVENTDIC[__MSG].Remove(__eventHandle);}else{Debug.LogError(__MSG + " is undefine from function: _EventBaseManager::_DetachEvent");}}/// <summary>/// 销毁管理器,当用代码(单例模式)控制事件分发的时候,结束使用请销毁(因为它不会自动销毁)/// </summary>public void __Destroy(){MonoBehaviour.Destroy(this.gameObject);}}


////  _ButtonEvent.cs//  EndlessRunner////  Created by jiabl on 09/28/2017.////using System.Collections;using System.Collections.Generic;using UnityEngine;using MiniJSONV;[MonoHeader("分发事件管理器,可以挂在到Button的on Click中使用,也可以直接调用里面的事件分发方法"+"示例\n void OnClick(){\n \ttriggerEventManager.TriggerEventFromNoParam(\"startgame\")\n }\n void OnClick(){\n \ttriggerEventManager.TriggerEventFromJsonParam(\"{\\\"key\\\":\\\"startgame\\\",param:\\\"1\\\"}\")\n }")]public class _TriggerEventManager : MonoBehaviour {private _EventManager _eventManager;void Start(){_eventManager = GetComponent<_EventManager>() as _EventManager;}/// <summary>/// 通过传递json参数,发送带有参数的事件/// 例如:button的onClik方法参数设置:{"key":"sttttt",   "param":"d" }/// </summary>public void TriggerEventFromJsonParam(string paramsJson){//判断是否json格式if(GameTools.getInstance().isJson(paramsJson)){Dictionary<string, object> response = (Dictionary<string, object>)Json.Deserialize(paramsJson);if (response.ContainsKey("key")){if(response.ContainsKey("param")){_eventManager.__TriggerEvent((string)response["key"],(string)response["param"]);}else{_eventManager.__TriggerEvent((string)response["key"]);}}else{Debug.Assert(true,"TriigerEventFromJsonParam Func param error: no 'key' inside the json => " + paramsJson);}}else{Debug.Assert(true,"AllButtonEvent Func: TriigerEvent param is not a json string =>  " + paramsJson);}}/// <summary>/// 无参事件/// </summary>/// <param name="eventKey">Event key.</param>public void TriggerEventFromNoParam(string eventKey){_eventManager.__TriggerEvent(eventKey);}}




 
原创粉丝点击