简介
本文实现有限状态机的一个基本框架,基于此框架实现一个物体巡逻以及追赶功能的演示
基本含义:有限状态机,在不同的阶段下会有不同运行状态的系统,这些状态是有限的、不重叠的。在某一时刻一定处于所有状态中的一种,可以受到影响,并可以做出反应,或者转移至其它状态。
在一个状态机中,主要具有
- 状态
- 条件
- 转移
- 行为
- 事件
为何要使用状态机?实际上类似于设计模式里的状态模式,对于状态模式
摘取《大话设计模式》里的一些描述——将与特定状态相关的行为局部化,并将不同状态的行为分割开来。消除庞大的套件分支语句,把各种状态转移逻辑分布到State子类之间,来减少互相间的依赖。当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式
简单的状态机实现
如果在应用过程中,不需要复杂的状态机实现,只需要按条件切换功能的话
下方实现也可算作一个非常简单的状态机实现
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum State1
{
Idle,
Walk,
Attack,
}
public class Simple_FSM : MonoBehaviour
{
private State1 state = State1.Idle;
void Update()
{
switch (state)
{
case State1.Idle:
ProcessStateIdle();
break;
case State1.Walk:
ProcessStateWalk();
break;
case State1.Attack:
ProcessStateAttack();
break;
default:
break;
}
}
private void ProcessStateIdle() { }
private void ProcessStateAttack() { }
private void ProcessStateWalk() { }
}
高级状态机实现
父状态类:
每个状态中维护一个 状态-条件 列表。记录切换条件和状态的对应关系
拥有添加条件,删除条件,得到状态的方法
Entering, Leaving, Act, Reason 为具体行为,在子类中编写
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 状态
/// </summary>
public enum StateID
{
NullState,
Patrol,
Chase
}
/// <summary>
/// 条件
/// </summary>
public enum Transition
{
NullTransition,
FindTarget,
LostTarget
}
public abstract class FSMState
{
protected StateID stateID;
public StateID ID => stateID;
protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
protected FSMSystem fsm;
public FSMState(FSMSystem fsm)
{
this.fsm = fsm;
}
/// <summary>
/// 添加条件-状态
/// </summary>
/// <param name="t"></param>
/// <param name="s"></param>
public void AddTransition(Transition t, StateID s)
{
if (t == Transition.NullTransition)
{
Debug.LogError("Transition is null");
return;
}
if (s == StateID.NullState)
{
Debug.LogError("StateID is null");
return;
}
if (map.ContainsKey(t))
{
Debug.LogError(t + " already exists in the map");
return;
}
map.Add(t, s);
}
/// <summary>
/// 删除条件
/// </summary>
/// <param name="t"></param>
public void RemoveTransition(Transition t)
{
if (t == Transition.NullTransition)
{
Debug.LogError("Transition is null");
return;
}
if (map.ContainsKey(t) == false)
{
Debug.LogError(t + " is not found in map");
return;
}
map.Remove(t);
}
/// <summary>
/// 得到某条件对应状态
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public StateID GetState(Transition t)
{
if (map.ContainsKey(t))
{
return map[t];
}
return StateID.NullState;
}
/// <summary>
/// 进入状态
/// </summary>
public virtual void Entering() { }
/// <summary>
/// 离开状态
/// </summary>
public virtual void Leaving() { }
/// <summary>
/// 执行状态
/// </summary>
public abstract void Act(GameObject npc);
/// <summary>
/// 判断转换条件
/// </summary>
public abstract void Reason(GameObject npc);
}
状态管理类
preformTransition用来进行状态转移
UpDate里不断执行 当前状态的Act行为,以及 Reason行为判断转移事件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.PlayerLoop;
public class FSMSystem
{
protected FSMState currentState;
protected StateID currentID;
private Dictionary<StateID, FSMState> states = new Dictionary<StateID, FSMState>();
public void Update(GameObject npc)
{
currentState.Act(npc);
currentState.Reason(npc);
}
public void AddState(FSMState s)
{
if (s is null)
{
Debug.LogError("s is null");
return;
}
if (currentState is null)
{
currentState = s;
currentID = s.ID;
}
if (s.ID == StateID.NullState)
{
Debug.LogError("ID is NullState");
return;
}
states.Add(s.ID, s);
}
public void RemoveState(FSMState s)
{
if (s is null)
{
Debug.LogError("s is null");
return;
}
StateID stateID = s.ID;
if(stateID == StateID.NullState)
{
Debug.LogError("ID is NullState");
return;
}
if(!states.ContainsKey(stateID))
{
Debug.LogError("ID is not found in states");
return;
}
states.Remove(stateID);
}
public void preformTransition(Transition t)
{
if(t is Transition.NullTransition)
{
Debug.LogError("t is NullTransition");
return;
}
StateID stateID = currentState.GetState(t);
if (stateID == StateID.NullState)
{
Debug.LogError("stateID is NullState");
return;
}
if (!states.ContainsKey(stateID))
{
Debug.LogError("stateID is not found in states");
return;
}
FSMState s = states[stateID];
//离开当前状态
currentState.Leaving();
currentState = s;
currentID = stateID;
//进入新状态
currentState.Entering();
}
}
使用示例实现
创建新的状态——巡逻态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatrolState : FSMState
{
private List<Transform> path = new List<Transform>();
private int index = 0;
private Transform player;
public PatrolState(FSMSystem fsm) : base(fsm)
{
stateID = StateID.Patrol;
Transform path = GameObject.Find("Path").transform;
Transform[] children = path.GetComponentsInChildren<Transform>();
foreach (Transform item in children)
{
if (item != path)
{
this.path.Add(item);
}
}
player = GameObject.Find("Player").transform;
}
public override void Act(GameObject npc)
{
npc.transform.LookAt(path[index].position);
npc.transform.Translate(npc.transform.forward * 3.0f * Time.deltaTime, Space.World);
if (Vector3.Distance(npc.transform.position, path[index].position) < 1.0f)
{
++index;
index %= path.Count;
}
}
public override void Reason(GameObject npc)
{
float tmp = Vector3.Angle(npc.transform.forward, player.position - npc.transform.position);
if (tmp <= 60 && Vector3.Distance(npc.transform.position, player.position) < 3.0f)
{
fsm.preformTransition(Transition.FindTarget);
}
}
}
创建追逐态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChaseState : FSMState
{
private Transform player;
public ChaseState(FSMSystem fsm) : base(fsm)
{
stateID = StateID.Chase;
player = GameObject.Find("Player").transform;
}
public override void Act(GameObject npc)
{
npc.transform.LookAt(player);
npc.transform.Translate(npc.transform.forward * 3.5f * Time.deltaTime, Space.World);
}
public override void Reason(GameObject npc)
{
if (Vector3.Distance(npc.transform.position, player.transform.position) > 8.0f)
{
fsm.preformTransition(Transition.LostTarget);
}
}
}
创建一个敌人脚本,用来与状态建立联系
InitFSM中需要做:
- 新建一个状态机系统对象
- 新建一个状态类
- 将该状态类下所有 切换条件 与 对应切换状态 绑定
- 重复 2 - 3 步骤,直到所需状态类创建完毕
- 将新建的状态全部添加进状态机系统里
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class Enemy : MonoBehaviour
{
private FSMSystem fsm;
// Start is called before the first frame update
void Start()
{
InitFSM();
}
// Update is called once per frame
void Update()
{
fsm.Update(gameObject);
}
void InitFSM()
{
fsm = new FSMSystem();
FSMState patrolState = new PatrolState(fsm);
patrolState.AddTransition(Transition.FindTarget, StateID.Chase);
FSMState chaseState = new ChaseState(fsm);
chaseState.AddTransition(Transition.LostTarget, StateID.Patrol);
fsm.AddState(patrolState);
fsm.AddState(chaseState);
}
}
在Unity中新建一个空对象Path,添加几个路径结点,新建一个Player对象 ,新建Enemy对象
在Enemy对象身上挂载Enemy脚本
效果演示
本文仅为学习内容总结,按需查看