Unity程序基础框架(三)事件中心公共模块

------------恢复内容开始------------

事件中心模块

本篇文章只是用来记录学习的笔记,老师上课教授,笔记内容有复制拷贝网上的资料、笔记。
知识点: Dictionary,委托,观察者模式
委托和事件详解
作用:降低程序耦合性,减小程序复杂度

image


image

按照这张图来创建4个脚本以及对象

image

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("其它 各个对象要做的事情");
}
}

将脚本挂载到对应的对象上
运行结果如下:
image

问题:怪物死亡后,触发另外三个脚本。如果还需要其他很多事件都在monster类中写的话耦合性会很高(关联性高,如果奖励、任务、或者是怪物死亡脚本没有写好,就会互相影响到)

解决方法:引入事件中心的概念,由事件中心来处理收发事件。 之前在Monster里面,要调用各个不同脚本中的方法时,需要分别访问,而有了事件中心的时候,就只需要访问事件中心的EventTrigger

image


image

事件中心代码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】事件中心

Unity3D程序基础框架(上)

posted @   专心Coding的程侠  阅读(558)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示