目录
一、 ProcedureLaunch流程
OnEnter
1、加载 BuildInfo.txt,该文件提供游戏版本号、补丁号、服务器的version.txt地址、各种平台的整包更新url
version.txt是客户端一份、服务器一份的,热更新模式时,每次启动游戏客户端会比对一下服务器的version.txt的信息
信息主要包括:是否强制整包更新、内部版本号、内部补丁号、内部资源版本号、服务器资源目录地址、资源包字节大小、资源包HashCode、
压缩资源包字节大小、压缩资源包HashCode(资源包都是同一份,只有一个资源包)
热更新流程博客:https://blog.csdn.net/qq_39574690/article/details/109234506
问题:version.txt会放在客户端哪?与服务器的version.txt如何比对判定是否需热更?
2、 多语言初始化(编辑器直接使用系统语言:简体中文)
若使用GameEntry.Setting.SetString(Constant.Setting.Language, language.ToString());保存指定语言,且GF框架支持该语言则使用它。
若不支持则临时使用英语替代。具体代码位于ProcedureLaunch的80行,默认支持英语、简体中文、繁体中文、韩语
默认加载GameMain/Localization/xxx/Dictionaries/yyy.zzz,其中xxx是语言目录、yyy是文件名、zzz是后缀(.bytes or .xml)
例如:大陆地址:GameMain/Localization/ChineseSimplified/Dictionaries/Default.xml (Default是默认文件)
多语言使用Localization Component脚本指定的Localization helper脚本的解析方法ParseData进行解析,例如StarForce.XmlLocalizationHelper类专门解析xml格式参考Default.xml
【多语言配置文件加载方法】:GameEntry.Localization.ReadData("Asset/.../Default.xml", this); 加载多语言字典xml格式文件并解析到LocalizationComponent里
在ProcedurePreload流程执行PreloadResources方法会加载指定语言的Default.xml多语言文件
用法例如:GameEntry.Localization.GetString("Dialog.ConfirmButton");
'Dialog.ConfirmButton' 是key,返回字符串string
【待测试:新增多语言(日文)】
若需要更改当前多语言,都需要重新加载框架所在的场景即StarForce Launcher (框架指Game Framework物体)
3、变体配置:根据使用的语言,通知底层加载对应的资源变体 【???好像和资源加载有关联,又和多语言有关联, 例如:"zh-cn” 是 中文简体变体 国际化本地化?】
4、声音配置:根据用户配置数据,设置即将使用的声音选项(设置音乐组是否静音、音量)
设置是否静音:GameEntry.Sound.Mute("Music", GameEntry.Setting.GetBool(Constant.Setting.MusicMuted, false));
设置音量:GameEntry.Sound.SetVolume("Music", GameEntry.Setting.GetFloat(Constant.Setting.MusicVolume, 0.3f));
ProcedurePreload流程会进行加载“Music”、“UISound”、“Sound”音乐组的表格形式配置文件.
加载方法:GameEntry.DataTable.LoadDataTable(dataTableName, dataTableAssetName, this);
其中,dataTableName 是 配置表名、 dataTableAssetName 是 配置表文件相对Assets路径 , "Assets/GameMain/DataTables/Sound.txt"
播放/停止/暂停Music音乐组的音乐:
int musicId = GameEntry.Sound.PlayMusic(id)
GameEntry.Sound.StopMusic()
GameEntry.Sound.PauseSound(musicId); //待测试
播放/停止/暂停播放Sound音乐组的音乐:
int soundId = GameEntry.Sound.PlaySound(id);
GameEntry.Sound.StopSound(soundId);
GameEntry.Sound.PauseSound(soundId);
播放/停止/暂停UISound音乐组的音乐
int uiSoundId = GameEntry.Sound.PlayUISound(id);
GameEntry.Sound.StopSound(uiSoundId); //待测
GameEntry.Sound.PauseSound(uiSoundId);//待测
5、加载热更新模块所使用的多语言配置文件Assets/GameMain/Configs/DefaultDictionary.xml (该文件不可热更,因为热更前就要用到的。)
它是一个GameEntry.Localization.ParseData直接解析由BuiltinDataComponent完成加载的配置文件text,解析后放入多语言字典。
关于Xml的格式和解析,它只会进行识别<Dictionary Language='xxx'>是你当前设置语言的内容进行解析,其他的语言的会过滤掉,
所以实际是可以写成DefaultDictionary那样的,但实际我们热更新的多语言文本,还是分开文件写了,因为占空间啊..这里它不占作者就懒得分开了。
OnUpdate
立即切换到ProcedureSplash流程
二、ProcedureSplash流程
OnEnter
//TODO 可播放一个进场动画再执行以下逻辑
1、检查BaseComponent(Builtin物体挂载的脚本)的Editor Resource Mode为true,则直接进入ProcedurePreload流程【预加载流程】——编辑器模式
2、检查ResourceComponent(Resource物体挂载的脚本)的Resource Mode为Package,则直接进入ProcedureInitResources流程【初始化游戏资源流程】——单机模式
3、若1、2都无法通过,则是可热更模式,进入ProcedureCheckVersion流程【检查版本流程】——可更新模式 【重点】
——————————————————————————————————————————————
可更新模式:
三、ProcedureCheckVersion流程
OnEnter
1、注册网络请求失败、成功事件:
GameEntry.Event.Subscribe(WebRequestSuccessEventArgs.EventId, OnWebRequestSuccess);
GameEntry.Event.Subscribe(WebRequestFailureEventArgs.EventId, OnWebRequestFailure);
2、发起网络请求:GameEntry.WebRequest.AddWebRequest(服务器version.txt地址, this);
StarForce案例:请求下载BuildInfo文件的CheckVersionUrl地址指定的version.txt文件
*地址:Utility.Text.Format(GameEntry.BuiltinData.BuildInfo.CheckVersionUrl, GetPlatformPath()) 内容 待测 应该是要加上平台名称的.【*待测】
3、OnWebRequestSuccess 网络请求成功回调
将GameEventArgs e 转为 WebRequestSuccessEventArgs 类对象ne
通过ne.GetWebResponseBytes() 获取byte[] 字节数组
Utility.Json.ToObject<VersionInfo>(byte[]) 转为VersionInfo类型对象m_VersionInfo
若不存在,则报错:“Parse VersionInfo failure.”
打印“Latest game version is '{0} ({1})', local game version is '{2} ({3})'.”
{0}({1})是服务器版本号(服务器补丁号) ,{2}({3})是当前版本号和补丁号
当m_VersionInfo.ForceUpdateGame为true时,进行强制更新游戏应用(整包更新)
弹窗提示用户点击跳转到官网下载,否则退出游戏。
官网地址是BuildInfo.txt设置的那些WindowsAppUrl、AndroidAppUrl、iOS...
否则,设置资源更新下载地址即m_VersionInfo.UpdatePrefixUri地址
一般为:"http://localhost/Windows"地址, localhost改为服务器IP地址,这是Windows平台的热更新。
Windows文件夹是从AB打包目录下的Full\x_x_x_x\平台英文名称获取的,AB打包后直接放到服务器上即可,
然后将这个文件夹的地址作为version.txt的UpdatePrefixUri内容。
m_CheckVersionComplete设置为true
* m_NeedUpdateVersion = GameEntry.Resource.CheckVersionList(m_VersionInfo.InternalResourceVersion) == CheckVersionListResult.NeedUpdate;
检查资源是否有变化,有变化则需要进行热更新, m_NeedUpdateVersion设为true。
这里估计是判断【服务器资源版本号】与【本地资源版本号】不同则需更新Version【版本信息】。
OnUpdate
当m_CheckVersionComplete为true时
当m_NeedUpdateVersion为true时,需要更新版本信息
将VersionListLength、VersionListHashCode、VersionListZipLength、VersionListZipHashCode内容【这些是版本资源相关的信息】
携带以上信息包进入【ProcedureUpdateVersion流程】【版本更新流程】
否则,不需要更新版本信息,进入ProcedureCheckResources流程【检查资源流程】
否则,游戏逻辑不会再进行下一个流程了 会在OnUpdate卡主不动。
1、解析VersionInfo报错:Parse VersionInfo failure
2、处于整包更新流程,玩家会打开官网 或 退出游戏
*四、ProcedureUpdateVersion流程
OnEnter
获取传入的VersionListLength、VersionListHashCode、VersionListZipLength、VersionListZipHashCode。
四个变量传入GameEntry.Resource.UpdateVersionList进行更新version list【版本列表, 可能是md5 + filepath 文件那个东西】
更新版本文件成功:OnUpdateVersionListSuccess回调,设置m_UpdateVersionComplete为true
OnUpdate
检查m_UpdateVersionComplete为true,则进入ProcedureCheckResources流程【检查资源流程】
即更新版本文件完毕后,进行检查一遍资源, 上面不需要更新版本是直接走到检查资源流程的。
【*问题】它是更新Version.txt?? 这个文件更新好后,再进行检查游戏资源?
目前搞不懂UpdateVersionList到底是更新的内容具体是什么。
五、ProcedureCheckResources流程
OnEnter
直接调用GameEntry.Resource.CheckResources方法检查资源,对比游戏资源文件,检查出需更新的文件数、大小等信息
OnCheckResourcesComplete 检查完成回调
回调传回参数:
movedCount 已移动的资源数量
removedCount 已移除的资源数量
updateCount 可更新的资源数量,大于0时则表示需热更资源
updateTotalLength 可更新的资源总大小
updateTotalZipLength 可更新的压缩后总大小
OnUpdate
若检查资源成功,则进行如下逻辑:
1、若需更新资源(updateCount > 0)
将updateCount和updateTotalZipLength打包,携带进入ProcedureUpdateResources流程【更新资源流程】 。
2、否则,不需要更新资源,进入ProcedurePreload流程【预加载流程】
六、ProcedureUpdateResources流程
OnEnter
监听资源更新开始、更新后、成功、失败事件
若网络处于使用网络数据,会询问是否使用网络数据进行更新(也就是你没连wifi会问一下是否真的花流量去更新)
若点确定更新,或使用wifi会进入StartUpdateResources开始更新下载资源。
首先加载出UpdateResourceForm热更新面板,显示热更进度
接着调用GameEntry.Resource.UpdateResources进行更新资源,
资源更细完毕回调 OnUpdateResourcesComplete,更新资源成功/失败会打印
Update resources complete with no errors. 成功 并设置完成标记为true
Update resources complete with errors. 失败
资源更新细节:注意以下回调是以单个资源文件为对象而言的,
1、开始更新资源时回调 OnResourceUpdateStart
传入将开始热更的资源文件对象,若已存在更新列表则把文件更新进度置为0,不放入更新列表;
若不存在于更新列表,则封装为一个UpdateLengthData对象放入更新列表。
UpdateLengthData只有Name和Length变量,Name是资源名,Length是进度.
2、正在更新的资源改变后回调OnResourceUpdateChanged
刷新与这个资源文件相关的UpdateLengthData对象的Length进度变量为新的进度,并更新进度条。
3、资源更新成功后回调 OnResourceUpdateSuccess
将对应的资源对象的Length设为完成值,并更新完成数+1,刷新进度条。
4、资源更新失败后回调 OnResourceUpdateFailure,
若重试次数大于总共重试次数,报错并退出;
否则, 报错 并 从更新列表移除报错的文件对象, 更新进度条。
报错信息:“Update resource '{0}' failure from '{1}' with error message '{2}', retry count '{3}'”
{0} 资源名,{1} 路径,{2} 错误信息,{3} 重试次数
【注意】关于2和3、4时,如果从更新列表找不到该资源,都会报错"Update resource '{0}' is invalid." {0}是资源名
更新进度条:
显示的{0}/{1}, {2}/{3}, {4:P0}, {5}/s
即更新成功数量/总数量,当前更新字节/总需更新字节,更新百分比,当前下载速度/s
在资源更新失败后,直接从更新列表移除后,会尝试重新下载,如果超出一定次数失败就真的失败了。
此时,进度条会卡死,可在OnUpdateResourcesComplete的else块处理失败流程。
OnUpdate
更新成功后,进入ProcedurePreload流程【预加载流程】
否则,卡死游戏逻辑,提示更新失败 Update resources complete with errors. 可在这里优化下处理失败后的流程。
七、ProcedurePreload流程【预加载流程】
OnEnter
监听Config配表表加载成功、失败事件
监听DataTable数据表加载成功、失败事件
监听多语言字典加载成功、失败事件
1、使用GameEntry.Config.ReadData, 加载Assets/GameMain/Configs/DefaultConfig.txt文件【全局配置文件】
具体:DataProvider的ReadData加载资源文件,加载优先级为0,加载完成后由Config Component指定的Config Helper类进行ReadData读取
一般读取的是object, ParseData是解析byte[]或string的. 最终变成一个<key, value>键值对存储在ConfigManager里.
可通过GetString(key,defaultValue)根据key获取value,类似PlayerPref类 具体参考IConfigManager.cs内部接口
2、使用GameEntry.DataTable.LoadDataTable(dataTableName, dataTableAssetName, this)
在介绍音乐初始化时讲过,参数1是表名,参数2是表文件路径
例如:加载GameMain/DataTables/Sound.txt
需为表格新建一个脚本作为表格对应的序列化类DRSound,它放于GameMain/Scripts/DataTable下。
DRSound类命名空间必须是StarForce,你想改的话可以去DataTableExtension类修改DataRowClassPrefixName变量指定的"StarForce.DR"
例如:改为"MyGame.",那么Sound.txt表格对应的序列化类名为Sound,命名空间为MyGame。
加载还是使用DataProvider进行加载资源文件,再通过DataTableComponent的Data Table Helper进行解析。
即DefaultDataTableHelper类进行ReadData(读资源文件object)和ParseData(解析资源文件的byte[]或string)
ReadData后接着就是直接到ParseData的.
最终,Sound.txt会变成一个以lua来形容:
["Sound"] = {
[id] = { id , 其他成员 },
[id] = { id , 其他成员 },
...
}
存储于DataTableComponent里
类似:Sound.txt命名的,它只允许有一个存在。
可以用Sound_sound1.txt命名的,它代表Sound类型的表格名为sound1,可以存在名为sound2的Sound类型表格..
可使用IDataTable<DRSound> table = GameEntry.DataTable.GetDataTable<DRSound>() 获取表格对象。
DRSound drSound = table.GetDataRow(id) 根据id获取具体内容 其他方式可看具体源码
若有命名的则可通过GameEntry.DataTable.GetDataTable<DRSound>("sound1") 获取表格对象。
3、LoadDictionary("Default"); 多语言字典加载,已介绍过
DataProvider加载, 再使用Localization Component的Localization Helper类 (XmlLocalizationHelper类) 进行解析
以<key,value>形式保存在Localization Component的字典对象里。
可通过GameEntry.Localization.GetString(string key)方法获取
支持GameEntry.Localization.GetString传递多个参数作为格式化字符串
例如:
存在键值对:["A"] = "{0}_{1}"
GameEntry.Localization.GetString("A", "hello", "world") 输出 hello_world
4、LoadFont("MainFont")
加载字体资源:Assets\GameMain\Fonts\MainFont.ttf
使用GameEntry.Resource.LoadAsset(path, 優先級,成功回調, 失败回调)
成功时设置UGuiForm.SetMainFont((Font)) 可能是UI的主字体设置为Font,优先选用这个字体.
OnUpdate
等预加载资源都加载完毕后,通过GameEntry.Config.GetInt("Scene.Menu") 获取DefaultConfig配置表的对应key内容
将其内容作为NextSceneId字段值传入ProcedureChangeScene流程【更新场景流程】
八、ProcedureChangeScene流程
专门用于切换场景,只要设置NextSceneId传入流程即可。
例如这样
procedureOwner.SetData<VarInt>("NextSceneId", GameEntry.Config.GetInt("Scene.Menu"));
ChangeState<ProcedureChangeScene>(procedureOwner);
这个NextSceneId是和Scene配置表和 DefaultConfig表格有关。
监听场景加载成功、失败、进行中、加载场景依赖资源事件
1、停止所有声音
2、隐藏所有实体
3、卸载所有场景
4、还原游戏速度
5、根据NextSceneId从Scene配置表搜出场景资源文件名,获取场景相对路径Assets/.../Menu
使用GameEntry.Scene.LoadScene(AssetUtility.GetSceneAsset(drScene.AssetName), Constant.AssetPriority.SceneAsset, this);同步加载追加场景
其中drScene是从Scene配置表根据id找出的DRScene对象, AssetName是资源名
加载成功:播放DRScene的BackgroundMusicId音乐,使用GameEntry.Sound.PlayMusic(id)播放,并设置加载成功标记 【传回LoadSceneSuccessEventArgs对象】
加载失败:打印Load scene '{0}' failure, error message '{1}'." {0} = ne.SceneAssetName, {1} = ne.ErrorMessage 【传回LoadSceneFailureEventArgs对象ne】
加载中:打印Load scene '{0}' update, progress '{1}'. {0} = ne.SceneAssetName, {1} = ne.Progress.ToString("P2") 【传回LoadSceneUpdateEventArgs对象ne】
加载依赖资源完成:打印Load scene '{0}' dependency asset '{1}', count '{2}/{3}'.", 【传回LoadSceneDependencyAssetEventArgs对象ne】
{0} = ne.SceneAssetName, {1} = ne.DependencyAssetName, {2} = ne.LoadedCount.ToString(), {3} = ne.TotalCount.ToString()
关于事件监听是每一个事件都有一个Id和一个XXXEventArgs类对象继承与GameEventArgs类,它就是个专门用于传递一些参数的,一般有个叫Create的静态方法拷贝自身。
例如:m_EventComponent.Fire(this, LoadSceneSuccessEventArgs.Create(e)); 这个e是LoadSceneSuccessEventArgs对象!这样传给加载成功回调..
目前仅有2个场景可去
1、Menu
2、Main
上面的NextSceneId是Menu的Id,所以等加载场景完毕后,跳转到ProcedureMenu流程
九、ProcedureMenu流程
监听OpenUIFormSuccessEventArgs事件【打开UI成功事件】
GameEntry.UI.OpenUIForm(UIFormId.MenuForm, this); 打开MenuForm面板
这个Id是在GameMain/DataTables/UIForm.txt表格的Id,表格序列化类为DRUIForm
DRUIForm内容:id、资源名、界面组GroupName、是否允许存在多个同样的UI出现、是否会被其覆盖的UI暂停
当前加载的UI资源名是MenuForm。
使用UIComponent类的方法OpenUIForm(资源相对路径,界面组GroupName, 优先级, 是否会被覆盖的UI所暂停, userData)进行打开UI
关于GF的UI系统的界面组其实是一个Canvas,然后设置指定的层级,把创建的UI放到界面组Canvas下。但个人觉得UI系统不是很好,可以不用。
加载回调OnOpenUIFormSuccess传递回的OpenUIFormSuccessEventArgs内带UIForm,它是预制体挂载的一个脚本基类。
UIForm内写有各种UI生命周期方法,它其实是在创建UI时用代码添加到物体上的。
UI预制体原本挂载有MenuForm、创建后动态添加UIForm脚本
继承关系:MenuForm -> UGuiForm -> UIFormLogic -> MonoBehaviour
UGuiForm脚本: 有一些控制关闭面板、播放UI音乐、设置字体等公用方法,在初始化时它会添加一个CanvasGroup,用它来在OnOpen和OnClose时做一些渐隐渐显的效果..
设置RectTransform为全屏适配,获得/添加GraphicRaycaster组件接收射线,并初始化所有Text的字体和内容,原本预制体上Text的内容是key,它会使用如下代码:
texts[i].text = GameEntry.Localization.GetString(texts[i].text); 将key转为当前多语言的对应内容。 也就是说策划看预览界面时的文本全都是key(体验感不好)
UIFormLogic会获取身上的UIForm对象,并声明了一堆UI生命周期虚方法,由MenuForm实现.
在MenuForm的OnOpen(useData),useData其实是在ProcedureMenu流程对象,它在调用GameEntry.UI.OpenUIForm(UIFormId.MenuForm, this);时传入的第二个参数this就是它。
将useData转为ProcedureMenu对象后,调用ProcedureMenu的StartGame公共方法,m_StartGame设置为true.
OnUpdate
当m_StartGame为true时,切到ProcedureChangeScene流程,加载Scene.Main配置对应的场景显示,等加载完毕后进入ProcedureMain流程。
procedureOwner.SetData<VarInt>("NextSceneId", GameEntry.Config.GetInt("Scene.Main")); 下一个场景为Main场景
procedureOwner.SetData<VarByte>("GameMode", (byte)GameMode.Survival); 这个GameMode并没有在ProcedureChangeScene使用,但是在ProcedureMain流程就用到了。
十、ProcedureMain流程
OnInit
初始化一个字典, [GameMode.Survival] = new SurvivalGame();
OnEnter
获取procedureOwner里存储的VarByte类型数据"GameMode" , 它对应地获取到了字典[GameMode.Survival]的内容: SurvivalGame对象
执行SurvivalGame对象的Initialize方法, m_CurrentGame为SurvivalGame对象。
OnUpdate
当检查到m_CurrentGame存在且m_CurrentGame.GameOver==false时,执行m_CurrentGame.Update(elapseSeconds, realElapseSeconds);
elapseSeconds是帧时间,realElapseSeconds是不受Time.timeScale影响的帧时间
否则,2f后跳转回Menu场景, 如下逻辑:
procedureOwner.SetData<VarInt>("NextSceneId", GameEntry.Config.GetInt("Scene.Menu"));
ChangeState<ProcedureChangeScene>(procedureOwner);
至此基本结束流程的讲解,关于战斗逻辑部分,暂时不看。