Gameframework架構思路

前言

這裏的GF就是Gameframework框架,俗話說得好不想懂底層代碼的程序猿,不是好程序猿(那裏來的俗話,我也不知道...),Unity引擎如何搭建的,不懂倒是沒有關係,畢竟代碼沒有開源(就算開源了,我也不會去讀的,畢竟在下智商有限),但是GF代碼是個人設計基於Unity的框架,你用着封裝好的十八大金剛,敲着簡單的代碼,良心不會痛嘛,而且萬一想要往框架封裝新模塊,@作者去添加模塊也不太好,我們也不是這樣的人,所以所以,還是看一下作者設計框架時的簡單思路。

  • 如何下手?

接觸GF差不多有一個月,本來準備拿它馬上練手的,去寫一個小遊戲嘗試一下,但是突然思考良久, 不行!作爲優秀的程序猿,連框架代碼原理都不瞭解,這麼可以如此匆忙的下手(萬一和傻子一樣手裏已經拿着省力槓桿,但還是用手去翹石頭,這樣也不太好),所以準備把每個模塊仔仔細細瞭解清楚,然後把搭建GF框架的思路也明明白白給大腦安排一遍,那個時候再去使用省力槓桿,豈不是甚好?知識都是高濃度轉向低濃度的,所以只要把腦袋緊貼着電腦屏幕就可以把知識轉移到腦子裏。在學習GF框架儘量要做到一個目標:你的框架就是我的框架,我的框架還是我的框架。

文字水了這麼多了,現在應該討論一下框架應該如何下手學習?當場捕獲兩個腳本(GameFrameworkEntry和GameEntry),各位可以先從GameFrameworkEntry腳本,畢竟作者把它備註成遊戲框架入口,看看遊戲框架入口到底寫了什麼鬼?具體代碼如下:

using System;
using System.Collections.Generic;

namespace GameFramework
{
    /// <summary>
    /// 遊戲框架入口。
    /// </summary>
    public static class GameFrameworkEntry
    {
        private static readonly GameFrameworkLinkedList<GameFrameworkModule> s_GameFrameworkModules = new GameFrameworkLinkedList<GameFrameworkModule>();

        /// <summary>
        /// 所有遊戲框架模塊輪詢。
        /// </summary>
        /// <param name="elapseSeconds">邏輯流逝時間,以秒爲單位。</param>
        /// <param name="realElapseSeconds">真實流逝時間,以秒爲單位。</param>
        public static void Update(float elapseSeconds, float realElapseSeconds)
        {
            foreach (GameFrameworkModule module in s_GameFrameworkModules)
            {
                module.Update(elapseSeconds, realElapseSeconds);
            }
        }

        /// <summary>
        /// 關閉並清理所有遊戲框架模塊。
        /// </summary>
        public static void Shutdown()
        {
            for (LinkedListNode<GameFrameworkModule> current = s_GameFrameworkModules.Last; current != null; current = current.Previous)
            {
                current.Value.Shutdown();
            }

            s_GameFrameworkModules.Clear();
            ReferencePool.ClearAll();
            GameFrameworkLog.SetLogHelper(null);
        }

        /// <summary>
        /// 獲取遊戲框架模塊。
        /// </summary>
        /// <typeparam name="T">要獲取的遊戲框架模塊類型。</typeparam>
        /// <returns>要獲取的遊戲框架模塊。</returns>
        /// <remarks>如果要獲取的遊戲框架模塊不存在,則自動創建該遊戲框架模塊。</remarks>
        public static T GetModule<T>() where T : class
        {
            Type interfaceType = typeof(T);
            if (!interfaceType.IsInterface)
            {
                throw new GameFrameworkException(Utility.Text.Format("You must get module by interface, but '{0}' is not.", interfaceType.FullName));
            }

            if (!interfaceType.FullName.StartsWith("GameFramework."))
            {
                throw new GameFrameworkException(Utility.Text.Format("You must get a Game Framework module, but '{0}' is not.", interfaceType.FullName));
            }

            string moduleName = Utility.Text.Format("{0}.{1}", interfaceType.Namespace, interfaceType.Name.Substring(1));
            Type moduleType = Type.GetType(moduleName);
            if (moduleType == null)
            {
                throw new GameFrameworkException(Utility.Text.Format("Can not find Game Framework module type '{0}'.", moduleName));
            }

            return GetModule(moduleType) as T;
        }

        /// <summary>
        /// 獲取遊戲框架模塊。
        /// </summary>
        /// <param name="moduleType">要獲取的遊戲框架模塊類型。</param>
        /// <returns>要獲取的遊戲框架模塊。</returns>
        /// <remarks>如果要獲取的遊戲框架模塊不存在,則自動創建該遊戲框架模塊。</remarks>
        private static GameFrameworkModule GetModule(Type moduleType)
        {
            foreach (GameFrameworkModule module in s_GameFrameworkModules)
            {
                if (module.GetType() == moduleType)
                {
                    return module;
                }
            }

            return CreateModule(moduleType);
        }

