前言
這裏的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函數,這個函數又是什麼作用?各位可以到以下傳送門: