在设计游戏AI时,我们需要找到一个简单,可扩展的编辑逻辑的方案,从而加速游戏开发的迭代速度。
案例:一个怪物在指定路径巡逻,当玩家怪物与玩家距离小于等于5米时会追逐玩家,当大于5米时会继续巡逻。这里就分为了巡逻、追逐两种状态。
FSM状态机主要使用多态来实现扩展。
首先状态切换一般分为三种进入状态触发、保持状态时触发,离开状态触发,进而我们创建基类:FSMState
public abstract class FSMState
{
protected int stateID;
protected FSMSystem fsm;
public int StateID
{
get { return this.stateID; }
}
public FSMState(int id, FSMSystem fsm)
{
this.stateID = id;
this.fsm = fsm;
}
//进入状态触发
public virtual void OnEnter(params object[] args) { }
//保持状态触发 每帧执行
public virtual void OnStay(params object[] args) { }
//离开状态触发
public virtual void OnExit(params object[] args) { }
}
定义一个状态机,主要用于存储所属对象所有状态映射、切换状态方法等:FSMSystem
public class FSMSystem
{
private Dictionary<int, FSMState> states = new Dictionary<int, FSMState>();
private FSMState curFSMState;
// 添加状态时,默认第一添加的时默认状态
public void AddState(FSMState fsmState)
{
if (fsmState == null)
{
Debug.LogError("FSMSystem AddState is not null");
return;
}
if (this.curFSMState == null)
{
this.curFSMState = fsmState;
this.curFSMState.OnEnter();
}
if (this.states.ContainsKey(fsmState.StateID))
{
Debug.LogError("FSMSystem map is contains stateId:" + fsmState.StateID.ToString());
}
else
{
this.states.Add(fsmState.StateID, fsmState);
}
}
public void DeleteState(int id)
{
if (!this.states.ContainsKey(id))
{
Debug.LogError("无法删除不存在的状态:" + id.ToString());
return;
}
this.states.Remove(id);
}
// 切换状态
public void TranslateState(int id)
{
if (this.curFSMState == null)
{
Debug.LogError("curFSMState is null");
return;
}
if (!this.states.ContainsKey(id))
{
Debug.LogError("states is not contains id " + id.ToString());
return;
}
FSMState state = this.states[id];
this.curFSMState.OnExit();
this.curFSMState = state;
this.curFSMState.OnEnter();
}
//状态保持
public void Update(GameObject npc)
{
if (this.curFSMState != null)
{
this.curFSMState.OnStay(npc);
}
}
}
巡逻状态脚本:PatrolState
// 巡逻状态
public class PatrolState : FSMState
{
Transform[] paths; // 巡逻路径
private int index = 0;
private Transform target;
public PatrolState(int stateID, FSMSystem fsm, Transform target) : base(stateID, fsm)
{
this.paths = GameObject.Find("Path").GetComponentsInChildren<Transform>();
this.target = target;
}
public override void OnStay(params object[] args)
{
if (args.Length == 0)
{
Debug.LogError("PatrolState args length is zero");
return;
}
var npc = args[0] as GameObject;
npc.transform.LookAt(paths[this.index].position);
npc.transform.Translate(Vector3.forward * Time.deltaTime * 3);
if (Vector3.Distance(npc.transform.position, this.paths[this.index].position) < 1)
{
this.index++;
this.index %= this.paths.Length;
}
if (Vector3.Distance(npc.transform.position, this.target.position) <= 5)
{
//切换为追逐状态
this.fsm.TranslateState((int)NPCState.Chase);
}
}
public override void OnEnter(params object[] args)
{
Debug.LogError("进入巡逻状态");
}
public override void OnExit(params object[] args)
{
Debug.LogError("退出巡逻状态");
}
}
怪物追逐状态脚本:ChaseState
//当怪物距离玩家小于5米的时候开始追逐,大于5米继续巡逻
public class ChaseState : FSMState
{
private Transform target; // 追逐目标对象
public ChaseState(int stateID, FSMSystem fsm, Transform target) : base(stateID, fsm)
{
this.target = target;
}
public override void OnEnter(params object[] args)
{
Debug.LogError("进入追逐状态");
}
public override void OnStay(params object[] args)
{
if (args.Length == 0)
{
Debug.LogError("ChaseState args length is zero");
return;
}
var npc = args[0] as GameObject;
if (Vector3.Distance(npc.transform.position, this.target.position) > 5)
{
//切换巡逻状态
this.fsm.TranslateState((int)NPCState.Patrol);
}
npc.transform.LookAt(this.target);
npc.transform.Translate(Vector3.forward * Time.deltaTime * 3, Space.Self);
}
public override void OnExit(params object[] args)
{
Debug.LogError("离开追逐状态");
}
}
创建怪物脚本,用来实例化 FSM状态机,然后将怪物所有状态添加到状态机中:Enemy
public enum NPCState
{
Idle,
Patrol, //巡逻状态
Chase, // 追逐
}
public class Enemy : MonoBehaviour
{
public Transform target;
private FSMSystem fsm;
void Start()
{
this.fsm = new FSMSystem();
FSMState patrolState = new PatrolState((int)NPCState.Patrol, fsm, target); // 巡逻状态
FSMState chaseState = new ChaseState((int)NPCState.Chase, fsm, target); // 追逐状态
this.fsm.AddState(patrolState);
this.fsm.AddState(chaseState);
}
void Update()
{
this.fsm.Update(this.gameObject);
}
}
如果需要添加新的状态只需要继承 FSMState写逻辑就可以了,包括FSMState类的输入函数都可根据需求进行增删,通过多态的特性使状态机扩展更方便。
FSM状态机优点:
1.解决过于麻烦的状态转换(人物动画过多)。
2.使的各个状态之间相互独立,互补影响。
3.避免状态与状态之间的耦合度。
4.规则简单,可读性强。
缺点:
1.状态一多,各状态联系就会变复杂。
2.代码量大。
随着开发进行,AI更智能,FSM已经不在适用,就有了行为树的抽象来解决这个问题。