GameFramework Demo StarForce详细解析(3)

废话不多说,让我们看看ProcedurePreload流程又做了些什么。

ProcedurePreload

OnEnter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);

GameEntry.Event.Subscribe(LoadConfigSuccessEventArgs.EventId, OnLoadConfigSuccess);
GameEntry.Event.Subscribe(LoadConfigFailureEventArgs.EventId, OnLoadConfigFailure);
GameEntry.Event.Subscribe(LoadDataTableSuccessEventArgs.EventId, OnLoadDataTableSuccess);
GameEntry.Event.Subscribe(LoadDataTableFailureEventArgs.EventId, OnLoadDataTableFailure);
GameEntry.Event.Subscribe(LoadDictionarySuccessEventArgs.EventId, OnLoadDictionarySuccess);
GameEntry.Event.Subscribe(LoadDictionaryFailureEventArgs.EventId, OnLoadDictionaryFailure);

m_LoadedFlag.Clear();

PreloadResources();
}

因为GF的资源加载都是用的异步的方式,所以在OnEnter中对加载成功失败用Event进行监听。分别为:加载全局配置、加载数据表、加载字典。
m_LoadedFlag是一个Dictionary<string, bool>的字典,用来存储对应string的资源是否加载完成的标识变量bool。
接下来进入PreloadResources。

PreloadResources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void PreloadResources()
{
// Preload configs
LoadConfig("DefaultConfig");

// Preload data tables
foreach (string dataTableName in DataTableNames)
{
LoadDataTable(dataTableName);
}

// Preload dictionaries
LoadDictionary("Default");

// Preload fonts
LoadFont("MainFont");
}
LoadConfig

首先是LoadConfig,传入默认的config的名字”DefaultConfig”。

1
2
3
4
5
6
private void LoadConfig(string configName)
{
string configAssetName = AssetUtility.GetConfigAsset(configName, false);
m_LoadedFlag.Add(configAssetName, false);
GameEntry.Config.ReadData(configAssetName, this);
}

AssetUtility.GetConfigAsset方法将configName拼接成本地路径(编辑器模式下是用AssetDatabase进行加载的。)
将configAssetName添加到字典中,对应值设置为false。
再调用GFU的config组件的ReadData函数。

1
2
3
4
5
6
7
8
/// <summary>
/// 读取全局配置。
/// </summary>
/// <param name="configAssetName">全局配置资源名称。</param>
public void ReadData(string configAssetName)
{
m_ConfigManager.ReadData(configAssetName);
}

我们一路跟进去。ConfigManager的ReadData。

1
2
3
4
5
6
7
8
/// <summary>
/// 读取数据。
/// </summary>
/// <param name="dataAssetName">内容资源名称。</param>
public void ReadData(string dataAssetName)
{
ReadData(dataAssetName, Constant.DefaultPriority, null);
}

