首先:要实现某个系统,首先要考虑是否再场景中我们只希望其存在一个就够了?显而易见,全局的消息传递系统就是这样的一个东西,因此需要将其做成单例;既然有单例就要用到上一文说到的,防止单例释放以后,还有脚本试图调用单例的方法,从而导致在游戏结束的时候,单例依然存在的情况; 不同的消息都需要可以处理,因此所有的消息类型应该有一个共同的基类,暂且叫做Message(所有消息类型的基类);既然要处理消息,那么消息触发的时候,就要有方法去处理,但是现在来看,我们是需要一个统一的格式来定义方法,因此这里使用委托;
其次:思考一下这个系统的基础功能,既然是消息传递,
对本身而言自然要有:消息注册、消息移除、消息处理(系统触发处理还是我们立即处理)
对外部使用系统的而言要有:消息注册函数,消息监听函数
代码如下:
SingletonComponent2 在上一篇中有
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public delegate bool My_MessgaeHandlerDelegate(Message message);// 使用这个委托来进行对应消息的调用
public class My_MessageSystem : SingletonComponent2<My_MessageSystem>
{
public static My_MessageSystem Instance
{
get
{
return (My_MessageSystem)_Instance2;
}
set {
_Instance2 = value;
}
}
private Queue<Message> _messageQueue = new Queue<Message>(); //消息队列
private Dictionary<string, List<My_MessgaeHandlerDelegate>> _listenerDict = new Dictionary<string, List<My_MessgaeHandlerDelegate>>(); //类型与对应关系
private System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();//消息处理的限制时间
private const int _maxQueueProcessingTime = 16667;//最大处理时间
//注册事件
public bool AddListener(System.Type type, My_MessgaeHandlerDelegate handle)
{
if(type == null)
{
Debug.Log("类型为空");
return false;
}
string msgType = type.Name;
if (!_listenerDict.ContainsKey(msgType))
{
_listenerDict.Add(msgType, new List<My_MessgaeHandlerDelegate>());
}
List<My_MessgaeHandlerDelegate> listenerList = _listenerDict[msgType];
if (listenerList.Contains(handle))
{
Debug.Log("您已经注册过此事件了,请勿重复注册");
return false;
}
listenerList.Add(handle);
return true;
}
//移除事件
public bool RemoveListener(System.Type type, My_MessgaeHandlerDelegate handle)
{
if(type == null)
{
Debug.Log("类型为空,请检查");
return false;
}
string msgType = type.Name;
if (_listenerDict.ContainsKey(msgType))
{
List<My_MessgaeHandlerDelegate> listenerList = _listenerDict[msgType];
if (listenerList.Contains(handle))
{
listenerList.Remove(handle);
}
}
return true;
}
//将消息添加到处理队列中,通过系统自动执行
public bool QueueMessage(Message msg) //适用于及时性不高的,不需要立即做出相应的事件
{
if (msg == null)
{
Debug.Log("消息不能为空");
return false;
}
//在这里,我们需要判断的并不是_messageQueue 而是_listenerDict ,_messageQueue只是消息的执行队列,你不能保证说,在这个队列里面只有一个相同类型的处理消息
//可能上一帧添加了一个此类型的处理消息,下一帧又添加了一个,你不能说不去处理他
if (!_listenerDict.ContainsKey(msg.type))
{
//如果在_listenerDict中并不包含这个类型的话,说明没有注册对应的处理函数
return false;
}
_messageQueue.Enqueue(msg);
return true;
}
//手动触发函数,不需要系统执行
public bool DispatchEvent(Message msg)
{
if(!_listenerDict.ContainsKey(msg.type))
{
Debug.Log("没有注册对应的执行事件");
return false;
}
List<My_MessgaeHandlerDelegate> listenerList = _listenerDict[msg.type];
for(int i = 0; i < listenerList.Count; i++)
{
if (listenerList[i](msg))
{
return true;
}
}
return true;
}
//系统自动处理
private void Update()
{
//需要注意的是
//当前帧所处理的消息所花费的时间超高我们所限定的最大值,那么就暂停处理,放到下一帧
//这样可以确保在不超过处理时间的限制阙值,如果消息传递系统压入的消息太多太快,可以防止游戏卡顿
timer.Start();//开始或继续测量执行时间
while(_messageQueue.Count > 0)
{
if (_maxQueueProcessingTime > 0)
{
if (timer.Elapsed.Milliseconds>_maxQueueProcessingTime)
{
return;
}
}
Message curMsg = _messageQueue.Dequeue();
if(!DispatchEvent(curMsg))
{
Debug.Log("Error when processing message" + curMsg.type);
}
}
}
}
上面的代码是全局消息传递系统的基础功能,下面来看一下,如何去使用他
消息的触发:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyEnemCreatorComponent : MonoBehaviour
{
//消息发送
private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
My_MessageSystem.Instance.QueueMessage(new My_TypeMessage());//相当于常用的dispach事件的发送
}
}
}
消息的监听与对应的处理函数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class My_EnemyManagerWitchMessageComponent : MonoBehaviour
{
private List<GameObject> _enemies = new List<GameObject>();
[SerializeField] private GameObject _enemyPrefab;
// Start is called before the first frame update
void Start()
{
My_MessageSystem.Instance.AddListener(typeof(My_TypeMessage), this.handle_CreateEnemyMessage); //监听
My_MessageSystem.Instance.AddListener(typeof(My_KillAllEnemy), this.handle_KillAllEnemyMessage); //监听
}
bool handle_KillAllEnemyMessage(Message msg)
{
this.KillAll();
return true;
}
private void KillAll()
{
foreach(var it in this._enemies)
{
Destroy(it);
}
}
bool handle_CreateEnemyMessage(Message msg)
{
My_TypeMessage castMsg = msg as My_TypeMessage;
string[] names = { "Tomcat", "dick", "Harry" };
GameObject gameObject = GameObject.Instantiate(_enemyPrefab.gameObject, this.transform);
gameObject.name = names[Random.Range(0, names.Length)];
_enemies.Add(gameObject);
My_MessageSystem.Instance.DispatchEvent(new My_EnemyCreatedMessage(gameObject, gameObject.name));//手动触发,立即响应,无需等待
return true;
}
void OnDestroy()
{
if (My_MessageSystem.IsAlive)
{
My_MessageSystem.Instance.RemoveListener(typeof(EnemyCreatedMessage), this.handle_CreateEnemyMessage);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class My_EnemyCreateListenerComponent : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
My_MessageSystem.Instance.AddListener(typeof(My_EnemyCreatedMessage), this.CreatedMessage);
}
bool CreatedMessage(Message msg)
{
My_EnemyCreatedMessage cast = msg as My_EnemyCreatedMessage;
Debug.Log("enemy Created name:" + cast.enemyName);
return true;
}
void OnDestroy()
{
if (My_MessageSystem.IsAlive)
{
My_MessageSystem.Instance.RemoveListener(typeof(My_EnemyCreatedMessage), this.CreatedMessage);
}
}
}
消息类型:
public class My_TypeMessage : Message
{
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class My_EnemyCreatedMessage : Message
{
//创建 创建敌人的消息
// Start is called before the first frame update
public readonly string enemyName;
public readonly GameObject enemyObject;
//默认的创建消息
public My_EnemyCreatedMessage(GameObject gameObject,string enemyName)
{
this.enemyObject = gameObject;
this.enemyName = enemyName;
}
public My_EnemyCreatedMessage()
{
this.enemyObject = null;
this.enemyName = "未创建实例";
}
}
将脚本挂载到场景中的物体上,并且关联上要创建的Enemy,运行,按下空格键,每次都会创建enemy并且输出对应的名字
如下、
注意:由于消息对象是类,那么他们在内存中创建的时候,都是动态的创建的,在消息处理完、分发给消息监听器之后,就会销毁,但是,随着时间的增长,内存会随着时间的增长而累计大量的消息,这样会导致在未来的某一个时刻的垃圾回收,因此,为了防止这种偶然性的垃圾回收,当需要销毁某个对象的时候,就注销委托,从而防止对象未在内存中完全释放