游戏开发过程中,各种游戏状态的切换无处不在。但很多时候,简单粗暴的if else加标志位的方式并不能很地道地解决状态复杂变换的问题,这时,就可以运用到状态模式以及状态机来高效地完成任务。状态模式与状态机,因为他们关联紧密,常常放在一起讨论和运用。而本文将对他们在游戏开发中的使用,进行一些探讨。
当然,要我一下讲的很明白,估计也不太可能,还是老样子,我们进行文章和代码分析,最后进行总结。首先我们先整一些小案例来进行相关说明,明白有限状态机有哪些运用,之后根据这些案例进行总结,最后规范unity中有限状态机和有限状态模式的下发以及作用。
案例之一:NPC发现Player发射子弹,简单的if(){ }elseif(){ } 也是一种状态机,只是比较简单
代码如下:
效果如下
案例二:枚举和Switch case的结合,这种模式可以采用,代码比较规整,也比较好理解:
首先用枚举把所有状态列举出来,然后写一个方法,进行switch case进行每个状态绑定的函数进行列举。然后,对于每个枚举类型绑定的函数进行实际化。然后再Update函数当中进行监听。
其实也可以把Fsm_enum类似的脚本整成一个单例,然后可以进行全局调用。
以上两种是简单的有限状态机。在游戏中人物的状态是不断变化的,所以写一个FSM来管理状态是必要的。一个有限状态机是一个设备,或者是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得一个状态变换到另一个状态,或者是使一个输入或者一种行为的发生。一个有限状态机在任何瞬间只能处在一种状态。
案例三:常见的状态机设计模式
有限状态机设计的核心原则就是:单一职责原则和里氏替换原则。单一职责就是每一个状态都有专门的一个脚本进行处理他的行为。里氏替换原则:所有具体状态类继承于一个抽象类,这样不管是那个状态实例化的对象,都可以借助基类进行。
大体上状态机模式的实现需要三个要点:1、为所有的状态定义一个接口或者基类别
2、为每个状态定义一个类
3、恰当进行状态的委托(关联起来,怎么实现类和方法的调用)
通常来说,状态模式中状态对象的存放有两种实现存放的思路:
1、静态状态,就是初始化的时候把所有可能的状态都new好,状态切换时通过赋值进行改变当前的状态
2、实例化状态,每次切换状态时动态的new 出新的状态
我们来分析下面的例子:有三个场景MainMenuSceneState、BattleScene、StartSceneState。一个基类ISceneState、一个场景控制类SceneStateManager、一个开启游戏(或者说所有脚本的类)GameContext类。脚本代码如下,我在GameContext类中做了代码的逐条分析。
ISceneState基类
BattleScene
MainMenuSceneState
StartSceneState
SceneStateManager
开始游戏场景的一个类,这个类继承于MonoBehaviour,挂载在开始场景的某一个游戏对象上
剩下的就是上面这个案例代码的全部解析
///
///各个场景要继承的基类
///
//ISceneState类中的内容
//protected string mSceneName{get; set;}
//public ISceneState(string sceneName){ this.mSceneName = sceneName; }
//在创建ISceneState类的时候,就会给string类型的mSceneName赋值
///
///各个场景的管理类SceneStateManager
///
//这个类是一个单例,SceneStateManager.GetInstance()进行获得SceneStateManager类中唯一的一个对象
//privete ISceneState mState{get; set;} 这个是用来赋值的。
//public void SetSceneState(ISceneState state)
//{
// if (mState != null) //如果当前场景不为空,就结束当前场景
// {
// mState.EndScene();
// }
// mState = state; //同时开始传入要开始的场景 经过这一步mState肯定不为空了
// if (mState != null)
// {
// mState.StartScene();
// }
//}
//这个SetSceneState()方法中无形之间有了一个循环,当你调用这个方法传入一个ISceneState子类对象时候
//开始判断如果当前mState不为空,就表示已经有场景在运行了。那就当有调用SetScenenState()的时候mState.EndScenen;
//然后再给mState = state 相当于重新赋值
//最后在进行mState.StartScenen
///
///BattleScene继承ISceneState
///
//继承有参构造器public BattleScene() : base("BattleScene"){ },当创建BattleScene对象的时候就会给mScenenName赋值
//就可以执行
//public override void StartScene()
//{
// SceneManager.LoadScene(this.mSceneName)
//}
//同理
///
///MainMenuSceneState类
///
//public class MainMenuSceneState : ISceneState
//{
// public MainMenuSceneState() : base("MainMenuScene")
// {
// }
// public override void StartScene()
// {
// SceneManager.LoadScene(this.mSceneName);
// }
//}
///
///场景开始界面控制脚本StartSceneState
///
//同样StartSceneState继承ISceneState类,也是一个界面,只是是开始界面,所以有一些功能的集成
//首先还是继承ISceneState这个构造器,只要创建StartSceneState这个类的子类,就会给mSceneName进行赋值
//public StartSceneState():base("StartScene "){ }
//然后在开始界面中添加两个按钮事件 控制界面的转换 重写基累中的StartScene方法
//public override void StartScene()
//{
// GameObject.FindGameObjectWithTag("Battle").GetComponent
在这个案例中,我们首先定义可一个基类ISceneState类,在这个基类中,定义了一个string类型的属性(mScenenName),以及一个带有string类型参数的构造器。当启动构造器的时候就给属性赋值。此外还包含三个虚函数SartScene(){ }、UpdateScene(){ }、EndScene(){ }。当三个子类MainMenuSceneState、BattleScene、StartSceneState继承以后、首先通过构造器给mSceneName赋值,然后就是可执行切换场景的功能。
根据之前我们总结的这个案例符合状态模式的实现三个要点,在状态对象存放方式上采用了第二种:即实例化状态,每次切换状态的时候动态的new出来新的状态。这一篇就先到这里,我准备用两个篇幅专门用于解析状态机和状态模式。
之前买了一本书,关于游戏开发和设计模式的,看到了状态模式设计,关于用状态模式进行场景场景切换的代码小框架。我写这些的用处主要时自己学习,加深巩固。如果你会了,或者觉得小儿科,原谅我,你可以无视。所以,即便很多类似相同,我还是决定继续刨析一遍。