流程模块的作用
流程在实现上其实是对有限状态机的一个封装,如果未读本系列文章中的有限状态机解析篇,建议可以先看完有限状态机的解析再看本文。
那么流程是解决什么问题呢?我们来看看 GF 官方文档的定义:
贯穿游戏运行时整个生命周期的有限状态机。通过流程,将不同的游戏状态进行解耦将是一个非常好的习惯。对于网络游戏,你可能需要如检查资源流程、更新资源流程、检查服务器列表流程、选择服务器流程、登录服务器流程、创建角色流程等流程,而对于单机游戏,你可能需要在游戏选择菜单流程和游戏实际玩法流程之间做切换。如果想增加流程,只要派生自 ProcedureBase 类并实现自己的流程类即可使用。
实际上就是用有限状态机把游戏整体状态管理了起来,我们应该让游戏在生命周期中的任何一刻,都属于某个流程中,且同时只会处于一个流程状态中。虽然实现简单,但起到了很好的逻辑划分作用,也很方便后期调整各流程的顺序,甚至可以构建一颗流程树,根据不同环境走不同的流程分支。
笔者曾经经历过这样的情景:项目原本是进入游戏后先走更新流程,再登录的,后来渠道方要求要把登录步骤放在前面,登录后再走版本检测、更新流程,由于那个项目对启动流程管理并没有那么清晰,导致我们最终不得不重构了游戏启动流程的代码。但如果严格按照状态来划分每一个流程,那我们调整流程顺序将会和调整 Animator 连线一样简单(前提是调整的两个流程是没有依赖顺序的,例如在更新资源前,必须走完版本检测流程,这种是有依赖顺序的)。
另外要注意,一般地说,一个游戏拥有的流程数量是非常有限的,如果规划出数十个流程出来,很可能是对流程的理解有所偏差。例如一个塔防游戏有数十个关卡,每个关卡的内容都不一样,但关卡中的地图,炮塔,敌人生成等,其实都是数据驱动的,而他们的逻辑其实是一样的,只是数据不同造成表现不同,所以无论是哪个关卡,他们都应该属于同一个流程。
流程的实现
结构
流程基类
ProcedureBase 类为所有流程的基类,它是一个抽象类,继承自 FsmState,(定义在 FSM 模块中)泛型参数 T 为 IProcedureManager,他具有 FsmState 的所有功能,虽然 ProcedureBase 重写了 FsmState 的生命周期方法,但并没有添加额外的逻辑。值得注意的是,ProcedureBase 已经限定了持有者为 IProcedureManager 类型,也就是限定了 ProcedureManager 为流程持有者,ProcedureBase 的子类不能改变这一限制。
流程管理类
简单地说 ProcedureManager 内部就是用 FsmManager 创建了一个专门管理游戏流程的状态机,并启动流程。
- 字段 m_FsmManager 为有限状态机管理器,会在 Initialize 方法初始化时作为参数传入,m_ProcedureFsm 为管理流程用的有限状态机。
- 方法 Initialize 会取得 FsmManager 实例和包括所有流程(继承 ProcedureBase 的对象)的列表,并用 FsmManager 创建出一个状态机实例储存于 m_ProcedureFsm 中。
- 与 Fsm 模块类似,流程模块提供 HasProcedure、GetProcedure 接口来查询和获取指定流程对象,CurrentProcedure 获得当前处于的流程,CurrentProcedureTime 获取当前流程持续时间。
- StartProcedure 方法,令状态机从指定流程启动,这里是游戏框架正式启动游戏的关键入口
流程组件
既然流程管理器里的 StartProcedure 方法是框架正式启动游戏的关键入口,那么这个 StartProcedure 是哪里调用的呢?看下面 ProcedureComponent 的部分代码。
ProcedureComponent属于框架UGF部分
ProcedureComponent 是一个 Mono 类,上面的 Start 方法会被 Unity 内部主动调用,调用后会根据 m_AvailableProcedureTypeNames 通过反射来创建流程对象,也就是我们只需要定义了流程的类就行,不需要写实例化流程类的逻辑,然后会调用 ProcedureManager 的 Initialize 方法,进行初始化,再以 m_EntranceProcedure 为起始状态,启动流程状态机。
可视化配置流程
上文流程组件中提到,既然是通过 m_AvailableProcedureTypeNames 来创建实例,并以 m_EntranceProcedure 为起始状态,启动流程状态机,那么这两个变量是怎么来的呢。如上图所示,我们直接通过流程组件的 Inspector 来配置,GF 会通过反射获取所有继承 ProcedureBase 的子类,并展示在此面板,我们只需要勾选需要流程即可把它加入到 m_AvailableProcedureTypeNames 中,而面板上的 Entrance Procedure 则代表了 m_EntranceProcedure,这里我们选择了 StarForce.ProcedureLaunch 作为起始状态,那么 ProcedureLaunch 类中的 OnEnter 方法中的逻辑,就是我们游戏启动后最先执行的游戏业务逻辑。
示例
本模块示例直接引用 GF 的官方 Demo 中的前两个流程的代码,个人认为非常有参考价值,若对流程仍然有疑问,相信把官方 Demo 的流程都看一遍就明白了~
启动流程
闪屏流程
Inspector 面板
Procedure 组件的 Inspector 面板在运行时会禁止配置操作,且最上面会多出一行信息显示当前正处于的流程。
思考
既然已经有状态机模块了,为什么还要另外封装一个流程模块?自己单独用一个状态机实例去管理不是一样效果吗
笔者的看法是,仅仅在功能上来看,是一样的,差别主要是以下:
- 普通的状态机状态一般属于各自系统去管理,系统外部不会去访问他们,而流程则很可能需要在各个系统访问,以获取当前流程信息,所以需要为专门管理流程的状态机提供一个全局访问的接口。GF 的做法则是把 Procedure 模块单独提出来与 FSM 同级,都属于全局访问的模块。
- 流程需要继承自 ProcedureBase,而 ProcedureBase 限定了持有者为 ProcedureManager,把流程与普通状态进一步划分开来。
- 基于 GF 在会在 Hierarchy 挂上各模块对应的组件以初始化模块,以及利用编辑器扩展实现可视化。在流程单独作为一个模块后,可以更方便地可视化配置和调试。
最后
GameFramework 解析 系列目录:GameFramework 解析:开篇
个人原创,未经授权,谢绝转载!