        /// <summary>
        /// 創建遊戲框架模塊。
        /// </summary>
        /// <param name="moduleType">要創建的遊戲框架模塊類型。</param>
        /// <returns>要創建的遊戲框架模塊。</returns>
        private static GameFrameworkModule CreateModule(Type moduleType)
        {
            GameFrameworkModule module = (GameFrameworkModule)Activator.CreateInstance(moduleType);
            if (module == null)
            {
                throw new GameFrameworkException(Utility.Text.Format("Can not create module '{0}'.", moduleType.FullName));
            }

            LinkedListNode<GameFrameworkModule> current = s_GameFrameworkModules.First;
            while (current != null)
            {
                if (module.Priority > current.Value.Priority)
                {
                    break;
                }

                current = current.Next;
            }

            if (current != null)
            {
                s_GameFrameworkModules.AddBefore(current, module);
            }
            else
            {
                s_GameFrameworkModules.AddLast(module);
            }

            return module;
        }
    }
}

通過腳本可以獲取到需要的管理器,並且也有框架清理釋放函數。還有Update函數可以輪詢每個管理器的更新函數,有沒有發現這個類是靜態的,而且在動態鏈接庫裏的。所以大膽猜測一定是某個腳本繼承了Mono腳本里然後去調用更新函數(雖然查看一下Update函數所有引用就知道了),這樣動態鏈接庫裏的每個管理器都接入了更新方案。GameFrameworkModule列表是各位在遊戲框架入口類裏可以看到的,所以如果有什麼和遊戲相關的管理器需要擴展時,就必須要繼承GameFrameworkModule纔可以,可以看一下框架有哪些類是繼承了模塊抽象類的,具體截圖如下:

目測十八大金鋼都在,爲什麼名字有如此多重複?如果有這種疑問的童靴,可以去看一下partial限定字作用,這樣就可以知道如何做到把類代碼分開實現的。至於爲什麼作者把這些管理器腳本分成這麼多份,因爲管理器下面有分成一些功能,如果把代碼全部集中在一起太low,而且類代碼可能又長又寬, 導致看起來很不清晰。像我這種菜鳥以前都是集中起來,慫什麼?幹就完事了。還有一點要知道,如果往框架添加遊戲模塊時,管理器代碼集中寫問題倒是不大。但是添加新模塊時不能引用Unity相關的庫,如果發現添加新模塊時引用了Unity相關庫,在下就錘爆在座各位的狗頭🙄。

這樣怎麼辦呢?動態鏈接庫代碼如何做到與Unity中代碼進行對接,其實每個管理器可以有很多個代理類,比如資源管理器可以有很多個資源加載代理類,下載管理器可以有很多下載代理類,命名差不多是xxxHelper,通俗的說它們的領導就是這些管理器,各位只需要在動態鏈接庫裏擬定一下接口,在Unity中具體實現接口即可,具體是什麼意思?各位參照設置管理器(SettingManager)就知道了,首先設置代理接口代碼如下:

namespace GameFramework.Setting
{
    /// <summary>
    /// 遊戲配置輔助器接口。
    /// </summary>
    public interface ISettingHelper
    {
        /// <summary>
        /// 加載遊戲配置。
        /// </summary>
        /// <returns>是否加載遊戲配置成功。</returns>
        bool Load();

        /// <summary>
        /// 保存遊戲配置。
        /// </summary>
        /// <returns>是否保存遊戲配置成功。</returns>
        bool Save();

        /// <summary>
        /// 檢查是否存在指定遊戲配置項。
        /// </summary>
        /// <param name="settingName">要檢查遊戲配置項的名稱。</param>
        /// <returns>指定的遊戲配置項是否存在。</returns>
        bool HasSetting(string settingName);

        /// <summary>
        /// 移除指定遊戲配置項。
        /// </summary>
        /// <param name="settingName">要移除遊戲配置項的名稱。</param>
        void RemoveSetting(string settingName);

        /// <summary>
        /// 清空所有遊戲配置項。
        /// </summary>
        void RemoveAllSettings();

        /// <summary>
        /// 從指定遊戲配置項中讀取布爾值。
        /// </summary>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <returns>讀取的布爾值。</returns>
        bool GetBool(string settingName);

        /// <summary>
        /// 從指定遊戲配置項中讀取布爾值。
        /// </summary>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <param name="defaultValue">當指定的遊戲配置項不存在時,返回此默認值。</param>
        /// <returns>讀取的布爾值。</returns>
        bool GetBool(string settingName, bool defaultValue);

        /// <summary>
        /// 向指定遊戲配置項寫入布爾值。
        /// </summary>
        /// <param name="settingName">要寫入遊戲配置項的名稱。</param>
        /// <param name="value">要寫入的布爾值。</param>
        void SetBool(string settingName, bool value);

