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. 关于Unity中的刚体和碰撞器的相关用法(二)

    在关于Unity中的刚体和碰撞器的相关用法(一)的基础上 有一个plane平面,一个ball球体,都挂了碰撞器,ball挂了刚体Rigidbody,写了一个脚本ball挂载在球体上,球体从空中落下装机 ...

  2. (笔记)Mysql命令mysqladmin:修改用户密码

    mysqladmin命令用于修改用户密码. mysqladmin命令格式:mysqladmin -u 用户名 -p 旧密码 password 新密码 1) 给root加个密码ab12首先在DOS下进入 ...

  3. e775. 设置JList组件项的维数

    By default, the width of the list is determined by the longest item and the height is determined by ...

  4. ASP.NET MVC使用Oauth2.0实现身份验证

    随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的 ...

  5. interproscan 软件对序列进行GO 注释

    interproscan 软件实际上将对输入的查询序列和interpro 数据库中的序列去比对,将比对上的序列对应的GO信息作为查询序列的GO注释 在interpro 数据库中,每条蛋白质序列有一个唯 ...

  6. 微信小程序开发1_资料收集

    [前言] 小程序 [一.资料] 微信官网 开发文档.工具 等 https://mp.weixin.qq.com/cgi-bin/wx [二] 创建小程序和编辑代码,先安装 开发者工具 ,根据所使用的操 ...

  7. linux下配置SS5(SOCK5)代理服务

    安装sock5所需依赖开发库: # yum install pam-devel openldap-devel openssl-devel 下载并解压安装sock5 # wget http://down ...

  8. <转>Win8.1+CentOS7 双系统 U盘安装

    0.准备工作 1.宏碁 Aspire 4752G 笔记本 2.Win8.1 企业版操作系统 3.8G 以上 U 盘 4.UltraISO(当然也可以选择其他的U盘制作工具,看个人喜好) 5.下载 Ce ...

  9. 以Windows服务方式启动MySQL,并将其默认编码设置为UTF-8

    系统环境:Windows XP Professional 版本 2002 Service Pack 3 // 第1步:创建选项文件.首先下载mysql-5.5.12-win32.zip,只需复制mys ...

  10. .net网站建设页面提交后css失效的问题

    问题描述:.net网站建设在提交后出现css部分失效,如div位置,字体大小. 问题解决:原因是,过去的提示语句我们一律使用了Response.write("<script>al ...