http://coder.beitown.com/archives/592

在之前的文章里介绍了一个基础U3D状态机框架(Unity3D游戏开发之状态流框架)即大Switch的枚举状态控制。这种方法虽然容易理解,编程方法也相对简单,但是弊端是当状态变得复杂之后,或需要添加一种新的状态时,会显得非常混乱并且难以下手。故我们需要引进一种更高级的状态机技术来避免这些问题。网上有一些讲述U3D-FSM状态机的文章,但都不针对基础讲解,而且大多带有冗余的与状态机不相关的代码,基础不好的读者容易看不清FSM状态机的核心所在。这里针对网上的一些文章和代码做了一个整理,意图使之简单易懂。

这里关于FSM有限状态机这类名词的解释这里就不再说明了,感兴趣的朋友可以自己去百度下(度娘链接),本文只说重点。

首先是状态机基类State.cs

/**
* 状态基类
*/
public class State[entity_type>
{
public entity_type Target;
//Enter state
public virtual void Enter (entity_type entityType)
{ }
//Execute state
public virtual void Execute (entity_type entityType)
{ }
//Exit state
public virtual void Exit (entity_type entityType)
{ } }

基类之所以设计成含有3个小的状态方法是因为,通常在游戏中有些行为都只是在进入或退出某个状态时出现的,并不会发生在通常的更新步骤中。这样设计就可以有效的将持续性调用语句和一次性调用语句有效的区分开来。(举例:发送技能时的特效,有些是持续性而有些又是一次性的)

接下来我们编写状态机代码,来使直接的这个基类的各个方法运作起来:

using UnityEngine;
using System.Collections; public class StateMachine[entity_type>
{
private entity_type m_pOwner; private State[entity_type> m_pCurrentState;//当前状态
private State[entity_type> m_pPreviousState;//上一个状态
private State[entity_type> m_pGlobalState;//全局状态 /*状态机构造函数*/
public StateMachine (entity_type owner)
{
m_pOwner = owner;
m_pCurrentState = null;
m_pPreviousState = null;
m_pGlobalState = null;
} /*进入全局状态*/
public void GlobalStateEnter()
{
m_pGlobalState.Enter(m_pOwner);
} /*设置全局状态*/
public void SetGlobalStateState(State[entity_type> GlobalState)
{
m_pGlobalState = GlobalState;
m_pGlobalState.Target = m_pOwner;
m_pGlobalState.Enter(m_pOwner);
} /*设置当前状态*/
public void SetCurrentState(State[entity_type> CurrentState)
{
m_pCurrentState = CurrentState;
m_pCurrentState.Target = m_pOwner;
m_pCurrentState.Enter(m_pOwner);
} /*Update*/
public void SMUpdate ()
{ if (m_pGlobalState != null)
m_pGlobalState.Execute (m_pOwner); if (m_pCurrentState != null)
m_pCurrentState.Execute (m_pOwner);
} /*状态改变*/
public void ChangeState (State[entity_type> pNewState)
{
if (pNewState == null) {
Debug.LogError ("can't find this state");
} //触发退出状态调用Exit方法
m_pCurrentState.Exit(m_pOwner);
//保存上一个状态
m_pPreviousState = m_pCurrentState;
//设置新状态为当前状态
m_pCurrentState = pNewState;
m_pCurrentState.Target = m_pOwner;
//进入当前状态调用Enter方法
m_pCurrentState.Enter (m_pOwner);
} public void RevertToPreviousState ()
{
//切换到前一个状态
ChangeState (m_pPreviousState); } public State[entity_type> CurrentState ()
{
//返回当前状态
return m_pCurrentState;
}
public State[entity_type> GlobalState ()
{
//返回全局状态
return m_pGlobalState;
}
public State[entity_type> PreviousState ()
{
//返回前一个状态
return m_pPreviousState;
} }

这个状态机其实还不是最简的,全局和上一个状态的相关部分都可以去掉,但同时功能上就会被削减,故这里将其保留。

现在状态基类和状态机类都有了,我们可以开始编写游戏对象的独立状态类,先编写游戏的总流程状态类,这里命名为MainState.cs

/**
* 全局状态
*/
public class MainState : State[Main>
{ public static MainState instance; /*构造函数单例化*/
public static MainState Instance()
{
if (instance == null)
instance = new MainState(); return instance;
} public override void Enter(Main Entity)
{
//这里添加进入此状态时执行的代码
} public override void Execute(Main Entity)
{
//这里添加持续此状态刷新代码 } public override void Exit(Main Entity)
{
//这里添加离开此状态时执行代码
} } /**
* Ready状态
*/
public class MainState_Ready : State[Main>
{ public static MainState_Ready instance; /*构造函数单例化*/
public static MainState_Ready Instance()
{
if (instance == null)
instance = new MainState_Ready(); return instance;
} public override void Enter(Main Entity)
{
//这里添加进入此状态时执行的代码
} public override void Execute(Main Entity)
{
//这里添加持续此状态刷新代码
//这里是重点 当满足某条件后 我们可以进行状态切换 执行如下代码 切换到 Run状态
Entity.GetFSM().ChangeState(MainState_Run.Instance());
}
public override void Exit(Main Entity)
{
//这里添加离开此状态时执行代码
}
} /**
* Run状态
*/
public class MainState_Run : State[Main>
{
public static MainState_Run instance;
/*构造函数单例化*/
public static MainState_Run Instance()
{
if (instance == null)
instance = new MainState_Run();
return instance;
} public override void Enter(Main Entity)
{
//这里添加进入此状态时执行的代码
} public override void Execute(Main Entity)
{
//这里添加持续此状态刷新代码
//当满足某条件后 我们可以继续进行状态切换 执行如下代码 切换到 Over状态
Entity.GetFSM().ChangeState(MainState_Over.Instance());
} public override void Exit(Main Entity)
{
//这里添加离开此状态时执行代码
}
} /**
* Over状态
*/
public class MainState_Over : State[Main>
{
public static MainState_Over instance;
/*构造函数单例化*/
public static MainState_Over Instance()
{
if (instance == null)
instance = new MainState_Over();
return instance;
} public override void Enter(Main Entity)
{
//这里添加进入此状态时执行的代码
} public override void Execute(Main Entity)
{
//这里添加持续此状态刷新代码
//如之前两个状态类一样 同理 当满足一定状态后 可以切换回Ready状态
Entity.GetFSM().ChangeState(MainState_Ready.Instance());
} public override void Exit(Main Entity)
{
//这里添加离开此状态时执行代码
}
}

代码有点长,主要是为了让大家能够看清楚如何进行一个状态的编写,其实基类都是一样的,都是重复内容。 这里我们看到,除了定义一个全局的状态类之外,我们还添加了Ready、Run、Over三个状态。重点注意一下Execute函数,这里是状态切换的关键,当带此状态绑定的对象Update时就在不停的执行Execute里的代码段,当满足一定条件后,即达成状态的切换。

这里我们看一下之前的状态机代码里的ChangeState方法,就知道整个状态切换是如何工作的了:

/*状态改变*/
public void ChangeState (State[entity_type> pNewState)
{
if (pNewState == null) {
Debug.LogError ("can't find this state");
} //触发退出状态调用Exit方法
m_pCurrentState.Exit(m_pOwner);
//保存上一个状态
m_pPreviousState = m_pCurrentState;
//设置新状态为当前状态
m_pCurrentState = pNewState;
m_pCurrentState.Target = m_pOwner;
//进入当前状态调用Enter方法
m_pCurrentState.Enter (m_pOwner);
}

可以看到当状态切换时,会自动触发当前状态的Exit方法和目标状态的Enter方法。这样就完成了一整个状态的切换过程。

到这里整个有限状态机体系基本就算完工了,剩下的是如何在Main里进行MainState类的创建及使用,Main.cs代码如下:

using UnityEngine;
using System.Collections; public class Main : MonoBehaviour{ StateMachine[Main> m_pStateMachine;//定义一个状态机 void Start () { m_pStateMachine = new StateMachine[Main>(this);//初始化状态机
m_pStateMachine.SetCurrentState(MainState_Ready.Instance()); //设置一个当前状态
m_pStateMachine.SetGlobalStateState(MainState.Instance());//设置全局状态
} void Update ()
{
m_pStateMachine.SMUpdate();
} /*返回状态机*/
public StateMachine[Main> GetFSM ()
{
return m_pStateMachine;
} }

写到这里我们整个状态机的框架及使用流程就基本结束了,这里要注意几个问题: ①不要在SetCurrentState()方法调用前,调用ChangeState()方法,否则会出现null对象错误,具体原因很简单,看一下ChangeState()里的代码调用了哪些变量就知道了。 ②状态间的通信,这个状态机其实还是有未完善的地方的,目前状态间的通知是通过直接调用其他状态机的ChangeState()方法实现的,这样势必要先获取该对象的脚本,这个功能待完善吧。 ③在U3D里每个游戏对象初始化并调用Start()方法的时机是不一样的,所以要注意,开始游戏时不要直接进入开始状态,而是要有一个等待态来让所有的游戏对象完成Start()方法后再调用这些对象的状态机。

另外,多个状态机间的通信,就像上文②中所述那样,仅仅是通过调用ChangeState()方法来实现,并不是非常完善,所以暂时不做讲解,以免误导大家,待日后有较好解决方案再另行开篇。 此FSM状态机仅为一个雏形,还有很多功能及优化要做,但对于入门FSM有限状态机来说,已经实现了其最主要的功能。不足之处欢迎大家提出讨论,并帮助加以完善。

谢谢关注。

U3D-FSM有限状态机的简单设计的更多相关文章

  1. Unity——FSM有限状态机

    FSM有限状态机 一.设计思路 1.共同的状态父类,提供可重写的进入,保持,退出该状态的生命周期方法: 2.状态机,管理所有状态(增删查改),状态机运行方法(Run): 3.在角色控制器中,实例化状态 ...

  2. FSM有限状态机

    1.什么是有限状态机 有限状态机(Finite State Machine),简称FSM,它由一组有限个状态.输入和根据输入及现有状态转换为下一个状态的转换函数组成,当然,通常每个状态机都必须有一个初 ...

  3. Unity中FSM有限状态机

    什么是FSM FSM 即有限状态机,它是一个状态管理系统,表示一个对象的几种状态在指定条件下转移行为,即随着条件的不断改变内部状态不断地切换. FSM用处或者使用背景 通常使用FSM去实现一些简单的A ...

  4. Java消息系统简单设计与实现

    前言:由于导师在我的毕设项目里加了消息系统(本来想水水就过的..),没办法...来稍微研究研究吧..简单简单... 需求分析 我的毕设是一个博客系统,类似于简书这样的,所以消息系统也类似,在用户的消息 ...

  5. 学生与部门管理app-产品功能与界面的简单设计

    学生与部门管理app-产品功能与界面的简单设计 1. 结对成员学号 我:********* 大佬:*******10 2. 需求分析(NABCD模型) 2.1 N-需求 各个部门在开学初占据学校青春广 ...

  6. C#网络编程TCP通信实例程序简单设计

    C#网络编程TCP通信实例程序简单设计 采用自带 TcpClient和TcpListener设计一个Tcp通信的例子 只实现了TCP通信 通信程序截图: 压力测试服务端截图: 俩个客户端链接服务端测试 ...

  7. Java秒杀简单设计二:数据库表和Dao层设计

    Java秒杀简单设计二:数据库表Dao层设计 上一篇中搭建springboot项目环境和设计数据库表  https://www.cnblogs.com/taiguyiba/p/9791431.html ...

  8. SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建

    SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建 技术栈 : SpringBoot + shiro + jpa + freemark ,因为篇幅原因,这里只 ...

  9. 3.NetDh框架之缓存操作类和二次开发模式简单设计(附源码和示例代码)

    前言 NetDh框架适用于C/S.B/S的服务端框架,可用于项目开发和学习.目前包含以下四个模块 1.数据库操作层封装Dapper,支持多种数据库类型.多库实例,简单强大: 此部分具体说明可参考博客: ...

随机推荐

  1. 移动互联网App兼容性测试

    我建议大家也可以参考一些针对App监测和统计的网站,都非常有意义,具体如下: 友盟品牌手机排行榜  http://www.umeng.com/ 移动观象台   https://www.talkingd ...

  2. TCP滑动窗口协议

    TCP的首部中​有一个很重要的字段就是16位长的窗口大小,它出现在每一个TCP数据报中,配合32位的确认序号,用于向对端通告本地socket的接收窗口大小.也就是说,如果本地socket发送一个TCP ...

  3. TinyBlob、Blob、MediumBlob、LongBlob大小

    BLOB类型的字段用于存储二进制数据 MySQL中,BLOB是个类型系列,包括:TinyBlob.Blob.MediumBlob.LongBlob,这几个类型之间的唯一区别是在存储文件的最大大小上不同 ...

  4. Android 8 AudioPolicy 分析

    AudioTrack最终会调用AudioPolicyManager::getOutput(); frameworks\av\services\audiopolicy\managerdefault\Au ...

  5. JDBC查询数据实例

    在本教程将演示如何在JDBC应用程序中,查询数据库的一个表中数据记录. 在执行以下示例之前,请确保您已经准备好以下操作: 具有数据库管理员权限,以在给定模式中数据库表中查询数据记录. 要执行以下示例, ...

  6. 【转载】C#反射机制详解

    反射的定义:审查元数据并收集关於它的类型信息的能力,元数据(编辑后的基本数据单元)就是一大堆表,编译器会创建一个类定义表,一个字段定义表,一个方法定义表等,System.Reflection命名空间包 ...

  7. eclipse中svn提交报错的解决

  8. 8款最新CSS3表单 环形表单很酷

    当我们在网站上注册登录还是提交评论,都需要用到表单,今天我们来分享8款最新CSS3表单,有几个效果很酷很特别,有些也非常实用,一起来看看. 1.CSS3环形特色表单 转圈切换表单焦点 这款CSS3表单 ...

  9. ESB架构之企业实施案例

    ESB架构之企业实施案例 ESB解决令企业最头痛的信息系统整合问题 SOA 架构中的ESB是更好的应用于异构系统集成整合还是用于统一服务调用/基础服务实施

  10. php过滤字段htmlentities,htmlspecialchars,strip_tags

    1.strip_tags:过滤html标签比如<a> <html> <script> 如: $str = '<a href="test.html&q ...