        /// <summary>
        /// 從指定遊戲配置項中讀取整數值。
        /// </summary>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <returns>讀取的整數值。</returns>
        int GetInt(string settingName);

        /// <summary>
        /// 從指定遊戲配置項中讀取整數值。
        /// </summary>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <param name="defaultValue">當指定的遊戲配置項不存在時,返回此默認值。</param>
        /// <returns>讀取的整數值。</returns>
        int GetInt(string settingName, int defaultValue);

        /// <summary>
        /// 向指定遊戲配置項寫入整數值。
        /// </summary>
        /// <param name="settingName">要寫入遊戲配置項的名稱。</param>
        /// <param name="value">要寫入的整數值。</param>
        void SetInt(string settingName, int value);

        /// <summary>
        /// 從指定遊戲配置項中讀取浮點數值。
        /// </summary>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <returns>讀取的浮點數值。</returns>
        float GetFloat(string settingName);

        /// <summary>
        /// 從指定遊戲配置項中讀取浮點數值。
        /// </summary>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <param name="defaultValue">當指定的遊戲配置項不存在時,返回此默認值。</param>
        /// <returns>讀取的浮點數值。</returns>
        float GetFloat(string settingName, float defaultValue);

        /// <summary>
        /// 向指定遊戲配置項寫入浮點數值。
        /// </summary>
        /// <param name="settingName">要寫入遊戲配置項的名稱。</param>
        /// <param name="value">要寫入的浮點數值。</param>
        void SetFloat(string settingName, float value);

        /// <summary>
        /// 從指定遊戲配置項中讀取字符串值。
        /// </summary>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <returns>讀取的字符串值。</returns>
        string GetString(string settingName);

        /// <summary>
        /// 從指定遊戲配置項中讀取字符串值。
        /// </summary>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <param name="defaultValue">當指定的遊戲配置項不存在時,返回此默認值。</param>
        /// <returns>讀取的字符串值。</returns>
        string GetString(string settingName, string defaultValue);

        /// <summary>
        /// 向指定遊戲配置項寫入字符串值。
        /// </summary>
        /// <param name="settingName">要寫入遊戲配置項的名稱。</param>
        /// <param name="value">要寫入的字符串值。</param>
        void SetString(string settingName, string value);

        /// <summary>
        /// 從指定遊戲配置項中讀取對象。
        /// </summary>
        /// <typeparam name="T">要讀取對象的類型。</typeparam>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <returns>讀取的對象。</returns>
        T GetObject<T>(string settingName);

        /// <summary>
        /// 從指定遊戲配置項中讀取對象。
        /// </summary>
        /// <param name="objectType">要讀取對象的類型。</param>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <returns>讀取的對象。</returns>
        object GetObject(Type objectType, string settingName);

        /// <summary>
        /// 從指定遊戲配置項中讀取對象。
        /// </summary>
        /// <typeparam name="T">要讀取對象的類型。</typeparam>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <param name="defaultObj">當指定的遊戲配置項不存在時,返回此默認對象。</param>
        /// <returns>讀取的對象。</returns>
        T GetObject<T>(string settingName, T defaultObj);

        /// <summary>
        /// 從指定遊戲配置項中讀取對象。
        /// </summary>
        /// <param name="objectType">要讀取對象的類型。</param>
        /// <param name="settingName">要獲取遊戲配置項的名稱。</param>
        /// <param name="defaultObj">當指定的遊戲配置項不存在時,返回此默認對象。</param>
        /// <returns>讀取的對象。</returns>
        object GetObject(Type objectType, string settingName, object defaultObj);

        /// <summary>
        /// 向指定遊戲配置項寫入對象。
        /// </summary>
        /// <typeparam name="T">要寫入對象的類型。</typeparam>
        /// <param name="settingName">要寫入遊戲配置項的名稱。</param>
        /// <param name="obj">要寫入的對象。</param>
        void SetObject<T>(string settingName, T obj);

        /// <summary>
        /// 向指定遊戲配置項寫入對象。
        /// </summary>
        /// <param name="settingName">要寫入遊戲配置項的名稱。</param>
        /// <param name="obj">要寫入的對象。</param>
        void SetObject(string settingName, object obj);
    }
}

這裏設置代理接口就是在要打包成動態鏈接庫的解決方案裏擬定的,因爲每個管理器都需要持有它們的代理類,所以設置管理器類裏一定有的參數就是ISettingHelper SettingHelper,繼續?看看Unity中如何實現ISettingHelper接口的,部分代碼如下:

public abstract class SettingHelperBase : MonoBehaviour, ISettingHelper

所以我們需要添加其他設置方案時(比如將本地設置保存成byte文件),繼承接口並且去實現即可,作者這裏默認實現的設置代理類是對Unity的PlayerPrefs的一層封裝,如此去設計GF簡直是天才之作(先把作者吹一波),這樣就可以具體實現功能的插拔,比如有天不想使用PlayerPrefs去實現設置模塊保存數據,這樣再額外實現一個設置代理類即可。

  •  各大管理器組件又是幹嘛的?

