C#全局消息传递系统 注意事项与基础实现

首先:要实现某个系统,首先要考虑是否再场景中我们只希望其存在一个就够了?显而易见,全局的消息传递系统就是这样的一个东西,因此需要将其做成单例;既然有单例就要用到上一文说到的,防止单例释放以后,还有脚本试图调用单例的方法,从而导致在游戏结束的时候,单例依然存在的情况; 不同的消息都需要可以处理,因此所有的消息类型应该有一个共同的基类,暂且叫做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并且输出对应的名字
如下、
在这里插入图片描述

在这里插入图片描述
注意:由于消息对象是类,那么他们在内存中创建的时候,都是动态的创建的,在消息处理完、分发给消息监听器之后,就会销毁,但是,随着时间的增长,内存会随着时间的增长而累计大量的消息,这样会导致在未来的某一个时刻的垃圾回收,因此,为了防止这种偶然性的垃圾回收,当需要销毁某个对象的时候,就注销委托,从而防止对象未在内存中完全释放

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值