官方说明: 提供创建、使用和销毁有限状态机的功能,一些适用于有限状态机机制的游戏逻辑,使用此模块将是一个不错的选择。
有限状态机并不是游戏中独有的,我们看一下其他的介绍:
有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。在计算机科学中,有限状态机被广泛用于建模应用行为、硬件电路系统设计、软件工程,编译器、网络协议、和计算与语言的研究。
图片来源:https://blog.csdn.net/DataAlchemist/article/details/98450010
参考上面两幅图,当玩家处于当前状态时,玩家会执行某些操作,当这些操作触发切换状态的条件时,则切换状态。如果用if else来判断则太过复杂不便于梳理流程,以及一堆条件变量控制,当状态改变或触发条件改变,整个流程的维护是很复杂的,我们不能专注当前状态要做的事,需要去看前后关联的触发条件是否会有问题,这些条件变量在其他状态是怎样的,会不会由于条件变量的改变导致其它问题,因此诞生了状态机。我们只需关注当前状态要干什么,干完你之后什么条件下会触发下一个状态。
前面有写过,流程是对状态机的封装,我们来理解一下流程的思路,首先进入游戏的初始状态完成版本构建、语言配置等工作即可触发Splash 动画流程展示,ProcedureSplash流程中选择资源加载模式,例如选择编辑器模式,则进入ProcedurePreload资源预加载流程。相关游戏资源加载完成后,则进入切换场景的流程ProcedureChangeScene。状态机所解决的问题与流程需要完成的事情是完全吻合的。
流程是对状态机做了一层封装,抽丝剥茧理解起来相对复杂,抓不住问题的本质,这里我们做一个简单地Demo来完成状态机学习。借用一下浅墨大哥写的设计模式和案例:(缅怀,人走了,知乎博客还在影响着我们)
【游戏设计模式】之三 状态模式、有限状态机 & Unity版本实现
状态机Demo:Behavioral Patterns-State Pattern-Exmaple4
接下来会参考这个案例用框架完成这个Demo的功能,Demo的流程图如下
ProcedureFSM:流程类
FSM_Hero:英雄逻辑类
State:FsmState
创建ProcedureFSM流程类,在流程类中创建英雄实体并添加逻辑类FSM_Hero,英雄逻辑类中创建四种状态类,随后在四个状态类中完成触发及状态切换等工作。(流程启动,实体创建及数据表配置请参考本专栏之前的内容)
下面贴上代码:
下蹲状态:
public class DuckingState : FsmState<FSM_Hero>
{
public override void OnEnter(IFsm<FSM_Hero> fsm)
{
Debug.Log("------------------------Heroine in DuckingState~!(进入下蹲躲避状态!)");
}
public override void OnUpdate(IFsm<FSM_Hero> fsm, float elapseSeconds, float realElapseSeconds)
{
HandleInput(fsm);
}
public void HandleInput(IFsm<FSM_Hero> fsm)
{
if (Input.GetKeyDown(KeyCode.DownArrow))
{
Debug.Log("已经在下蹲躲避状态中!");
return;
}
if (Input.GetKeyUp(KeyCode.UpArrow))
{
Debug.Log("get GetKeyUp.UpArrow!");
ChangeState<StandingState>(fsm);
}
}
}
站立状态:
public class StandingState : FsmState<FSM_Hero>
{
public override void OnEnter(IFsm<FSM_Hero> fsm)
{
Debug.Log("------------------------Heroine in StandingState~!(进入站立状态!)");
}
public override void OnUpdate(IFsm<FSM_Hero> fsm, float elapseSeconds, float realElapseSeconds)
{
HandleInput(fsm);
}
public void HandleInput(IFsm<FSM_Hero> fsm)
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
Debug.Log("get KeyCode.UpArrow!");
ChangeState<JumpingState>(fsm);
}
if (Input.GetKeyDown(KeyCode.DownArrow))
{
Debug.Log("get KeyCode.DownArrow!");
ChangeState<DuckingState>(fsm);
}
}
}
下斩状态:
public class DrivingState : FsmState<FSM_Hero>
{
public override void OnEnter(IFsm<FSM_Hero> fsm)
{
Debug.Log("------------------------Heroine in DrivingState~!(进入下斩状态!)");
}
public override void OnUpdate(IFsm<FSM_Hero> fsm, float elapseSeconds, float realElapseSeconds)
{
HandleInput(fsm);
}
public void HandleInput(IFsm<FSM_Hero> fsm)
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
Debug.Log("get KeyCode.UpArrow!");
ChangeState<StandingState>(fsm);
}
}
}
跳跃状态:
public class JumpingState : FsmState<FSM_Hero>
{
public override void OnEnter(IFsm<FSM_Hero> fsm)
{
Debug.Log("------------------------Heroine in JumpingState~!(进入跳跃状态!)");
}
public override void OnUpdate(IFsm<FSM_Hero> fsm, float elapseSeconds, float realElapseSeconds)
{
HandleInput(fsm);
}
public void HandleInput(IFsm<FSM_Hero> fsm)
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
Debug.Log("get GetKeyDown.UpArrow! but already in Jumping! return!(已经在跳跃状态中!)");
return;
}
if (Input.GetKeyDown(KeyCode.DownArrow))
{
Debug.Log("get KeyCode.DownArrow!");
ChangeState<DrivingState>(fsm);
}
}
}
英雄逻辑类
public class FSM_Hero : EntityLogic
{
public override void OnInit(object userData)
{
base.OnInit(userData);
FsmState<FSM_Hero>[] heroState =
{
new StandingState(),
new DuckingState(),
new JumpingState(),
new DrivingState(),
};
var heroFsm = StarForce.GameEntry.Fsm.CreateFsm(this, heroState);
heroFsm.Start<StandingState>();
}
public override void OnShow(object userData)
{
base.OnShow(userData);
}
public override void OnHide(bool isShutdown, object userData)
{
base.OnHide(isShutdown, userData);
StarForce.GameEntry.Fsm.DestroyFsm<FSM_Hero>();
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
}
}
Demo流程启动:
public class ProcedureFSM : ProcedureBase
{
public override bool UseNativeDialog
{
get
{
return true;
}
}
public override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
GameEntry.Entity.ShowEntity<FSM_Hero>(99999, "Assets/GameMain/Entities/Hero.prefab", "Aircraft");
}
}
状态机内部不同状态间通常需要数据交互,GetData,SetData,HasData,RemoveData四个接口提供了数据的获取、设置、是否存在、删除的功能。数据以key-value的形式存储于字典中。
框架对于数据做了一层封装,获取数据时,不用关心是什么类型,用统一的接口获取,强制转换。传入数据时,也可根据自己的需要自定义数据类型。
如下代码,是框架定义的数据类型,演示了四种接口的使用
VarBoolean a = new VarBoolean();
a.Value = true;
heroFsm.SetData("name", a);
g = (bool)heroFsm.GetData("name").GetValue();
if (heroFsm.HasData("name"))
{
heroFsm.RemoveData("name");
}
自定义数据类型的两种写法如下,用bool类型做一个演示。
方式一:
public class Boolable : Variable
{
public bool? value;
public Boolable(bool? value = null)
{
this.value = value;
}
public override Type Type => value.GetType();
public override void Clear()
{
value = null;
}
public override object GetValue()
{
return value;
}
public override void SetValue(object value)
{
this.value = (bool)value;
}
}
方式二:
public class Boolean : Variable<bool>
{
///
/// 初始化 System.Boolean 变量类的新实例。
///
public Boolean()
{
}
///
/// 从 System.Boolean 到 System.Boolean 变量类的隐式转换。
///
/// 值。
public static implicit operator Boolean(bool value)
{
Boolean varValue = ReferencePool.Acquire<Boolean>();
varValue.Value = value;
return varValue;
}
///
/// 从 System.Boolean 变量类到 System.Boolean 的隐式转换。
///
/// 值。
public static implicit operator bool(Boolean value)
{
return value.Value;
}
}
两种方式没有本质上的区别,Variable是框架的抽象变量类,变量的类型用泛型表示,如果继承于该类,只需定义构造函数即可。如果对于SetData,GetData有其他的实现形式,可以直接继承于Variable。