前言:
有限状态机是一种在实现敌人Ai上比较常用的方法,一个最基本的有限状态机可以由三部分组成:
1.状态基类
2.状态类
3.载体类(敌人类、玩家类等等)
一、首先是状态基类的实现:
载体用玩家类来举例,基类中需要实现的主要方法有:
1、进入状态时的函数,在进入状态时调用一次,在这个函数中,我们需要传入一个载体类的引用,以便后续对载体进行操作
2、退出状态时的函数,在退出状态时调用一次
3、状态转换的函数,判断切换状态的条件,满足条件时切换到对应的状态
4、状态执行函数,在这里使用了两个函数来分别执行Updata和FixedUpdata中的逻辑
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class StateBase
{
public Player player;
/// <summary>
/// 进入状态时执行的函数
/// </summary>
/// <param name="player"></param>
public virtual void OnEnter(Player player) => this.player = player;
/// <summary>
/// 状态在Update中执行的函数
/// </summary>
public abstract void OnUpdate();
/// <summary>
/// 状态在FixedUpdate中执行的函数
/// </summary>
public abstract void OnFixedUpdate();
/// <summary>
/// 状态退出时执行的函数
/// </summary>
public abstract void OnExit();
/// <summary>
/// 状态转换条件
/// </summary>
public abstract void QuitReason();
}
二、实现了状态基类后,就可以根据需求实现继承了状态基类的状态类了
这里以Idle状态举例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IdleState : StateBase
{
public override void OnEnter(Player player)
{
base.OnEnter(player);
}
public override void OnExit()
{
}
public override void OnFixedUpdate()
{
}
public override void OnUpdate()
{
}
public override void QuitReason()
{
}
}
三、载体类
这里以Player举例
1.首先我们声明一些需要使用到的状态,并在Awake中实例化它们。
2.之后我们在Updata和FixedUpdata中分别执行我们当前状态的状态执行函数OnUpdata和OnFixedUpdata。
3.我们还需要在Updata中每帧检测是否满足了切换到下一个状态的条件,如果满足了就应该立刻切换到下一个状态,所以我们需要在Updata中执行当前状态的QuiteReason函数
4.我们也可以写一个SwitchState函数来统一的管理我们的状态切换,在每次切换状态时,都需要执行一次当前状态的退出函数OnExit和下一个状态的进入函数OnEnter
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
private StateBase currentState;
private StateBase idleState;
private StateBase walkState;
private void Awake()
{
idleState = new IdleState();
walkState = new WalkState();
//初始化状态
SwitchState(stateEnums.IdleState);
}
void Start()
{
}
void Update()
{
currentState?.QuitReason();
currentState?.OnUpdate();
}
private void FixedUpdate()
{
currentState?.OnFixedUpdate();
}
void SwitchState(stateEnums state)
{
currentState?.OnExit();
currentState = state switch
{
stateEnums.IdleState => idleState,
stateEnums.WalkState => walkState,
_ => null,
};
currentState?.OnEnter(this);
}
}
public enum stateEnums
{
IdleState,WalkState
}
这样我们就实现了一个最基本的有限状态机。
关于优化的一些思考
- 我们目前对各种状态的实例化处理还只是直接在脚本中声明一个状态变量并实例化,但如果状态非常多时,一个一个的声明变量就会显得代码很冗长,这时我们可以在SwitchState中考虑结合反射,来通过反射直接实例化指定类名的状态类,并将所有声明的状态都统一存储在一个字典中,通过键名来一一对应。
- 我们可以考虑将状态机的切换操作,实例化操作等等只针对状态机本身的操作都剥离出来,放在一个单独的FSM静态类中,这样可以让程序进一步的解耦,并提高程序的通用性。
具体代码有空再更啦~