初学有限状态机
想象一下
想象一个场景,你有一个ARPG游戏里的角色,你的角色有很多骚气的动作:蹬墙跳、滑铲、二段跳、滑翔、过肩摔、格挡反击、咸鱼冲刺、吹口哨……
但是,你的角色并不能在滑铲的时候使出咸鱼冲刺,在滑翔的时候对着敌人使出过肩摔,在挨打的时候对着怪吹口哨,否则其他看到的玩家就会直呼:卧槽,有挂
如果你是Unity菜鸟,你会怎样设计角色控制代码呢,正好我就是菜鸟,我会告诉你应该这样做:声明一溜布尔值来判断角色所处的状态
bool isGround;// 在地面上
bool isHuaXiang;// 正在滑翔
bool isHurt;// 正在挨打
……
当按下动作按键时用if来判断
if(!isHurt)
ChuiKouShao();// 如果角色没有在挨打,就对着怪吹口哨嘲讽

当然,学过有限状态机的你对着我的嘴就是一巴掌:如果角色的状态和行为不断变得复杂,慢慢的,你会创建114514个条件变量,慢慢的,if层级会越来越多。。然后你交给了我一个新的方法——有限状态机:
有限状态机
FSM,有限状态机,可以枚举出有限多个状态,当满足特定的条件时可以在这些条件中来回切换
有限状态机的核心思想:
- 拥有有限个的多种状态
- 当前处于其中一个状态
- 状态之间可以互相切换
比如游戏中的敌人AI,正常情况下敌人会在特定的路线上来回走动进行巡逻,当玩家发出动静或者首次进入视野时会警觉,这时候玩家再次发出动静或者暴露在视野中敌人就会追击玩家,直到玩家消失在视野中
Unity当中的Animator就是一个FSM:

只不过每个状态里存放的是动画,我们的FSM也会沿用这个思想,只不过状态里存放的是逻辑代码
FSM可以说是一个强化版的 switch case ,判断处于哪个状态,执行对应的逻辑,FSM可以很方便地进行扩展,加入新状态只需继承基类,不用修改原来的代码
一个最简单的有限状态机

FSM思想一:拥有有限个的多种状态
我们首先思考一下敌人有哪些状态,这里我只使用了简单的两种状态:
- 巡逻
- 追赶
使用枚举 enum 类型来存储所有的状态便于使用:
public enum StateType
{
Patrol,// 巡逻状态
Chase// 追赶状态
}
FSM思想二:当前处于其中一个状态
接下来需要知道敌人当前正处于哪种状态,使用一个 StateType 类型的变量来存储
private StateType currentState;
FSM思想三:状态之间可以互相切换
在Update中,做状态之间切换的判断,当前正处于哪种状态,就执行对应状态的响应函数
private void Update()
{
switch (currentState)
{
case StateType.Patrol:
OnPatrol();// 巡逻状态的响应函数
break;
case StateType.Chase:
OnChase();// 追击状态的响应函数
break;
default:
break;
}
}
接下来只要补充两个响应函数中的代码逻辑:
private void OnPatrol()
{
if (Vector2.Distance(transform.position, targetPos) < 0.1f)
{
targetPos = (targetPos == patrolPos1) ? patrolPos2 : patrolPos1;
}
transform.position = Vector2.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
if (Vector2.Distance(player.position, transform.position) < dangerDistance)
{
transform.GetComponentInChildren<Text>().text = "Enemy(Chasing)";
enemyMaterial.color = Color.red;
currentState = StateType.Chase;
}
}
private void OnChase()
{
transform.position = Vector2.MoveTowards(transform.position, player.position, speed * Time.deltaTime);
if (Vector2.Distance(transform.position, player.position) > dangerDistance)
{
transform.GetComponentInChildren<Text>().text = "Enemy(patrolling)";
enemyMaterial.color = Color.green;
currentState = StateType.Patrol;
}
}
更复杂点的有限状态机
简单版本的状态机的代码耦合性太大,不便于更改,这时候需要加入一个中间类 状态机管理类 来控制、调用所有的状态;同时为了便于管理,所有的状态都继承自同一个 状态基类 ,这里也可以用接口来实现同样的效果,总体来说就是下面的框架:
- 每个独立的敌人对象都有一个自己的状态机管理器,这个管理器中存放着敌人的所有状态,通过这个管理器来实现自己当前状态的切换和运行
状态枚举
便于规范和使用,用一个枚举来存放游戏中的所有状态
public enum StateEnum
{
Patrol,
Chase
}
状态机管理器类
用字典来存放单个敌人的状态名字和实例,并创建 AddState 函数便于在单个敌人类中对自己拥有状态进行管理
public Dictionary<StateEnum, BaseState> stateDic;
public GameObject aIObject; // 当前状态机管理器的拥有者
public BaseState currentState; // 当前所处于的状态
/// <summary>
/// 向字典中添加State的方法
/// </summary>
/// <param name="stateEnum">State名</param>
public void AddState(StateEnum state)
{
switch (state)
{
case StateEnum.Patrol:
stateDic.Add(state, new PatrolState(this));
break;
case StateEnum.Chase:
stateDic.Add(state, new ChaseState(this));
break;
default: break;
}
}
状态的基类
抽象类,只能被继承。所有状态都具有的共同行为
OnEnter、OnUpdate、OnExit
单个状态类
每个状态单独一个类,存放这个状态拥有的逻辑代码,继承于状态基类
角色对象类
创建StateMachineManager类的实例,每个角色都有自己专属的状态机,因此StateMachineManager类不能是抽象类

浙公网安备 33010602011771号