[unity框架搭建總結一] unity自動反射廣播消息消息組件

前言

在我的上一篇文章裏面使用反射實現了一個消息驅動組件:https://blog.csdn.net/qq_16054639/article/details/79579546
之所以要用反射而不用委託去實現廣播消息系統是因爲:
1. 反射比委託更加靈活(不定參數,空引用判斷)
2. 效率上並不會比委託差多少(如果項目不能接受那就另說)
3. 可以做出更好方便使用的框架(今天的主題)

遊戲通常的架構

分層設計+消息驅動+控制反轉

設計實現

廢話不多說先上代碼


  1. 廣播核心類
#define DEBUG
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;


namespace MiniCoco
{
    public class MiniBroadCast
    {

        private static MiniBroadCast Instance = new MiniBroadCast();
        static private MessengerHelper messengerHelper = (new GameObject("MessengerHelper")).AddComponent<MessengerHelper>();
        private SafeDictionary<string, List<Observer>> m_observer = new SafeDictionary<string, List<Observer>>();

        /**
         * 註冊觀察者
         */
        public static void RegistListener(System.Object o)
        {
            Type _t = o.GetType();
            foreach (MethodInfo _method in _t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
            {
                System.Object[] _attrs = _method.GetCustomAttributes(typeof(MiniUrl), false);  //反射獲得用戶自定義屬性
                Observer _observer = new Observer();
                ///多個特性取最後一個
                foreach (System.Attribute _attr in _attrs)
                {
                    if (_attr is MiniUrl)
                    {
                        MiniUrl _mini_url = _attr as MiniUrl;
                        if (_mini_url.Message != null)
                        {
                            _observer.message = _mini_url.Message;
                            _observer.method = _method;
                            _observer.instance = o;
                            _observer.is_static = false;
                            _observer.object_name = o.ToString();
                        }
                    }
                }
                if (_observer.message != null)
                {
#if DEBUG
                    Debug.Log("instance regist Url:" + _observer.message + " Method:" + _method.Name);
#endif
                    if (!MiniBroadCast.Instance.m_observer.ContainsKey(_observer.message))
                        MiniBroadCast.Instance.m_observer[_observer.message] = new List<Observer>();
                    MiniBroadCast.Instance.m_observer[_observer.message].Add(_observer);
                }
            }
        }

        /**
         * 註冊靜態觀察者
         */
        public static void RegistListener(System.Type t)
        {
            foreach (MethodInfo _method in t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static))
            {
                System.Object[] _attrs = _method.GetCustomAttributes(typeof(MiniUrl), false);  //反射獲得用戶自定義屬性
                Observer _observer = new Observer();
                ///多個特性取最後一個
                foreach (System.Attribute _attr in _attrs)
                {
                    if (_attr is MiniUrl)
                    {
                        MiniUrl _mini_url = _attr as MiniUrl;
                        if (_mini_url.Message != null)
                        {
                            _observer.method = _method;
                            _observer.message = _mini_url.Message;
                            _observer.instance = null;
                            _observer.is_static = true;
                        }
                    }
                }
                if (_observer.message != null)
                {
#if DEBUG
                    Debug.Log("static regist Url:" + _observer.message + " Method:" + _method.Name);
#endif
                    if (!MiniBroadCast.Instance.m_observer.ContainsKey(_observer.message))
                        MiniBroadCast.Instance.m_observer[_observer.message] = new List<Observer>();
                    MiniBroadCast.Instance.m_observer[_observer.message].Add(_observer);
                }
            }
        }

        /**
         * p_params:不定參數
         */
        public static void Broadcast(string key, params object[] p_params)
        {

            if (!MiniBroadCast.Instance.m_observer.ContainsKey(key))
            {
#if DEBUG
                Debug.LogError("observer not find");
#endif
                return;
            }
            //反射調用(包括靜態方法)
            List<Observer> _list = new List<Observer>();
            //深度複製一份,避免在反射執行過程中刪除觀察者出現錯誤
            for (int i = MiniBroadCast.Instance.m_observer[key].Count - 1; i >= 0; i--)
            {
                if (!MiniBroadCast.Instance.m_observer[key][i].is_static && MiniBroadCast.Instance.m_observer[key][i].instance.Equals(null))
                {
#if DEBUG
                    Debug.LogWarning("null is " + MiniBroadCast.Instance.m_observer[key][i].object_name);
#endif
                    MiniBroadCast.Instance.m_observer[key].RemoveAt(i);

                    if (MiniBroadCast.Instance.m_observer[key].Count == 0)
                        MiniBroadCast.Instance.m_observer.Remove(key);
                }
                else
                {
                    _list.Add(MiniBroadCast.Instance.m_observer[key][i]);
                }

            }
            //執行方法
            foreach (Observer o in _list)
            {
                o.method.Invoke(o.instance, p_params);
            }
        }