走到DataProvider的ReadData,就开始判断资源的类型做对应的加载操作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/// <summary>
/// 读取数据。
/// </summary>
/// <param name="dataAssetName">内容资源名称。</param>
/// <param name="priority">加载数据资源的优先级。</param>
/// <param name="userData">用户自定义数据。</param>
public void ReadData(string dataAssetName, int priority, object userData)
{
if (m_ResourceManager == null)
{
throw new GameFrameworkException("You must set resource manager first.");
}

if (m_DataProviderHelper == null)
{
throw new GameFrameworkException("You must set data provider helper first.");
}

HasAssetResult result = m_ResourceManager.HasAsset(dataAssetName);
switch (result)
{
case HasAssetResult.AssetOnDisk:
case HasAssetResult.AssetOnFileSystem:
m_ResourceManager.LoadAsset(dataAssetName, priority, m_LoadAssetCallbacks, userData);
break;

case HasAssetResult.BinaryOnDisk:
m_ResourceManager.LoadBinary(dataAssetName, m_LoadBinaryCallbacks, userData);
break;

case HasAssetResult.BinaryOnFileSystem:
int dataLength = m_ResourceManager.GetBinaryLength(dataAssetName);
EnsureCachedBytesSize(dataLength);
if (dataLength != m_ResourceManager.LoadBinaryFromFileSystem(dataAssetName, s_CachedBytes))
{
throw new GameFrameworkException(Utility.Text.Format("Load binary '{0}' from file system with internal error.", dataAssetName));
}

try
{
if (!m_DataProviderHelper.ReadData(m_Owner, dataAssetName, s_CachedBytes, 0, dataLength, userData))
{
throw new GameFrameworkException(Utility.Text.Format("Load data failure in data provider helper, data asset name '{0}'.", dataAssetName));
}

if (m_ReadDataSuccessEventHandler != null)
{
ReadDataSuccessEventArgs loadDataSuccessEventArgs = ReadDataSuccessEventArgs.Create(dataAssetName, 0f, userData);
m_ReadDataSuccessEventHandler(this, loadDataSuccessEventArgs);
ReferencePool.Release(loadDataSuccessEventArgs);
}
}
catch (Exception exception)
{
if (m_ReadDataFailureEventHandler != null)
{
ReadDataFailureEventArgs loadDataFailureEventArgs = ReadDataFailureEventArgs.Create(dataAssetName, exception.ToString(), userData);
m_ReadDataFailureEventHandler(this, loadDataFailureEventArgs);
ReferencePool.Release(loadDataFailureEventArgs);
return;
}

throw;
}

break;

default:
throw new GameFrameworkException(Utility.Text.Format("Data asset '{0}' is '{1}'.", dataAssetName, result));
}
}

我们就以第二种HasAssetResult.AssetOnFileSystem,继续研究。
来到GF下的ResourceManager,又是层层调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// 异步加载资源。
/// </summary>
/// <param name="assetName">要加载资源的名称。</param>
/// <param name="priority">加载资源的优先级。</param>
/// <param name="loadAssetCallbacks">加载资源回调函数集。</param>
/// <param name="userData">用户自定义数据。</param>
public void LoadAsset(string assetName, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData)
{
if (string.IsNullOrEmpty(assetName))
{
throw new GameFrameworkException("Asset name is invalid.");
}

if (loadAssetCallbacks == null)
{
throw new GameFrameworkException("Load asset callbacks is invalid.");
}

m_ResourceLoader.LoadAsset(assetName, null, priority, loadAssetCallbacks, userData);
}

