关于基于unity3d(C#)的有限状态机设计的攻略说明
1. 什么是有限状态机
有限状态机(Finite State Machine,FSM)是一种将对象的行为分解为易于处理的“块”或状态的机制。以简单的灯的开关为例,它就是一个典型的有限状态机,具有“开”和“关”两种状态。
再设想一个机器小猫,它的肚子有一个插槽,可以放入多个用以控制小猫状态的模块。这些模块包含了小猫的不同行为,如玩毛线、吃鱼或睡觉等。若没有插入任何模块,小猫就如同一个死气沉沉的金属雕塑,只能静静坐着。当小猫处于玩毛线状态时,它会监控自身的饥饿等级。一旦饥饿等级上升,小猫会自动切换到吃鱼状态。当小猫吃饱后,又会跳回到玩毛线状态。在这个示例中,机器小猫就相当于我们的程序,而里面的模块则对应着程序里的各种状态。
2. 为什么要用有限状态机
在通常的程序设计中,实现状态转换往往会使用一系列的 if - then 语句或者 switch 语句。以下是一个游戏状态转换的示例代码:
while (游戏未结束)
{
switch (游戏状态)
{
case 资源加载:
case 进入关卡:
Load_gate(); // 加载背景、飞机、炮弹的图片
break;
case 游戏菜单:
if (游戏结束)
计算游戏结果;
break;
case 游戏进行:
New_paodan(); // 产生新炮弹
Move(); // 计算出该时刻飞机以及所有炮弹所在的位置
Is_pengzhuang(); // 碰撞判断
break;
case 游戏暂停:
Thread_pause(); // 游戏暂停操作
break;
}
}
// Draw() 函数框架的伪代码
Draw()
{
switch (游戏状态)
{
case 游戏进行:
Draw_background(); // 绘制背景
Draw_paodan(); // 画炮弹
Draw_feiji(); // 画飞机
break;
case 其他:
// 略
break;
}
}
这种方法直观易懂且看似合理,但当应用场景稍微复杂一些时,switch 或 if - then 语句就会变得难以维护。随着更多状态和条件的加入,代码结构会变得像意大利面条一样混乱,程序流程难以理解,调试也会成为一场噩梦。此外,这种方式缺乏灵活性。在初次规划整个程序时,除非你是天才,否则几乎肯定需要频繁修改 switch 或 if - then 语句。
另外,当需要让对象在进入或退出某个状态时执行特定操作时,switch 或 if - then 语句的局限性就更加明显。例如,当一个敌人对象进入逃跑状态时,可能希望它挥着胳膊并喊出“啊!”;当它逃脱并进入巡逻状态时,可能希望它发出一声叹息并擦去额头的汗水。这些行为通常只在进入或退出状态时出现,而不会在常规的更新步骤中执行。要在 switch 或 if - then 语句架构中实现这些功能,会使代码变得糟糕且难以维护。
3. 如何使用有限状态机
3.1 基类设计
整个程序的基础由 BaseGameEntity 类继承而来。该类用于存储每个对象的唯一 ID 号码,并定义一个 Update 函数,该函数会在每个更新步骤被调用。在 Unity3D 中,每个对象都自带了 Update 函数,因此基类不需要再重复定义。BaseGameEntity 类的声明如下:
public class BaseGameEntity : MonoBehaviour
{
private int m_ID; // 每个对象具有一个唯一的识别数字
private static ArrayList m_idArray = new ArrayList();
public int ID()
{
return m_ID;
}
protected void SetID(int val)
{
// 这个函数用来确认 ID 是否正确设置
if (m_idArray.Contains(val))
{
Debug.LogError("id cuo wu ");
return;
}
m_idArray.Add(val);
m_ID = val;
}
public int getID()
{
return m_ID;
}
}
3.2 对象类设计
对象类继承自 BaseGameEntity 类,包含该对象的各种特性数据成员。例如,如果该对象是一个人物角色,类中会包含其健康状况、疲劳程度、位置等信息。每个对象有一个指向 State(状态)类的实例,以及一个用于改变指针指向状态的方法。以下是一个 People 类的示例:
public class People : BaseGameEntity
{
// 指向一个状态实例的指针
StateMachine m_pStateMachine;
// 角色当前的位置
public location_type m_Location;
// 矿工角色包中装了多少金块
public int m_iGoldCarried;
// 矿工角色在银行存了多少钱
public int m_iMontyInBank;
// 矿工角色口渴程度
public int m_iThirst;
// 矿工角色疲劳程度
public int m_iFatigue;
void Start()
{
// 设置 ID
SetID((int)People);
// 设置状态接口,并指向一个状态
m_pStateMachine = new StateMachine(this);
m_pStateMachine.SetCurrentState(People_GloballState.Instance());
}
void Update()
{
// 调用正在使用的状态
m_pStateMachine.SMUpdate();
}
public StateMachine GetFSM()
{
// 返回状态管理机
return m_pStateMachine;
}
}
3.3 状态基类设计
状态基类 State 定义了状态的基本行为,包括进入状态、正常执行状态和退出状态的方法。其代码如下:
// C# 范型
public class State<entity_type>
{
public entity_type Target;
// 进入状态
public virtual void Enter(entity_type entityType)
{
}
// 状态正常执行
public virtual void Execute(entity_type entityType)
{
}
// 退出状态
public virtual void Exit(entity_type entityType)
{
}
}
3.4 状态控制机类设计
状态控制机类 StateMachine 负责管理对象的状态转换和状态执行。其代码如下:
using UnityEngine;
using System.Collections;
public class StateMachine<entity_type>
{
// entity 实体
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 SetGlobalState(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);
}
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("该状态不存在");
}
// 退出之前状态
m_pCurrentState.Exit(m_pOwner);
// 保存之前状态
m_pPreviousState = m_pCurrentState;
// 设置当前状态
m_pCurrentState = pNewState;
m_pCurrentState.Target = m_pOwner;
// 进入当前状态
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;
}
public bool HandleMessage(Telegram msg)
{
// 处理消息
if (m_pCurrentState != null && m_pCurrentState.OnMessage(m_pOwner, msg))
{
return true;
}
// 处理全局状态消息
if (m_pGlobalState != null && m_pGlobalState.OnMessage(m_pOwner, msg))
{
return true;
}
return false;
}
}
3.5 角色状态类设计
角色的状态类继承自 State 基类。以下是一个 People_GloballState 类的示例,它采用了单例设计模式,确保该对象只能实例化一次,并且全局可访问。
public class People_GloballState : State<People>
{
private static People_GloballState instance;
// Singleton 设计模式,确保了一个对象只能实例化一次,它是全局可访问的。
public static People_GloballState Instance()
{
if (instance == null)
instance = new People_GloballState();
return instance;
}
public override void Enter(People Entity)
{
// base.Enter (Entity);
}
public override void Execute(People Entity)
{
// base.Execute (Entity);
}
public override void Exit(People Entity)
{
// base.Exit (Entity);
}
}
这是角色对象的全局状态,其他状态类的设计与之类似。通过这种方式,可以实现基于 Unity3D(C#)的有限状态机设计,提高代码的可维护性和灵活性。