前言
ProcedureComponent 组件是上一份笔记中的 21个组件之一, 在Awake中注册到了 GameEntry的组件列表中, 今天来学习这个组件的执行逻辑和内部实现
状态机
先复习一下第一篇中的内容:
有限状态机 (FSM) – 提供创建、使用和销毁有限状态机的功能,一些适用于有限状态机机制的游戏逻辑,使用此模块将是一个不错的选择。
状态机大白话:
举个栗子: 游戏中的角色会有各种状态: 比如 走路, 跑步, 死亡;
玩家会在不同状态之间切换;
当玩家进入走路状态时,播放走路动画;
当玩家从走路状态切换到跑步状态时,退出走路动画, 播放跑步动画;
当玩家从任意状态死亡时,退出当前状态的动画,播放死亡动画
状态机就是负责 管理这些状态和状态切换的 类
总结一下:
有各种状态, 封装一个状态的共同基类, 每个状态都有进入和退出两种行为
玩家只能处于 一个状态, 即 当前状态
状态切换: 前一个状态执行退出操作, 后一个状态执行Enter操作, 当前状态修改为后一个状态
为了简化,删了部分代码, 状态类封装代码
//为了简化,删了很多代码
public abstract class FsmState<T> where T : class
{
/// 有限状态机状态初始化时调用。
protected internal virtual void OnInit(IFsm<T> fsm)
{
}
/// 有限状态机状态进入时调用。
protected internal virtual void OnEnter(IFsm<T> fsm)
{
}
/// 有限状态机状态轮询时调用。
protected internal virtual void OnUpdate(IFsm<T> fsm, float elapseSeconds, float realElapseSeconds)
{
}
/// 有限状态机状态离开时调用。
protected internal virtual void OnLeave(IFsm<T> fsm, bool isShutdown)
{
}
/// 有限状态机状态销毁时调用。
protected internal virtual void OnDestroy(IFsm<T> fsm)
{
}
/// 切换当前有限状态机状态。
protected void ChangeState<TState>(IFsm<T> fsm) where TState : FsmState<T>
{
Fsm<T> fsmImplement = (Fsm<T>)fsm;
fsmImplement.ChangeState<TState>();
}
...
}
为了简化,删了部分代码, 状态机类封装代码:
使用字典容器保存所有的状态,切换状态实质就是执行上一个状态的退出操作,修改当前状态为下一个状态, 执行下一个状态的进入操作,是不是很简单
// 为了简化,删了很多代码
internal sealed class Fsm<T> : FsmBase, IReference, IFsm<T> where T : class
{
private readonly Dictionary<Type, FsmState<T>> m_States;
private FsmState<T> m_CurrentState;
/// 创建有限状态机。
public static Fsm<T> Create(string name, T owner, params FsmState<T>[] states)
{
Fsm<T> fsm = ReferencePool.Acquire<Fsm<T>>();
foreach (FsmState<T> state in states)
{
Type stateType = state.GetType();
fsm.m_States.Add(stateType, state);
state.OnInit(fsm);
}
return fsm;
}
/// <summary>
/// 清理有限状态机。
/// </summary>
public void Clear()
{
if (m_CurrentState != null)
{
m_CurrentState.OnLeave(this, true);
}
foreach (KeyValuePair<Type, FsmState<T>> state in m_States)
{
state.Value.OnDestroy(this);
}
...
}
/// 开始有限状态机。
public void Start(Type stateType)
{
FsmState<T> state = GetState(stateType);
m_CurrentState = state;
m_CurrentState.OnEnter(this);
}
/// 获取有限状态机状态。
public FsmState<T> GetState(Type stateType)
{
FsmState<T> state = null;
if (m_States.TryGetValue(stateType, out state))
{
return state;
}
return null;
}
/// 有限状态机轮询
internal override void Update(float elapseSeconds, float realElapseSeconds)
{
m_CurrentStateTime += elapseSeconds;
m_CurrentState.OnUpdate(this, elapseSeconds, realElapseSeconds);
}
/// 关闭并清理有限状态机。
internal override void Shutdown()
{
ReferencePool.Release(this);
}
/// 切换当前有限状态机状态。
internal void ChangeState(Type stateType)
{
FsmState<T> state = GetState(stateType);
m_CurrentState.OnLeave(this, false);
m_CurrentState = state;
m_CurrentState.OnEnter(this);
}
}
在游戏中,很多地方都可以使用状态机的概念, 再举个栗子:
场景切换: 每一个场景,我们可以认为是一个状态, 前一个场景执行退出操作,后一个场景执行加载和初始化操作,都可以使用状态机来实现;
流程
整个程序的生命周期中,一些重要的操作,也可以视为状态,GF框架将这些操作封装为流程, 流程本质上就是一个状态机。
流程 (Procedure) – 是贯穿游戏运行时整个生命周期的有限状态机。通过流程,将不同的游戏状态进行解耦将是一个非常好的习惯。对于网络游戏,你可能需要如检查资源流程、更新资源流程、检查服务器列表流程、选择服务器流程、登录服务器流程、创建角色流程等流程,而对于单机游戏,你可能需要在游戏选择菜单流程和游戏实际玩法流程之间做切换。如果想增加流程,只要派生自 ProcedureBase 类并实现自己的流程类即可使用
//流程继承自FSMState, 一个流程就是一个状态
public abstract class ProcedureBase : FsmState<IProcedureManager>
{
protected internal override void OnInit(ProcedureOwner procedureOwner)
{
base.OnInit(procedureOwner);
}
/// 进入状态时调用。
protected internal override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
}
/// 状态轮询时调用。
protected internal override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
}
/// 离开状态时调用。
protected internal override void OnLeave(ProcedureOwner procedureOwner, bool isShutdown)
{
base.OnLeave(procedureOwner, isShutdown);
}
/// 状态销毁时调用。
protected internal override void OnDestroy(ProcedureOwner procedureOwner)
{
base.OnDestroy(procedureOwner);
}
流程管理器, 实现类,请自行查看 ProcedureManager.cs
public interface IProcedureManager
{
/// 获取当前流程。
ProcedureBase CurrentProcedure
{
get;
}
/// 获取当前流程持续时间。
float CurrentProcedureTime
{
get;
}
/// 初始化流程管理器。
void Initialize(IFsmManager fsmManager, params ProcedureBase[] procedures);
/// 开始流程。
void StartProcedure<T>() where T : ProcedureBase;
/// 是否存在流程。
bool HasProcedure<T>() where T : ProcedureBase;
/// 获取流程。
ProcedureBase GetProcedure<T>() where T : ProcedureBase;
}
StarForce执行流程
在ProceduceComponent的Start中,使用反射实例化所有的流程(状态), 调用m_ProcedureManager.Initialize把状态数组传入,创建状态机,状态机中的字典容器内容就是流程数组传递过来的值
m_ProcedureManager.StartProcedure, 设置当前状态,执行OnEnter
// procedureComponent.cs
protected override void Awake()
{
base.Awake();
m_ProcedureManager = GameFrameworkEntry.GetModule<IProcedureManager>();
if (m_ProcedureManager == null)
{
Log.Fatal("Procedure manager is invalid.");
return;
}
}
private IEnumerator Start()
{
ProcedureBase[] procedures = new ProcedureBase[m_AvailableProcedureTypeNames.Length];
for (int i = 0; i < m_AvailableProcedureTypeNames.Length; i++)
{
Type procedureType = Utility.Assembly.GetType(m_AvailableProcedureTypeNames[i]);
if (procedureType == null)
{
Log.Error("Can not find procedure type '{0}'.", m_AvailableProcedureTypeNames[i]);
yield break;
}
//使用反射实例化所有流程
procedures[i] = (ProcedureBase)Activator.CreateInstance(procedureType);
if (procedures[i] == null)
{
Log.Error("Can not create procedure instance '{0}'.", m_AvailableProcedureTypeNames[i]);
yield break;
}
if (m_EntranceProcedureTypeName == m_AvailableProcedureTypeNames[i])
{
m_EntranceProcedure = procedures[i];
}
}
if (m_EntranceProcedure == null)
{
Log.Error("Entrance procedure is invalid.");
yield break;
}
//创建状态机,把流程加入到状态字典中
m_ProcedureManager.Initialize(GameFrameworkEntry.GetModule<IFsmManager>(), procedures);
yield return new WaitForEndOfFrame();
//把当前状态设置为 m_EnterProceduce,执行OnEnter方法
m_ProcedureManager.StartProcedure(m_EntranceProcedure.GetType());
}
面板中设置的入口流程为 ProcedureLauncher,即 m_EntranceProceduce
m_ProcedureManager.StartProcedure(m_EntranceProcedure.GetType()); 之后会调用ProceduceLauncher.OnEnter函数
在Launch流程OnEnter中初始化了一些配置, 在OnUpdate轮询中,切换到了 Splash流程
Splash流程,在编辑器模式,直接跳到 PreLoad流程
在PreLoad流程中,加载了配置文件, 配置文件加载完成后, 设置NextSceneID为 Menu, 跳转到 ChangeScene流程
在ChangeScene流程中,加载Menu场景,场景加载完成后, 跳转到ProcedureMenu流程
在Menu流程中,调用GameEntry.UI.OpenUIForm(UIFormId.MenuForm, this), 打开了UI界面;
自定义流程
自定义流程只需要继承自 Procedurebase即可
//我的自定义流程,必须继承ProcedureBase
public class ProcedureILRuntime : ProcedureBase
{
public override bool UseNativeDialog
{
get
{
return false;
}
}
//private IMethod m_Update;
//private IMethod m_Shutdown;
protected override void OnEnter(IFsm<IProcedureManager> procedureOwner)
{
base.OnEnter(procedureOwner);
//GameEntry.ILRuntime.LoadHotfixDll();
}
protected override void OnUpdate(IFsm<IProcedureManager> procedureOwner, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
//GameEntry.ILRuntime.InvokeUpdate();
}
protected override void OnLeave(IFsm<IProcedureManager> procedureOwner, bool isShutdown)
{
base.OnLeave(procedureOwner, isShutdown);
//GameEntry.ILRuntime.InvokeShutdown();
}
}
如果需要在代码中使用自定义流程,在unity编辑器,必须启用自定义流程,在前面打勾即可
然后再代码中就可以切换到自定义流程了
if (GameEntry.ILRuntime.ILRuntimeMode)
{
ChangeState<ProcedureILRuntime>(procedureOwner);
}
else
{
procedureOwner.SetData<VarInt32>(Constant.ProcedureData.NextSceneId, (int)SceneId.Start);
ChangeState<ProcedureChangeScene>(procedureOwner);
}
下一篇开始学时 事件EventComponent,敬请期待,如有错误或不解的地方,还请留言,谢谢!