最终还是调到ResourceManager的LoadAsset函数,传入资源名,类型,优先级,回调以及自定义的数据。
接下来的操作就比较多了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// 异步加载资源。
/// </summary>
/// <param name="assetName">要加载资源的名称。</param>
/// <param name="assetType">要加载资源的类型。</param>
/// <param name="priority">加载资源的优先级。</param>
/// <param name="loadAssetCallbacks">加载资源回调函数集。</param>
/// <param name="userData">用户自定义数据。</param>
public void LoadAsset(string assetName, Type assetType, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData)
{
ResourceInfo resourceInfo = null;
string[] dependencyAssetNames = null;
if (!CheckAsset(assetName, out resourceInfo, out dependencyAssetNames))
{
string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", assetName);
if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{
loadAssetCallbacks.LoadAssetFailureCallback(assetName, resourceInfo != null && !resourceInfo.Ready ? LoadResourceStatus.NotReady : LoadResourceStatus.NotExist, errorMessage, userData);
return;
}

throw new GameFrameworkException(errorMessage);
}

先调用自身的CheckAsset函数,检测是否有对应的资源信息及依赖资源信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private bool CheckAsset(string assetName, out ResourceInfo resourceInfo, out string[] dependencyAssetNames)
{
resourceInfo = null;
dependencyAssetNames = null;

if (string.IsNullOrEmpty(assetName))
{
return false;
}

AssetInfo assetInfo = m_ResourceManager.GetAssetInfo(assetName);
if (assetInfo == null)
{
return false;
}

resourceInfo = m_ResourceManager.GetResourceInfo(assetInfo.ResourceName);
if (resourceInfo == null)
{
return false;
}

dependencyAssetNames = assetInfo.GetDependencyAssetNames();
return m_ResourceManager.m_ResourceMode == ResourceMode.UpdatableWhilePlaying ? true : resourceInfo.Ready;
}

1.先调用GetAssetInfo函数,获得资源大致信息。(m_AssetName、m_ResourceName、m_DependencyAssetNames)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private AssetInfo GetAssetInfo(string assetName)
{
if (string.IsNullOrEmpty(assetName))
{
throw new GameFrameworkException("Asset name is invalid.");
}

if (m_AssetInfos == null)
{
return null;
}

AssetInfo assetInfo = null;
if (m_AssetInfos.TryGetValue(assetName, out assetInfo))
{
return assetInfo;
}

return null;
}

它会在初始化所有资源时缓存的一个字典m_AssetInfos中查找有没有对应的assetName,有则返回。
2.先调用GetResourceInfo函数,获得资源具体信息。(m_ResourceName、m_FileSystemName、m_LoadType、m_Length、m_HashCode、m_CompressedLength、m_StorageInReadOnly、m_Ready)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private ResourceInfo GetResourceInfo(ResourceName resourceName)
{
if (m_ResourceInfos == null)
{
return null;
}

ResourceInfo resourceInfo = null;
if (m_ResourceInfos.TryGetValue(resourceName, out resourceInfo))
{
return resourceInfo;
}

return null;
}

GetResourceInfo和GetAssetInfo同理。
return m_ResourceManager.m_ResourceMode == ResourceMode.UpdatableWhilePlaying ? true : resourceInfo.Ready;
如果一切资源都无误,会根据是否是使用时可更新的下载模式来决定返回值。在可更新的下载模式下,只有资源准备好时,才会返回true。
CheckAsset完毕后,继续判断是否是通过二进制方式加载。如果是,则加载失败。执行加载失败回调,并抛出一个GF错误。

1
2
3
4
5
6
7
8
9
10
11
if (resourceInfo.IsLoadFromBinary)
{
string errorMessage = Utility.Text.Format("Can not load asset '{0}' which is a binary asset.", assetName);
if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{
loadAssetCallbacks.LoadAssetFailureCallback(assetName, LoadResourceStatus.TypeError, errorMessage, userData);
return;
}

throw new GameFrameworkException(errorMessage);
}

一切校验无误后,会创建一个LoadAssetTask。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    LoadAssetTask mainTask = LoadAssetTask.Create(assetName, assetType, priority, resourceInfo, dependencyAssetNames, loadAssetCallbacks, 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 asset '{1}'.", dependencyAssetName, assetName);
if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{
loadAssetCallbacks.LoadAssetFailureCallback(assetName, LoadResourceStatus.DependencyError, errorMessage, userData);
return;
}

throw new GameFrameworkException(errorMessage);
}
}

m_TaskPool.AddTask(mainTask);
if (!resourceInfo.Ready)
{
m_ResourceManager.UpdateResource(resourceInfo.ResourceName);
}
}

LoadAssetTask的Create当然也会用到引用池~

1
2
3
4
5
6
7
public static LoadAssetTask Create(string assetName, Type assetType, int priority, ResourceInfo resourceInfo, string[] dependencyAssetNames, LoadAssetCallbacks loadAssetCallbacks, object userData)
{
LoadAssetTask loadAssetTask = ReferencePool.Acquire<LoadAssetTask>();
loadAssetTask.Initialize(assetName, assetType, priority, resourceInfo, dependencyAssetNames, userData);
loadAssetTask.m_LoadAssetCallbacks = loadAssetCallbacks;
return loadAssetTask;
}