        public static void RemoveListener(System.Object o)
        {

            Type _t = o.GetType();
            foreach (MethodInfo _method in _t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
            {
                System.Object[] _attrs = _method.GetCustomAttributes(typeof(MiniUrl), false);  //反射獲得用戶自定義屬性
                Observer _observer = new Observer();
                string _message="";
                ///多個特性取最後一個
                foreach (System.Attribute _attr in _attrs)
                {
                    if (_attr is MiniUrl)
                    {
                        MiniUrl _mini_url = _attr as MiniUrl;
                        if (_mini_url.Message != null)
                        {
                            _message = _mini_url.Message;
                        }
                    }
                }

                if (!MiniBroadCast.Instance.m_observer.ContainsKey(_message))
                    continue;

                List<Observer> _list = MiniBroadCast.Instance.m_observer[_message];
                for (int i = _list.Count - 1; i >= 0; i--)
                {
                    if (_list[i].instance == o && _list[i].method.Name == _method.Name)
                    {
                        _list.RemoveAt(i);
#if DEBUG
                        Debug.Log("Remove Instance:" + o.ToString() + " Method:" + _method.Name);
#endif
                        if (_list.Count == 0)
                            MiniBroadCast.Instance.m_observer.Remove(_message);
                    }
                }

                if (_list.Count == 0)
                    MiniBroadCast.Instance.m_observer.Remove(_message);
            }
        }

        public static void RemoveListener(System.Type t)
        {
            foreach (MethodInfo _method in t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static))
            {
                System.Object[] _attrs = _method.GetCustomAttributes(typeof(MiniUrl), false);  //反射獲得用戶自定義屬性
                Observer _observer = new Observer();
                string _message = "";
                ///多個特性取最後一個
                foreach (System.Attribute _attr in _attrs)
                {
                    if (_attr is MiniUrl)
                    {
                        MiniUrl _mini_url = _attr as MiniUrl;
                        if (_mini_url.Message != null)
                        {
                            _message = _mini_url.Message;
                        }
                    }
                }

                if (!MiniBroadCast.Instance.m_observer.ContainsKey(_message))
                    continue;

                List<Observer> _list = MiniBroadCast.Instance.m_observer[_message];
                for (int i = _list.Count - 1; i >= 0; i--)
                {
                    if (_list[i].instance == null && _list[i].method.Name == _method.Name)
                    {
                        _list.RemoveAt(i);
#if DEBUG
                        Debug.Log("Remove Instance:null Method:" + _method.Name);
#endif
                        if (_list.Count == 0)
                            MiniBroadCast.Instance.m_observer.Remove(_message);
                    }
                }

                if (_list.Count == 0)
                    MiniBroadCast.Instance.m_observer.Remove(_message);
            }
        }
        public static void CleanAll()
        {
            MiniBroadCast.Instance.m_observer = new SafeDictionary<string, List<Observer>>();
#if DEBUG
            Debug.Log("clean all");
#endif
        }
        private class Observer
        {
            public string message;
            public object instance;
            public MethodInfo method;
            public bool is_static;
            public string object_name = "";
        }

        private sealed class MessengerHelper : MonoBehaviour
        {
            void Awake()
            {
                DontDestroyOnLoad(gameObject);
            }

            //Clean up eventTable every time a new level loads.
            public void OnLevelWasLoaded(int unused)
            {
                MiniBroadCast.CleanAll();
            }
        }
    }
}

相比於上一個實現,這次使用了特徵+反射的方式,自動反射當前需要註冊的方法.用法上顯得更加輕便了有木有,另外加入了切換關卡自動清空字典的功能,這個問題其實在上一個版本里面就有發現,當時處理的是一旦發現廣播中存在空引用就移除這個Observer.加入自動清除會使代碼效率高一點也使代碼更加的健壯.


  1. 特性類
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MiniCoco{

    [AttributeUsage(AttributeTargets.Method, AllowMultiple=false,Inherited=false)]
    public class MiniUrl : Attribute
    {
        public string Message { get; set; }
    }
}

  1. 線程安全字典
using System.Collections;
using System.Collections.Generic;

namespace MiniCoco
{
    public class SafeDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        private readonly object syncRoot = new object();
        private readonly Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

        #region IDictionary<TKey,TValue> Members

