Unity UI事件管理系统设计

       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 initialization
	void 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);
	}
}





















  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值