這個故事還是從GameEntry腳本開始吧!盯着電腦屏幕半天,感覺這個腳本和上面開始說的那個腳本簡直神似???只不過這個腳本在Unity解決方案裏的,多了註冊模塊的函數,用來將管理器組件添加到列表裏,缺少了更新函數(想想也對的,都是在Unity中的解決方案了,還要什麼更新函數?)。因爲組件是掛載到Unity實際對象上的,所以GF框架在Awake時直接去註冊了,而動態鏈接庫裏是獲取管理器時,如果無法找到管理器的話就馬上創建管理器然後保存到列表裏,還是來看看組件們是如何被註冊吧,具體代碼如下:

namespace UnityGameFramework.Runtime
{
    /// <summary>
    /// 遊戲框架組件抽象類。
    /// </summary>
    public abstract class GameFrameworkComponent : MonoBehaviour
    {
        /// <summary>
        /// 遊戲框架組件初始化。
        /// </summary>
        protected virtual void Awake()
        {
            GameEntry.RegisterComponent(this);
        }
    }
}

只要管理器組件繼承了組件抽象類時,就可以在Awake時註冊到列表裏,當然如果要重寫Awake函數時也是沒有問題的 ,但是要記住在Awake開頭調用寫上以下代碼:

base.Awake();

可能各位會說這個就不必多說了,這個不是基礎知識嘛,那個那個...,就隨便提一嘴而已,各位接下來就來分析一下各個管理器組件腳本的具體的用處是什麼?動態鏈接庫裏的管理器總需要有去調用和初始化參數的腳本,這個時候就需要像月老牽線一樣,天下情侶都是一對的(如果你們敢說國外有些地方都可以一夫多妻,我只能對各位說渣男!),所以一個管理器組件對應上一個管理器,來看看設置管理器組件到底做了什麼事情,具體代碼如下:

using GameFramework.Resource;
using System;
using System.Collections.Generic;

namespace GameFramework.Scene
{
    /// <summary>
    /// 場景管理器。
    /// </summary>
    internal sealed class SceneManager : GameFrameworkModule, ISceneManager
    {
        private readonly List<string> m_LoadedSceneAssetNames;
        private readonly List<string> m_LoadingSceneAssetNames;
        private readonly List<string> m_UnloadingSceneAssetNames;
        private readonly LoadSceneCallbacks m_LoadSceneCallbacks;
        private readonly UnloadSceneCallbacks m_UnloadSceneCallbacks;
        private IResourceManager m_ResourceManager;
        private EventHandler<LoadSceneSuccessEventArgs> m_LoadSceneSuccessEventHandler;
        private EventHandler<LoadSceneFailureEventArgs> m_LoadSceneFailureEventHandler;
        private EventHandler<LoadSceneUpdateEventArgs> m_LoadSceneUpdateEventHandler;
        private EventHandler<LoadSceneDependencyAssetEventArgs> m_LoadSceneDependencyAssetEventHandler;
        private EventHandler<UnloadSceneSuccessEventArgs> m_UnloadSceneSuccessEventHandler;
        private EventHandler<UnloadSceneFailureEventArgs> m_UnloadSceneFailureEventHandler;

        /// <summary>
        /// 初始化場景管理器的新實例。
        /// </summary>
        public SceneManager()
        {
            m_LoadedSceneAssetNames = new List<string>();
            m_LoadingSceneAssetNames = new List<string>();
            m_UnloadingSceneAssetNames = new List<string>();
            m_LoadSceneCallbacks = new LoadSceneCallbacks(LoadSceneSuccessCallback, LoadSceneFailureCallback, LoadSceneUpdateCallback, LoadSceneDependencyAssetCallback);
            m_UnloadSceneCallbacks = new UnloadSceneCallbacks(UnloadSceneSuccessCallback, UnloadSceneFailureCallback);
            m_ResourceManager = null;
            m_LoadSceneSuccessEventHandler = null;
            m_LoadSceneFailureEventHandler = null;
            m_LoadSceneUpdateEventHandler = null;
            m_LoadSceneDependencyAssetEventHandler = null;
            m_UnloadSceneSuccessEventHandler = null;
            m_UnloadSceneFailureEventHandler = null;
        }

        /// <summary>
        /// 獲取遊戲框架模塊優先級。
        /// </summary>
        /// <remarks>優先級較高的模塊會優先輪詢,並且關閉操作會後進行。</remarks>
        internal override int Priority
        {
            get
            {
                return 60;
            }
        }

        /// <summary>
        /// 加載場景成功事件。
        /// </summary>
        public event EventHandler<LoadSceneSuccessEventArgs> LoadSceneSuccess
        {
            add
            {
                m_LoadSceneSuccessEventHandler += value;
            }
            remove
            {
                m_LoadSceneSuccessEventHandler -= value;
            }
        }