紧接着就开始遍历依赖的资源名,并调用加载依赖资源函数LoadDependencyAsset。如果依赖加载失败,也会执行失败回调并抛出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private bool LoadDependencyAsset(string assetName, int priority, LoadResourceTaskBase mainTask, object userData)
{
if (mainTask == null)
{
throw new GameFrameworkException("Main task is invalid.");
}

ResourceInfo resourceInfo = null;
string[] dependencyAssetNames = null;
if (!CheckAsset(assetName, out resourceInfo, out dependencyAssetNames))
{
return false;
}

if (resourceInfo.IsLoadFromBinary)
{
return false;
}

LoadDependencyAssetTask dependencyTask = LoadDependencyAssetTask.Create(assetName, priority, resourceInfo, dependencyAssetNames, mainTask, userData);
foreach (string dependencyAssetName in dependencyAssetNames)
{
if (!LoadDependencyAsset(dependencyAssetName, priority, dependencyTask, userData))
{
return false;
}
}

m_TaskPool.AddTask(dependencyTask);
if (!resourceInfo.Ready)
{
m_ResourceManager.UpdateResource(resourceInfo.ResourceName);
}

return true;
}

加载依赖资源和加载普通资源流程差不多,这里就先略过了。
依赖资源加载完毕之后,会把模板资源mainTask加入任务池m_TaskPool中。资源管理器会在轮询中执行m_TaskPool中缓存的任务。
如果资源没有准备完毕,还会调用UpdateResource更新指定资源,我们这里暂时不会用上。
终于,走了一长串,我们的资源加载完毕了,他会执行我们之前监听的回调函数。

1
2
3
4
5
6
7
8
9
10
11
private void OnLoadConfigSuccess(object sender, GameEventArgs e)
{
LoadConfigSuccessEventArgs ne = (LoadConfigSuccessEventArgs)e;
if (ne.UserData != this)
{
return;
}

m_LoadedFlag[ne.ConfigAssetName] = true;
Log.Info("Load config '{0}' OK.", ne.ConfigAssetName);
}

终于有个比较简单的了,加载完毕后就把对应的bool值变为true。
后续的LoadDataTable、LoadDictionary、LoadFont就大同小异,这里就不赘述了。

OnUpdate

OnEnter就比较简单了,它会每次轮询所有资源是否加载完毕。
如果加载完毕就执行procedureOwner.SetData(“NextSceneId”, GameEntry.Config.GetInt(“Scene.Menu”));
相对于向procedureOwner塞入一个Int值,以供后一个流程使用。这里塞入的是全局表中配置的下一个场景ID。
接着就切换到下一个流程ProcedureChangeScene。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);

foreach (KeyValuePair<string, bool> loadedFlag in m_LoadedFlag)
{
if (!loadedFlag.Value)
{
return;
}
}

procedureOwner.SetData<VarInt32>("NextSceneId", GameEntry.Config.GetInt("Scene.Menu"));
ChangeState<ProcedureChangeScene>(procedureOwner);
}

OnLeave

1
2
3
4
5
6
7
8
9
10
11
protected override void OnLeave(ProcedureOwner procedureOwner, bool isShutdown)
{
GameEntry.Event.Unsubscribe(LoadConfigSuccessEventArgs.EventId, OnLoadConfigSuccess);
GameEntry.Event.Unsubscribe(LoadConfigFailureEventArgs.EventId, OnLoadConfigFailure);
GameEntry.Event.Unsubscribe(LoadDataTableSuccessEventArgs.EventId, OnLoadDataTableSuccess);
GameEntry.Event.Unsubscribe(LoadDataTableFailureEventArgs.EventId, OnLoadDataTableFailure);
GameEntry.Event.Unsubscribe(LoadDictionarySuccessEventArgs.EventId, OnLoadDictionarySuccess);
GameEntry.Event.Unsubscribe(LoadDictionaryFailureEventArgs.EventId, OnLoadDictionaryFailure);

base.OnLeave(procedureOwner, isShutdown);
}

最后在离开一个流程前,记得移除所有的事件监听。