Unity程序基础框架(三)事件中心公共模块
------------恢复内容开始------------
事件中心模块
本篇文章只是用来记录学习的笔记,老师上课教授,笔记内容有复制拷贝网上的资料、笔记。
知识点: Dictionary,委托,观察者模式
委托和事件详解
作用:降低程序耦合性,减小程序复杂度
按照这张图来创建4个脚本以及对象
Monster
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Monster : MonoBehaviour { void Start() { Dead(); } /// <summary> /// 死亡方法 /// </summary> void Dead() { Debug.Log("怪物死亡"); //玩家得奖励 GameObject.Find("Player").GetComponent<Player>().MonsterDeadDo(); //任务记录 GameObject.Find("Task").GetComponent<Task>().TaskWaitMonsterDeadDo(); //其它 GameObject.Find("Other").GetComponent<Other>().OtherWaitMonsterDeadDo(); //等等 } }
Player
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { /// <summary> /// 怪物死亡时要做些什么 /// </summary> /// <param name="info"></param> public void MonsterDeadDo() { Debug.Log("玩家得奖励" ); } }
Task
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Task : MonoBehaviour { public void TaskWaitMonsterDeadDo() { Debug.Log("任务,记录"); } }
Other
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Other : MonoBehaviour { /// <summary> /// 怪物死的时候要做的事 /// </summary> /// <param name="info"></param> public void OtherWaitMonsterDeadDo() { Debug.Log("其它 各个对象要做的事情"); } }
将脚本挂载到对应的对象上
运行结果如下:
问题:怪物死亡后,触发另外三个脚本。如果还需要其他很多事件都在monster类中写的话耦合性会很高(关联性高,如果奖励、任务、或者是怪物死亡脚本没有写好,就会互相影响到)
解决方法:引入事件中心的概念,由事件中心来处理收发事件。 之前在Monster里面,要调用各个不同脚本中的方法时,需要分别访问,而有了事件中心的时候,就只需要访问事件中心的EventTrigger
事件中心代码EventCenter
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; /// <summary> /// 事件中心 单例模式对象 /// 1.Dictionary /// 2.委托 /// 3.观察者设计模式 /// </summary> public class EventCenter :BaseManger<EventCenter> { //key对应的是事件的名字 //value对应的是监听这个事件对应的委托函数们 private Dictionary<string, UnityAction> eventDic = new Dictionary<string, UnityAction>(); /// <summary> ///添加事件监听 /// </summary> /// <param name="name">事件的名字</param> /// <param name="action">准备用来处理事件的委托函数</param> public void AddEventListener(string name, UnityAction action) { //判断字典里有没有对应这个事件,有就执行,没有就加进去。 if (eventDic.ContainsKey(name)) { eventDic[name] += action; } else { eventDic.Add(name, action); } } /// <summary> /// 事件触发 /// </summary> /// <param name="name">哪一个名字的事件触发了</param> public void EventTrigger(string name) { if (eventDic.ContainsKey(name)) { // eventDic[name](); eventDic[name].Invoke(); } } }
Monster
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Monster : MonoBehaviour { void Start() { Dead(); } /// <summary> /// 死亡方法 /// </summary> void Dead() { Debug.Log("怪物死亡"); //触发事件 EventCenter.instance.EventTrigger("MonsterDead"); } }
Other
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Other : MonoBehaviour { void Awake() { EventCenter.instance.AddEventListener("MonsterDead", OtherWaitMonsterDeadDo); } /// <summary> /// 怪物死的时候要做的事 /// </summary> /// <param name="info"></param> public void OtherWaitMonsterDeadDo() { Debug.Log("其它 各个对象要做的事情"); } }
Player
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { void Awake() { EventCenter.instance.AddEventListener("MonsterDead", MonsterDeadDo); } /// <summary> /// 怪物死亡时要做些什么 /// </summary> /// <param name="info"></param> public void MonsterDeadDo() { Debug.Log("玩家得奖励" ); } }
Task
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Task : MonoBehaviour { void Awake() { EventCenter.instance.AddEventListener("MonsterDead", TaskWaitMonsterDeadDo); } public void TaskWaitMonsterDeadDo() { Debug.Log("任务,记录"); } }
问题:在例如比如玩家死亡,怪物在玩家之后死亡,他仍要执行玩家里的那个方法,这是不对的,所以玩家死亡的时候在内存里不会真正消失,因为怪物跟玩家有所关联,这就可能造成内存泄漏,应该怎么做呢?
解答:要在Destroy中移除事件
/// <summary> /// 移除对应的实践监听 /// </summary> /// <param name="name">事件的名字</param> /// <param name="action">对应之前添加的委托函数</param> public void RemoveEventListener(string name,UnityAction action) { if (eventDic.ContainsKey(name)) { eventDic[name] -= action; } }
当对象销毁时回执行Ondestory生命周期函数,移除监听事件。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Task : MonoBehaviour { void Start() { EventCenter.instance.AddEventListener("MonsterDead", TaskWaitMonsterDeadDo); } public void TaskWaitMonsterDeadDo() { Debug.Log("任务,记录"); } void OnDestroy() { EventCenter.instance.RemoveEventListener("MonsterDead", TaskWaitMonsterDeadDo); } }
某些时候忘记添加移除事件的函数,或者场景切换时出现问题时,监听事件没有移除干净,这时候就需要清空事件中心。
/// <summary> /// 清空事件中心 /// 主要用在场景切换时 /// </summary> public void Clear() { eventDic.Clear(); }
现在还有一个问题:当有多种怪物死亡时(多种触发条件)无法具体判断是哪种怪物从而导致事件的混乱,
方法:为了解决这个问题,使用泛型委托。所有对象都是继承于object,用object可以代替很多类型变量。虽然会有拆箱装箱的消耗,但是这样就可以通用。
EventCenter
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; /// <summary> /// 事件中心 单例模式对象 /// 1.Dictionary /// 2.委托 /// 3.观察者设计模式 /// </summary> public class EventCenter :BaseManger<EventCenter> { //key对应的是事件的名字 //value对应的是监听这个事件对应的委托函数们 private Dictionary<string, UnityAction<object>> eventDic = new Dictionary<string, UnityAction<object>>(); /// <summary> ///添加事件监听 /// </summary> /// <param name="name">事件的名字</param> /// <param name="action">准备用来处理事件的委托函数</param> public void AddEventListener(string name, UnityAction<object> action) { //判断字典里有没有对应这个事件,有就执行,没有就加进去。 if (eventDic.ContainsKey(name)) { eventDic[name] += action; } else { eventDic.Add(name, action); } } /// <summary> /// 移除对应的实践监听 /// </summary> /// <param name="name">事件的名字</param> /// <param name="action">对应之前添加的委托函数</param> public void RemoveEventListener(string name,UnityAction<object> action) { if (eventDic.ContainsKey(name)) { eventDic[name] -= action; } } /// <summary> /// 事件触发 /// </summary> /// <param name="name">哪一个名字的事件触发了</param> public void EventTrigger(string name,object info) { if (eventDic.ContainsKey(name)) { // eventDic[name](); eventDic[name].Invoke(info); } } /// <summary> /// 清空事件中心 /// 主要用在场景切换时 /// </summary> public void Clear() { eventDic.Clear(); } }
Monster
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Monster : MonoBehaviour { public string Tite= "经验到手"; void Start() { Dead(); } /// <summary> /// 死亡方法 /// </summary> void Dead() { Debug.Log("怪物死亡"); //触发事件 EventCenter.instance.EventTrigger("MonsterDead",this); } }
Player
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { void Start() { EventCenter.instance.AddEventListener("MonsterDead", MonsterDeadDo); } /// <summary> /// 怪物死亡时要做些什么 /// </summary> /// <param name="info"></param> public void MonsterDeadDo(object info) { Debug.Log("玩家得奖励"+(info as Monster).Tite ); } void OnDestroy() { EventCenter.instance.RemoveEventListener("MonsterDead", MonsterDeadDo); } }
公共Mono模块
问题:如果一个类没有继承MonoBehaviour 是没有Update函数的,但是如果这个类有一些事件需要每帧都执行,应该怎么办?
(Unity中Inspector面板中的每一项都称为组件,包括创建的脚本),该脚本是必须要继承MonoBehaviour,否则无法添加为组件。
解决方案:
帧更新
调用函数需要接口所以再创建一个供外部函数调用的类,继承单例模式基类。
MonoMger管理MonoController里面的代码
MonoMgr.cs
/*1.可以提供给外部添加帧更新事件的方法 2.可以提供给外部添加协程的方法*/ using System.Collections; using System.Collections.Generic; using System.ComponentModel; using UnityEngine; using UnityEngine.Events; public class MonoMgr : Singleton<MonoMgr> { private MonoController controller; public MonoMgr() { //保证了MonoController对象的唯一性(单例模式只有在为空时才会new一次 MonoMgr()构造函数也只会new一次) GameObject obj = new GameObject("MonoController"); controller = obj.AddComponent<MonoController>(); } /// <summary> /// 给外部提供的 添加帧更新事件的函数 /// </summary> /// <param name="fun"></param> public void AddUpdateListener(UnityAction fun) { controller.AddUpdateListener(fun); } /// <summary> /// 提供给外部用于移除帧更新事件函数 /// </summary> /// <param name="fun"></param> public void RemoveUpdateListener(UnityAction fun) { controller.RemoveUpdateListener(fun); } #region 以下为封装 协程相关的接口 public Coroutine StartCoroutine(string methodName) { return controller.StartCoroutine(methodName); } public Coroutine StartCoroutine(IEnumerator routine) { return controller.StartCoroutine(routine); } public Coroutine StartCoroutine(string methodName, [DefaultValue("null")] object value) { return controller.StartCoroutine(methodName, value); } public void StopAllCoroutines() { controller.StopAllCoroutines(); } public void StopCoroutine(IEnumerator routine) { controller.StopCoroutine(routine); } public void StopCoroutine(Coroutine routine) { controller.StopCoroutine(routine); } public void StopCoroutine(string methodName) { controller.StopCoroutine(methodName); } #endregion }
笔记复制参考链接:
【Unity】事件中心