        /// <summary>
        /// 加載場景失敗事件。
        /// </summary>
        public event EventHandler<LoadSceneFailureEventArgs> LoadSceneFailure
        {
            add
            {
                m_LoadSceneFailureEventHandler += value;
            }
            remove
            {
                m_LoadSceneFailureEventHandler -= value;
            }
        }

        /// <summary>
        /// 加載場景更新事件。
        /// </summary>
        public event EventHandler<LoadSceneUpdateEventArgs> LoadSceneUpdate
        {
            add
            {
                m_LoadSceneUpdateEventHandler += value;
            }
            remove
            {
                m_LoadSceneUpdateEventHandler -= value;
            }
        }

        /// <summary>
        /// 加載場景時加載依賴資源事件。
        /// </summary>
        public event EventHandler<LoadSceneDependencyAssetEventArgs> LoadSceneDependencyAsset
        {
            add
            {
                m_LoadSceneDependencyAssetEventHandler += value;
            }
            remove
            {
                m_LoadSceneDependencyAssetEventHandler -= value;
            }
        }

        /// <summary>
        /// 卸載場景成功事件。
        /// </summary>
        public event EventHandler<UnloadSceneSuccessEventArgs> UnloadSceneSuccess
        {
            add
            {
                m_UnloadSceneSuccessEventHandler += value;
            }
            remove
            {
                m_UnloadSceneSuccessEventHandler -= value;
            }
        }

        /// <summary>
        /// 卸載場景失敗事件。
        /// </summary>
        public event EventHandler<UnloadSceneFailureEventArgs> UnloadSceneFailure
        {
            add
            {
                m_UnloadSceneFailureEventHandler += value;
            }
            remove
            {
                m_UnloadSceneFailureEventHandler -= value;
            }
        }

        /// <summary>
        /// 場景管理器輪詢。
        /// </summary>
        /// <param name="elapseSeconds">邏輯流逝時間,以秒爲單位。</param>
        /// <param name="realElapseSeconds">真實流逝時間,以秒爲單位。</param>
        internal override void Update(float elapseSeconds, float realElapseSeconds)
        {
        }

        /// <summary>
        /// 關閉並清理場景管理器。
        /// </summary>
        internal override void Shutdown()
        {
            string[] loadedSceneAssetNames = m_LoadedSceneAssetNames.ToArray();
            foreach (string loadedSceneAssetName in loadedSceneAssetNames)
            {
                if (SceneIsUnloading(loadedSceneAssetName))
                {
                    continue;
                }

                UnloadScene(loadedSceneAssetName);
            }

            m_LoadedSceneAssetNames.Clear();
            m_LoadingSceneAssetNames.Clear();
            m_UnloadingSceneAssetNames.Clear();
        }

        /// <summary>
        /// 設置資源管理器。
        /// </summary>
        /// <param name="resourceManager">資源管理器。</param>
        public void SetResourceManager(IResourceManager resourceManager)
        {
            if (resourceManager == null)
            {
                throw new GameFrameworkException("Resource manager is invalid.");
            }

            m_ResourceManager = resourceManager;
        }

        /// <summary>
        /// 獲取場景是否已加載。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        /// <returns>場景是否已加載。</returns>
        public bool SceneIsLoaded(string sceneAssetName)
        {
            if (string.IsNullOrEmpty(sceneAssetName))
            {
                throw new GameFrameworkException("Scene asset name is invalid.");
            }

            return m_LoadedSceneAssetNames.Contains(sceneAssetName);
        }

        /// <summary>
        /// 獲取已加載場景的資源名稱。
        /// </summary>
        /// <returns>已加載場景的資源名稱。</returns>
        public string[] GetLoadedSceneAssetNames()
        {
            return m_LoadedSceneAssetNames.ToArray();
        }

        /// <summary>
        /// 獲取已加載場景的資源名稱。
        /// </summary>
        /// <param name="results">已加載場景的資源名稱。</param>
        public void GetLoadedSceneAssetNames(List<string> results)
        {
            if (results == null)
            {
                throw new GameFrameworkException("Results is invalid.");
            }

            results.Clear();
            results.AddRange(m_LoadedSceneAssetNames);
        }

        /// <summary>
        /// 獲取場景是否正在加載。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        /// <returns>場景是否正在加載。</returns>
        public bool SceneIsLoading(string sceneAssetName)
        {
            if (string.IsNullOrEmpty(sceneAssetName))
            {
                throw new GameFrameworkException("Scene asset name is invalid.");
            }

            return m_LoadingSceneAssetNames.Contains(sceneAssetName);
        }

        /// <summary>
        /// 獲取正在加載場景的資源名稱。
        /// </summary>
        /// <returns>正在加載場景的資源名稱。</returns>
        public string[] GetLoadingSceneAssetNames()
        {
            return m_LoadingSceneAssetNames.ToArray();
        }

