GameFramework食用指南
框架简介
GF框架分两部分,GameFramework(GF)和UnityGameFramework(UGF);
通过接口的形式对Unity引擎进行了解耦;
GF独立于Unity,具体业务逻辑实现都在GF中;
UGF是继承了MonoBehaviour的组件,通过接口调用GF中Module的方法;
框架流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AsL05rro-1656664378468)(https://raw.githubusercontent.com/Rebort1012/picgo/main/image-20220623115830791.png)]
左边GF层,由GameFrameworkEntry管理所有GameFrameworkModule;(使用GF自己实现的链表非list)
Update阶段根据每个Module的Priority顺序轮询执行所有Module的Update;
GameFrameworkEntry对外提供获取单个Module的方法;
右边UGF层,UGF中所有Component都继承GameFrameworkComponent;
GameFrameworkComponent做两件事:
- 继承MonoBehaviour,未每个组件提供UnityComponent的生命周期;
- Awake方法中注册该UGF组件,将所有UGF组件存储在UGF层的GameEntry类中;
UGF中的BaseComponent,Awake阶段做Text,Log,版本,json辅助器的初始化;
Update阶段调用GF层的GameFrameworkEntry的Update方法,轮询实行所有GF层Module的Update;
BaseComponent还提供推出程序,重启暂停游戏的方法;
UGF层的GameEntry:
上文提到,所有的UGF组件都继承GameFrameworkComponent;
GameFrameworkComponent的Awake阶段调用GameEntry的RegisterComponent方法,存储UGF组件;
E大官方文档建议,在实际游戏开发阶段,对UGF的GameEntry再做一层封装——业务层的GameEntry;
将所有用到的UGF组件创建静态只可get的属性,只有业务逻辑中只需要通过业务层GameEntry中的静态属性来获取UGF组件;(官方Starforce命名空间下的GameEntry)
接口解耦
GFModule继承对应Module的接口,实现具体方法;
UGF组合对应组件接口,Awake阶段对接口初始化,通过接口调用GF方法;
以ConfigComponent为例:
ConfigManager实现IConfigManager接口方法;
ConfigComponent组合IConfigManager变量,在Awake阶段用GF层的ConfigManagerModule初始化;
资源管理
GF框架的资源管理非常强大,集成了VFS系统效率高,编辑器拓展管理打包资源清晰明了;
Package单机模式:无法热更;
Updatable可更新模式:游戏开始前更新完进入游戏;
Updatable while playing可更新分包加载:游戏运行时需要使用资源时即时下载;
Config下ResourceBuilder.xml存储了打包界面的设置信息;
ResourceEditor.xml设置了ResourceEditor界面中可被打包的资源格式,过滤所有脚本,同时设置了资源编辑界面的搜索路径;
ResourceCollection.xml记录了所有ab包,以及资源依赖;
界面操作GF的官方文档写的非常详细,这里介绍一下资源热更流程;
热更流程
Resource节点选择Updatable,GameFramework节点关闭EditorResourceMode;
ResourceBuilder打包勾选OutputFullPath;
修改GameMian/Configs/BuildInfo.txt中CheckVersionUrl;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vaNyCC9P-1656664378469)(https://raw.githubusercontent.com/Rebort1012/picgo/main/image-20220623161924265.png)]
打包输出路径找到对应打包版本的BuildLog.txt,找出加密hash;
打包输出路径/Full/1_0_0(对应版本)下,创建version.txt,本地服务器也搭建在这个位置;
{
"ForceUpdateGame": false,
"LatestGameVersion": "1",
"InternalGameVersion": 0,
"InternalResourceVersion": 1,
"UpdatePrefixUri": "http://localhost:8888/Windows", //资源服务器地址
"VersionListLength": 3280, //BuildLog中的加密hash
"VersionListHashCode": -553090012,
"VersionListCompressedLength": 1326,
"VersionListCompressedHashCode": 1159160375,
"END_OF_JSON": ""
}
更新时比对LatestGameVersion和InternalGameVersion,确定是否更新;
一定要删除本地缓存的persistantData路径下的文件;
数据表
GF的DataTable默认excel复制下来的结构,像CSV但分隔符是/t;
直接用DataTable组件根据行数加载单条数据;
支持bytes和json格式;
Configs中DataTableCodeTemplate,用于生成C#类模板;
怎么说呢,简单游戏够了,复杂点的还是加个导表工具或者魔改一下;
DataTable也可以添加自定义数据解析;
另外直接在txt中添加数据条总是莫名奇妙的格式错误,毕竟空格肉眼也区分不了;
我改成,
分隔后,注释列又显得很多余;
所以我根据GF的原理自己谁先了直接读Excel的DataTable,支持json和bytes;
一键导出,且自动生成c#类,支持数组和枚举,支持excel公式;
事件系统
GF的事件系统是基于引用池的,每次回收前需要Clear数据,防止脏数据;
注册和取消注册
GameEntry.Event.Subscribe(TestArgs.EventId, OnTest);
GameEntry.Event.Unsubscribe(TestArgs.EventId, OnTest);
自定义事件类
public class TestArgs:GameEventArgs
{
public static readonly int EventId = typeof(ResourceVerifyStartEventArgs).GetHashCode();
public override int Id
{
get
{
return EventId;
}
}
public override void Clear()
{
}
//以上部分是必须有的部分,以下属于自定义,事件需要传递的参数都可以写在下面,相应的Clear中需要清空
}
事件发送
GameEntry.Event.Fire(object sender,new TestArgs());
sender发送人,一般填this;
第二参数,具体事件类,如果有参数,需要先赋值,个人喜欢写带参构造,也可以单独写Init函数;
事件响应
public void OnTest(object sender, GameEventArgs e)
{
TestArgs en = (TestArgs)e;
//执行逻辑
}
流程控制
GF框架使用流程控制来控制游戏阶段;
流程控制组件是一个被固定了拥有者(ProcedureOwner)的状态机(FSM);
通过状态机来控制更新,加载,初始化等;
这里的流程不指游戏中具体业务逻辑流程;
StarForce中的流程控制;有问题打个断点总能看清楚的;
切换场景GF时切换到changeScene流程卸载资源后加载新场景流程;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U2u0xR7M-1656664378470)(https://raw.githubusercontent.com/Rebort1012/picgo/main/StarForce%E6%B5%81%E7%A8%8B%E6%8E%A7%E5%88%B6.png)]
UI模块
GF的UI分为UIGroup和UIForm;
UIGroup中包含多个UIForm,由UIGroup管理其中的UIForm;
UIForm的层级等于所属UIGroup层级自身层级(SortOrder);
UIGroup概念有点想UI中的默认层,Pop层,前景层,背景层,可自定义;
在场景UI节点添加UIGroup;
所有UI脚本必须继UGuiForm,UGuiForm继承UIFormLogic,UGuiForm中对所有Text做了本地化处理;
UI生命周期:
UI的层级问题:
每个UIGroup要求设置order层级;
每个UIForm的层级最终是自身sortorder+所属group的order;
在创建预制体时可设置好;
Entity模块
Entity个人理解相当于自己封装了一个MonoBehaviour类,但是还集成了Mono;
同时有GF的生命周期+Mono的生命周期;
Enity包括了GameObject的功能,由GF的EntityModule来控制Active;
Entity也是基于对象池的,从框架层管理内存问题;
使用
逻辑控制类继承EntityLogic,重写其中生命周期的方法(别忘记base.);
显示Entity调用GameEntry.Entity.ShowEntity(id,path,group);
id在datatable中配置;
path为datatable中配置的默认路径+AssetName;(ResourceEditor界面的path)
Entity中提供了Attach方法;
通过GameEntry.Entity.AttachEntity()调用,此时会调用父物体EntityLogic中OnAttached周期,和子物体OnAttachTo周期;
解绑时调用OnDetached,OnDetachFrom;
Entity和EntityLogic
预制体不需要绑EntityLogic脚本,ShowEntity时会自动绑上EntityLogic+Entiy脚本;
这俩脚本相互引用,Entity的周期中都在调用EntiyLogic对应的周期函数;
Entity继承自IEntity,被EntityComponent管理;
小声比比:所以,这两个有一个就行,或者Entity其实不用继承Mono;
具体操作时,只写EntityLogic脚本,不用管Entity;
FSM有限状态机
GF的状态机都由FsmComponent管理(GameFrameworkComponent);
创建状态机时,需要状态机的Owner(泛型T),和FsmState数组;
eg:Hero继承EntityLogic
ActBase<Hero>[] array = new ActBase<Hero>[] { new IdleAct<Hero>(), new RunAct<Hero>() };
mFsm = GameEntry.Fsm.CreateFsm<Hero>(this, array);
public class IdleAct<T>:ActBase<T> where T : Hero
{
protected override void OnInit(IFsm<T> fsm)
{
base.OnInit(fsm);
}
protected override void OnEnter(IFsm<T> fsm)
{
base.OnEnter(fsm);
fsm.Owner.PlayCurAnima();
}
protected override void OnUpdate(IFsm<T> fsm, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(fsm, elapseSeconds, realElapseSeconds);
if (fsm.Owner.curState == ActState.Run)
{
ChangeState<RunAct<T>>(fsm);
}
}
}
本地化工具
GF的Localization存储的xml;
UI的Init阶段遍历所有组件,获得所有Text组件,根据Text组件中的text加载本地化语言,同时设置对应字体;
该语言=重新加载游戏;
这个组件简单但不够智能;
个人添加了两个功能:
-
根据TextMeshProGUI组件的Name和text内容自动添加简体中文多语言xml的key;
-
调用谷歌接口自动翻译,生成其他语言xml;