U3D-FSM有限状态机的简单设计
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有限状态机的简单设计的更多相关文章
- Unity——FSM有限状态机
FSM有限状态机 一.设计思路 1.共同的状态父类,提供可重写的进入,保持,退出该状态的生命周期方法: 2.状态机,管理所有状态(增删查改),状态机运行方法(Run): 3.在角色控制器中,实例化状态 ...
- FSM有限状态机
1.什么是有限状态机 有限状态机(Finite State Machine),简称FSM,它由一组有限个状态.输入和根据输入及现有状态转换为下一个状态的转换函数组成,当然,通常每个状态机都必须有一个初 ...
- Unity中FSM有限状态机
什么是FSM FSM 即有限状态机,它是一个状态管理系统,表示一个对象的几种状态在指定条件下转移行为,即随着条件的不断改变内部状态不断地切换. FSM用处或者使用背景 通常使用FSM去实现一些简单的A ...
- Java消息系统简单设计与实现
前言:由于导师在我的毕设项目里加了消息系统(本来想水水就过的..),没办法...来稍微研究研究吧..简单简单... 需求分析 我的毕设是一个博客系统,类似于简书这样的,所以消息系统也类似,在用户的消息 ...
- 学生与部门管理app-产品功能与界面的简单设计
学生与部门管理app-产品功能与界面的简单设计 1. 结对成员学号 我:********* 大佬:*******10 2. 需求分析(NABCD模型) 2.1 N-需求 各个部门在开学初占据学校青春广 ...
- C#网络编程TCP通信实例程序简单设计
C#网络编程TCP通信实例程序简单设计 采用自带 TcpClient和TcpListener设计一个Tcp通信的例子 只实现了TCP通信 通信程序截图: 压力测试服务端截图: 俩个客户端链接服务端测试 ...
- Java秒杀简单设计二:数据库表和Dao层设计
Java秒杀简单设计二:数据库表Dao层设计 上一篇中搭建springboot项目环境和设计数据库表 https://www.cnblogs.com/taiguyiba/p/9791431.html ...
- SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建
SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建 技术栈 : SpringBoot + shiro + jpa + freemark ,因为篇幅原因,这里只 ...
- 3.NetDh框架之缓存操作类和二次开发模式简单设计(附源码和示例代码)
前言 NetDh框架适用于C/S.B/S的服务端框架,可用于项目开发和学习.目前包含以下四个模块 1.数据库操作层封装Dapper,支持多种数据库类型.多库实例,简单强大: 此部分具体说明可参考博客: ...
随机推荐
- Python 引用
python引用python中的数值类型变量也是引用,例如: a = 100b=a那么a和b指向同一块内存但是当修改a或者b的值得时候,Python会新分配一块内存来存储新的值 python中不可变类 ...
- Tornado + Celery + RabbitMQ
声明:代码是从项目中截取的, 为进行测试 使用Celery任务队列,Celery 只是一个任务队列,需要一个broker媒介,将耗时的任务传递给Celery任务队列执行,执行完毕将结果通过broker ...
- 第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询
第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询 bool查询说明 filter:[],字段的过滤,不参与打分must:[] ...
- C++ 查询某个变量的类型
#include <typeinfo> int iii = 100; printf("%s\n",typeid(iii).name());//类型 详见:http:// ...
- [转]android ANR产生原因和解决办法
ANR (Application Not Responding) ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(AN ...
- C# 结构体 struct
C# 结构体 struct C#中结构类型和类类型在语法上非常相似,他们都是一种数据结构,都可以包括数据成员和方法成员. 结构和类的区别: 1.结构是值类型,它在栈中分配空间:而类是引用类型,它在堆中 ...
- VS2013 此模板尝试加载组件程序集”NuGet.VisualStudio.interop,Version=1.0.0.0 的解决办法
此模板尝试加载组件程序集 NuGet.VisualStudio.Interop 分析错误 既然错误信息提示模板尝试加载组件程序集,那说明NuGet.VisualStudio.Interop程序集不 ...
- Jedis客户端操作redis缓存命令详解
1.对value操作的命令 exists(key):确认一个key是否存在 del(key):删除一个key type(key):返回值的类型 keys(pattern):返回满足给定pattern的 ...
- lakala GradientBoostedTrees
/** * Created by lkl on 2017/12/6. */ import org.apache.spark.mllib.evaluation.BinaryClassificationM ...
- 微信小程序省市区选择器对接数据库
前言,小程序本身是带有地区选着器的(网站:https://mp.weixin.qq.com/debug/wxadoc/dev/component/picker.html),由于自己开发的程序的数据是很 ...