        /// <summary>
        /// 獲取正在加載場景的資源名稱。
        /// </summary>
        /// <param name="results">正在加載場景的資源名稱。</param>
        public void GetLoadingSceneAssetNames(List<string> results)
        {
            if (results == null)
            {
                throw new GameFrameworkException("Results is invalid.");
            }

            results.Clear();
            results.AddRange(m_LoadingSceneAssetNames);
        }

        /// <summary>
        /// 獲取場景是否正在卸載。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        /// <returns>場景是否正在卸載。</returns>
        public bool SceneIsUnloading(string sceneAssetName)
        {
            if (string.IsNullOrEmpty(sceneAssetName))
            {
                throw new GameFrameworkException("Scene asset name is invalid.");
            }

            return m_UnloadingSceneAssetNames.Contains(sceneAssetName);
        }

        /// <summary>
        /// 獲取正在卸載場景的資源名稱。
        /// </summary>
        /// <returns>正在卸載場景的資源名稱。</returns>
        public string[] GetUnloadingSceneAssetNames()
        {
            return m_UnloadingSceneAssetNames.ToArray();
        }

        /// <summary>
        /// 獲取正在卸載場景的資源名稱。
        /// </summary>
        /// <param name="results">正在卸載場景的資源名稱。</param>
        public void GetUnloadingSceneAssetNames(List<string> results)
        {
            if (results == null)
            {
                throw new GameFrameworkException("Results is invalid.");
            }

            results.Clear();
            results.AddRange(m_UnloadingSceneAssetNames);
        }

        /// <summary>
        /// 加載場景。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        public void LoadScene(string sceneAssetName)
        {
            LoadScene(sceneAssetName, Constant.DefaultPriority, null);
        }

        /// <summary>
        /// 加載場景。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        /// <param name="priority">加載場景資源的優先級。</param>
        public void LoadScene(string sceneAssetName, int priority)
        {
            LoadScene(sceneAssetName, priority, null);
        }

        /// <summary>
        /// 加載場景。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        /// <param name="userData">用戶自定義數據。</param>
        public void LoadScene(string sceneAssetName, object userData)
        {
            LoadScene(sceneAssetName, Constant.DefaultPriority, userData);
        }

        /// <summary>
        /// 加載場景。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        /// <param name="priority">加載場景資源的優先級。</param>
        /// <param name="userData">用戶自定義數據。</param>
        public void LoadScene(string sceneAssetName, int priority, object userData)
        {
            if (string.IsNullOrEmpty(sceneAssetName))
            {
                throw new GameFrameworkException("Scene asset name is invalid.");
            }

            if (m_ResourceManager == null)
            {
                throw new GameFrameworkException("You must set resource manager first.");
            }

            if (SceneIsUnloading(sceneAssetName))
            {
                throw new GameFrameworkException(Utility.Text.Format("Scene asset '{0}' is being unloaded.", sceneAssetName));
            }

            if (SceneIsLoading(sceneAssetName))
            {
                throw new GameFrameworkException(Utility.Text.Format("Scene asset '{0}' is being loaded.", sceneAssetName));
            }

            if (SceneIsLoaded(sceneAssetName))
            {
                throw new GameFrameworkException(Utility.Text.Format("Scene asset '{0}' is already loaded.", sceneAssetName));
            }

            m_LoadingSceneAssetNames.Add(sceneAssetName);
            m_ResourceManager.LoadScene(sceneAssetName, priority, m_LoadSceneCallbacks, userData);
        }

        /// <summary>
        /// 卸載場景。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        public void UnloadScene(string sceneAssetName)
        {
            UnloadScene(sceneAssetName, null);
        }

        /// <summary>
        /// 卸載場景。
        /// </summary>
        /// <param name="sceneAssetName">場景資源名稱。</param>
        /// <param name="userData">用戶自定義數據。</param>
        public void UnloadScene(string sceneAssetName, object userData)
        {
            if (string.IsNullOrEmpty(sceneAssetName))
            {
                throw new GameFrameworkException("Scene asset name is invalid.");
            }

            if (m_ResourceManager == null)
            {
                throw new GameFrameworkException("You must set resource manager first.");
            }

            if (SceneIsUnloading(sceneAssetName))
            {
                throw new GameFrameworkException(Utility.Text.Format("Scene asset '{0}' is being unloaded.", sceneAssetName));
            }

            if (SceneIsLoading(sceneAssetName))
            {
                throw new GameFrameworkException(Utility.Text.Format("Scene asset '{0}' is being loaded.", sceneAssetName));
            }

            if (!SceneIsLoaded(sceneAssetName))
            {
                throw new GameFrameworkException(Utility.Text.Format("Scene asset '{0}' is not loaded yet.", sceneAssetName));
            }

            m_UnloadingSceneAssetNames.Add(sceneAssetName);
            m_ResourceManager.UnloadScene(sceneAssetName, m_UnloadSceneCallbacks, userData);
        }