        /// <summary>
        /// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </summary>
        /// <param name="key">The object to use as the key of the element to add.</param>
        /// <param name="value">The object to use as the value of the element to add.</param>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"></see> is read-only.</exception>
        /// <exception cref="T:System.ArgumentException">An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</exception>
        /// <exception cref="T:System.ArgumentNullException">key is null.</exception>
        public void Add(TKey key, TValue value)
        {
            lock (syncRoot)
            {
                d.Add(key, value);
            }
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"></see> contains an element with the specified key.
        /// </summary>
        /// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</param>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.IDictionary`2"></see> contains an element with the key; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">key is null.</exception>
        public bool ContainsKey(TKey key)
        {
            return d.ContainsKey(key);
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </summary>
        /// <value></value>
        /// <returns>An <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</returns>
        public ICollection<TKey> Keys
        {
            get
            {
                lock (syncRoot)
                {
                    return d.Keys;
                }
            }
        }

        /// <summary>
        /// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <returns>
        /// true if the element is successfully removed; otherwise, false.  This method also returns false if key was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"></see> is read-only.</exception>
        /// <exception cref="T:System.ArgumentNullException">key is null.</exception>
        public bool Remove(TKey key)
        {
            lock (syncRoot)
            {
                return d.Remove(key);
            }
        }

        /// <summary>
        /// Tries the get value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public bool TryGetValue(TKey key, out TValue value)
        {
            lock (syncRoot)
            {
                return d.TryGetValue(key, out value);
            }
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </summary>
        /// <value></value>
        /// <returns>An <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</returns>
        public ICollection<TValue> Values
        {
            get
            {
                lock (syncRoot)
                {
                    return d.Values;
                }
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="TValue"/> with the specified key.
        /// </summary>
        /// <value></value>
        public TValue this[TKey key]
        {
            get { return d[key]; }
            set
            {
                lock (syncRoot)
                {

                    d[key] = value;
                }
            }
        }

        /// <summary>
        /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </summary>
        /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
        public void Add(KeyValuePair<TKey, TValue> item)
        {
            lock (syncRoot)
            {
                ((ICollection<KeyValuePair<TKey, TValue>>)d).Add(item);
            }
        }

        /// <summary>
        /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only. </exception>
        public void Clear()
        {
            lock (syncRoot)
            {
                d.Clear();
            }
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> contains a specific value.
        /// </summary>
        /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
        /// <returns>
        /// true if item is found in the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false.
        /// </returns>
        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return ((ICollection<KeyValuePair<TKey, TValue>>)d).Contains(item);
        }

        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">arrayIndex is less than 0.</exception>
        /// <exception cref="T:System.ArgumentNullException">array is null.</exception>
        /// <exception cref="T:System.ArgumentException">array is multidimensional.-or-arrayIndex is equal to or greater than the length of array.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"></see> is greater than the available space from arrayIndex to the end of the destination array.-or-Type T cannot be cast automatically to the type of the destination array.</exception>
        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            lock (syncRoot)
            {
                ((ICollection<KeyValuePair<TKey, TValue>>)d).CopyTo(array, arrayIndex);
            }
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </summary>
        /// <value></value>
        /// <returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</returns>
        public int Count
        {
            get { return d.Count; }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only; otherwise, false.</returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </summary>
        /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
        /// <returns>
        /// true if item was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false. This method also returns false if item is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            lock (syncRoot)
            {
                return ((ICollection<KeyValuePair<TKey, TValue>>)d).Remove(item);
            }
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return ((ICollection<KeyValuePair<TKey, TValue>>)d).GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)d).GetEnumerator();
        }

        #endregion
    }
}

因爲考慮到多線程安全問題,又不能使用ConcurrentDictionary,無奈封裝了個線程安全字典.


  1. 實驗代碼
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MessageConst {
    public const string TEST = "test";
    public const string TEST_2 = "other";
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MiniCoco;
using UnityEngine.SceneManagement;

public class Test : MonoBehaviour {

    // Use this for initialization
    void Start () {
        //註冊實例方法
        MiniBroadCast.RegistListener(this);
        //註冊靜態方法
        MiniBroadCast.RegistListener(typeof(Test));
    }

    // Update is called once per frame
    void Update () {

    }

    [MiniUrl(Message = MessageConst.TEST)]
    void Test1()
    {
        Debug.Log("test1 get message!");
        MiniBroadCast.RemoveListener(this);
    }

    public void OnClick()
    {
        //發送消息
        MiniBroadCast.Broadcast(MessageConst.TEST);
    }

    [MiniUrl(Message = MessageConst.TEST)]
    static void Test2()
    {
        Debug.Log("test2 get message!");
        MiniBroadCast.RemoveListener(typeof(Test));
    }
}

優點

  1. 省去了先前的一堆消息註冊代碼
  2. 消息就在對應的函數頭頂上,以後閱讀起來方便

注意事項

  1. 自己特別要控住好方法的參數個數,廣播和接受參數個數要對應

結語

歡迎在留言區跟我進行討論,指出我的不足
我也在學習的途中,希望我的總結能幫助到正在學習的小夥伴
歡迎轉載,轉載請註明出處

參考

  1. 《Unity 3D遊戲客戶端基礎框架》消息系統

  2. 自定義Dictionary支持線程安全

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章