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 ( ) { LoadConfig("DefaultConfig" ); foreach (string dataTableName in DataTableNames) { LoadDataTable(dataTableName); } LoadDictionary("Default" ); 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 public void ReadData (string configAssetName ) { m_ConfigManager.ReadData(configAssetName); }
我们一路跟进去。ConfigManager的ReadData。
1 2 3 4 5 6 7 8 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 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 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 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); }
最后在离开一个流程前,记得移除所有的事件监听。