        private void LoadSceneSuccessCallback(string sceneAssetName, float duration, object userData)
        {
            m_LoadingSceneAssetNames.Remove(sceneAssetName);
            m_LoadedSceneAssetNames.Add(sceneAssetName);
            if (m_LoadSceneSuccessEventHandler != null)
            {
                LoadSceneSuccessEventArgs loadSceneSuccessEventArgs = LoadSceneSuccessEventArgs.Create(sceneAssetName, duration, userData);
                m_LoadSceneSuccessEventHandler(this, loadSceneSuccessEventArgs);
                ReferencePool.Release(loadSceneSuccessEventArgs);
            }
        }

        private void LoadSceneFailureCallback(string sceneAssetName, LoadResourceStatus status, string errorMessage, object userData)
        {
            m_LoadingSceneAssetNames.Remove(sceneAssetName);
            string appendErrorMessage = Utility.Text.Format("Load scene failure, scene asset name '{0}', status '{1}', error message '{2}'.", sceneAssetName, status.ToString(), errorMessage);
            if (m_LoadSceneFailureEventHandler != null)
            {
                LoadSceneFailureEventArgs loadSceneFailureEventArgs = LoadSceneFailureEventArgs.Create(sceneAssetName, appendErrorMessage, userData);
                m_LoadSceneFailureEventHandler(this, loadSceneFailureEventArgs);
                ReferencePool.Release(loadSceneFailureEventArgs);
                return;
            }

            throw new GameFrameworkException(appendErrorMessage);
        }

        private void LoadSceneUpdateCallback(string sceneAssetName, float progress, object userData)
        {
            if (m_LoadSceneUpdateEventHandler != null)
            {
                LoadSceneUpdateEventArgs loadSceneUpdateEventArgs = LoadSceneUpdateEventArgs.Create(sceneAssetName, progress, userData);
                m_LoadSceneUpdateEventHandler(this, loadSceneUpdateEventArgs);
                ReferencePool.Release(loadSceneUpdateEventArgs);
            }
        }

        private void LoadSceneDependencyAssetCallback(string sceneAssetName, string dependencyAssetName, int loadedCount, int totalCount, object userData)
        {
            if (m_LoadSceneDependencyAssetEventHandler != null)
            {
                LoadSceneDependencyAssetEventArgs loadSceneDependencyAssetEventArgs = LoadSceneDependencyAssetEventArgs.Create(sceneAssetName, dependencyAssetName, loadedCount, totalCount, userData);
                m_LoadSceneDependencyAssetEventHandler(this, loadSceneDependencyAssetEventArgs);
                ReferencePool.Release(loadSceneDependencyAssetEventArgs);
            }
        }

        private void UnloadSceneSuccessCallback(string sceneAssetName, object userData)
        {
            m_UnloadingSceneAssetNames.Remove(sceneAssetName);
            m_LoadedSceneAssetNames.Remove(sceneAssetName);
            if (m_UnloadSceneSuccessEventHandler != null)
            {
                UnloadSceneSuccessEventArgs unloadSceneSuccessEventArgs = UnloadSceneSuccessEventArgs.Create(sceneAssetName, userData);
                m_UnloadSceneSuccessEventHandler(this, unloadSceneSuccessEventArgs);
                ReferencePool.Release(unloadSceneSuccessEventArgs);
            }
        }

        private void UnloadSceneFailureCallback(string sceneAssetName, object userData)
        {
            m_UnloadingSceneAssetNames.Remove(sceneAssetName);
            if (m_UnloadSceneFailureEventHandler != null)
            {
                UnloadSceneFailureEventArgs unloadSceneFailureEventArgs = UnloadSceneFailureEventArgs.Create(sceneAssetName, userData);
                m_UnloadSceneFailureEventHandler(this, unloadSceneFailureEventArgs);
                ReferencePool.Release(unloadSceneFailureEventArgs);
                return;
            }

            throw new GameFrameworkException(Utility.Text.Format("Unload scene failure, scene asset name '{0}'.", sceneAssetName));
        }
    }
}

看來組件做的事情也不少,動態鏈接庫裏管理器持有代理對象,而管理器組件持有着管理器對象,可能有些人會說爲什麼就一定要使用動態鏈接庫裏去調用,就不能在Unity解決方案裏直接寫嘛?可以是可以,但是主要的代碼寫到動態鏈接裏,性能方面會更加好,管理器組件持有管理器去調用方法即可。給大家畫個比較簡單的GF架構圖,讓各位更好的理解設計思路。結構思路圖如下:

這樣需要添加新管理器時,就可以按照這個思路去設計了,武林祕籍已經傾囊傳授給各位了,接下來各位去看看十八模塊的事件是如何觸發起來的,比如設置成功以後需要有一個成功的回調函數。

  • 管理器事件觸發機制

每個管理器都需要有一定回調函數,通知它是否成功的回調,成功或失敗之後總需要乾點什麼事情,在管理器組件裏寥寥無幾的事件代碼,只需要在Awake裏給它添加到一個多播事件裏即可,以場景管理器組件(SceneComponent)作爲例子,具體代碼如下:

            m_SceneManager.LoadSceneSuccess += OnLoadSceneSuccess;
            m_SceneManager.LoadSceneFailure += OnLoadSceneFailure;

查找所有引用看一下到底那裏調用這個多播事件,先給各位截個圖吧,畢竟這個算是一個轉折點了,諸君請看下圖:

可以看到事件的回調時放到這個函數裏了,但是具體這個函數在那裏調用呢?讓我們繼續查找一下引用,發現一個事件管理器把所有回調函數全部封裝到一個對象裏了,具體代碼段如下:

m_LoadSceneCallbacks = new LoadSceneCallbacks(LoadSceneSuccessCallback, LoadSceneFailureCallback, LoadSceneUpdateCallback, LoadSceneDependencyAssetCallback);

所以需要找到m_LoadSceneCallbacks對象引用即可,最後的最後!!!可以發現的是把對象交給任務池管理器了,添加到任務管理池裏面,大概是放到類似列表的數據結構裏進行輪迴,先給各位看一下跳轉到的添加到任務池的代碼段。

            public void LoadScene(string sceneAssetName, int priority, LoadSceneCallbacks loadSceneCallbacks, object userData)
            {
                ResourceInfo? resourceInfo = null;
                string[] dependencyAssetNames = null;

                if (!CheckAsset(sceneAssetName, out resourceInfo, out dependencyAssetNames))
                {
                    string errorMessage = Utility.Text.Format("Can not load scene '{0}'.", sceneAssetName);
                    if (loadSceneCallbacks.LoadSceneFailureCallback != null)
                    {
                        loadSceneCallbacks.LoadSceneFailureCallback(sceneAssetName, LoadResourceStatus.NotReady, errorMessage, userData);
                        return;
                    }

                    throw new GameFrameworkException(errorMessage);
                }

                LoadSceneTask mainTask = LoadSceneTask.Create(sceneAssetName, priority, resourceInfo.Value, dependencyAssetNames, loadSceneCallbacks, userData);
                foreach (string dependencyAssetName in dependencyAssetNames)
                {
                    if (!LoadDependencyAsset(dependencyAssetName, priority, mainTask, userData))
                    {
                        string errorMessage = Utility.Text.Format("Can not load dependency asset '{0}' when load scene '{1}'.", dependencyAssetName, sceneAssetName);
                        if (loadSceneCallbacks.LoadSceneFailureCallback != null)
                        {
                            loadSceneCallbacks.LoadSceneFailureCallback(sceneAssetName, LoadResourceStatus.DependencyError, errorMessage, userData);
                            return;
                        }

                        throw new GameFrameworkException(errorMessage);
                    }
                }

                m_TaskPool.AddTask(mainTask);
            }

至於TaskPool裏代碼確實是在Update裏執行,任務池不斷的更新,執行任務以後就把任務對象從列表裏移除,給各位看一下任務池的Update裏是如何執行的,調用截圖如下:

 進入任務隊列以後,實際調用的函數是通過ProcessRunningTasks,這裏又有一個概念就是任務代理類,說起來有點繁瑣,這裏就先簡單的畫龍點睛一下,具體的調用原理可以看一下在下寫的任務池原理分析,首先給各位看看ProcessRunningTasks函數到底做了那些事情吧!具體代碼如下:

        private void ProcessRunningTasks(float elapseSeconds, float realElapseSeconds)
        {
            LinkedListNode<ITaskAgent<T>> current = m_WorkingAgents.First;
            while (current != null)
            {
                T task = current.Value.Task;
                if (!task.Done)
                {
                    current.Value.Update(elapseSeconds, realElapseSeconds);
                    current = current.Next;
                    continue;
                }

                LinkedListNode<ITaskAgent<T>> next = current.Next;
                current.Value.Reset();
                m_FreeAgents.Push(current.Value);
                m_WorkingAgents.Remove(current);
                ReferencePool.Release(task);
                current = next;
            }
        }

到這裏的current.Value.Update就是調到管理器需要執行的回調函數(m_LoadSceneCallbacks),各位是不是慢慢忘記了標題...好像這麼調着調着,代碼就講到這裏了,不要驚慌!只需要看過任務池文章,你的思路就會漸漸清楚了(雖然寫這篇文章時,任務池文章還沒有開始寫,哈哈哈哈哈哈)。

這裏Update函數具體調用代碼給各位看一下,具體是在DefaultLoadResourceAgentHelper裏,如圖所示:

到這裏算是結束了...還有一點差點忘記了,就是調用半天了,其實回調函數裏只是調用m_EventComponent.Fire函數,這個函數又是什麼作用?各位可以到以下傳送門:

https://blog.csdn.net/m0_37920739/article/details/104723210

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