有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。任何一个FSM都可以用状态转换图来描述,图中的节点表示FSM中的一个状态,有向加权边表示输入字符时状态的变化。如果图中不存在与当前状态与输入字符对应的有向边,则FSM将进入“消亡状态(Doom State)”,此后FSM将一直保持“消亡状态”。状态转换图中还有两个特殊状态:状态1称为“起始状态”,表示FSM的初始状态。状态6称为“结束状态”,表示成功识别了所输入的字符序列。
在启动一个FSM时,首先必须将FSM置于“起始状态”,然后输入一系列字符,最终,FSM会到达“结束状态”或者“消亡状态”。
说明:
在通常的FSM模型中,一般还存在一个“接受状态”,并且FSM可以从“接受状态”转换到另一个状态,只有在识别最后一个字符后,才会根据最终状态来决定是否接受所输入的字符串。此外,也可以将“其实状态”也作为接受状态,因此空的输入序列也是可以接受的。
FSM的实现
程序设计思路大致如下:
方式一:枚举
enum State
{
STATE_STANDING,
STATE_JUMPING,
STATE_DUCKING,
STATE_DIVING
}
switch (state_)
{
case STATE_STANDING:
if (input == PRESS_B)
{
state_ = STATE_JUMPING;
yVelocity_ = JUMP_VELOCITY;
setGraphics(IMAGE_JUMP);
}
else if (input == PRESS_DOWN)
{
state_ = STATE_DUCKING;
setGraphics(IMAGE_DUCK);
}
break;
case STATE_JUMPING:
if (input == PRESS_DOWN)
{
state_ = STATE_DIVING;
setGraphics(IMAGE_DIVE);
}
break;
case STATE_DUCKING:
if (input == RELEASE_DOWN)
{
state_ = STATE_STANDING;
setGraphics(IMAGE_STAND);
}
break;
}
方式二:虚函数
在场景中添加两个游戏物体,一个为玩家并修改其Tag为Player,另一个为NPC为其添加NPCControl脚本,并为其将玩家角色和路径添加上去。(该案例利用状态机简单的实现了一个NPC的简单AI—巡逻—看到玩家----追逐玩家----丢失玩家----巡逻)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// Place the labels for the Transitions in this enum.
/// Don't change the first label, NullTransition as FSMSystem class uses it.
/// 为过渡加入枚举标签
/// 不要修改第一个标签,NullTransition会在FSMSytem类中使用
///
public enum Transition
{
NullTransition = 0, // Use this transition to represent a non-existing transition in your system
//用这个过度来代表你的系统中不存在的状态
SawPlayer,//这里配合NPCControl添加两个NPC的过渡
LostPlayer,
}
///
/// Place the labels for the States in this enum.
/// Don't change the first label, NullStateID as FSMSystem class uses it.
/// 为状态加入枚举标签
/// 不要修改第一个标签,NullStateID会在FSMSytem中使用
///
public enum StateID
{
NullStateID = 0, // Use this ID to represent a non-existing State in your syste
//使用这个ID来代表你系统中不存在的状态ID
ChasingPlayer,//这里配合NPCControl添加两个状态
FollowingPath,
}
///
/// This class represents the States in the Finite State System.
/// Each state has a Dictionary with pairs (transition-state) showing
/// which state the FSM should be if a transition is fired while this state
/// is the current state.
/// Method Reason is used to determine which transition should be fired .
/// Method Act has the code to perform the actions the NPC is supposed do if it's on this state.
/// 这个类代表状态在有限状态机系统中
/// 每个状态都有一个由一对搭档(过渡-状态)组成的字典来表示当前状态下如果一个过渡被触发状态机会进入那个状态
/// Reason方法被用来决定那个过渡会被触发
/// Act方法来表现NPC出在当前状态的行为
///
public abstract class FSMState
{
protected Dictionary map = new Dictionary();
protected StateID stateID;
public StateID ID { get { return stateID; } }
public void AddTransition(Transition trans, StateID id)
{
// Check if anyone of the args is invalid
//验证每个参数是否合法
if (trans == Transition.NullTransition)
{
Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
return;
}
if (id == StateID.NullStateID)
{
Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
return;
}
// Since this is a Deterministic FSM,
// check if the current transition was already inside the map
//要知道这是一个确定的有限状态机(每个状态后金对应一种状态,而不能产生分支)
//检查当前的过渡是否已经在地图字典中了
if (map.ContainsKey(trans))
{
Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
"Impossible to assign to another state");
return;
}
map.Add(trans, id);
}
///
/// This method deletes a pair transition-state from this state's map.
/// If the transition was not inside the state's map, an ERROR message is printed.
/// 这个方法用来在状态地图中删除transition-state对儿
/// 如果过渡并不存在于状态地图中,那么将会打印出一个错误
///
public void DeleteTransition(Transition trans)
{
// Check for NullTransition
if (trans == Transition.NullTransition)
{
Debug.LogError("FSMState ERROR: NullTransition is not allowed");
return;
}
// Check if the pair is inside the map before deleting
//再删除之前确认该键值对是否存在于状态地图中(键值对集合)
if (map.ContainsKey(trans))
{
map.Remove(trans);
return;
}
Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
" was not on the state's transition list");
}
///
/// This method returns the new state the FSM should be if
/// this state receives a transition and
/// 该方法在该状态接收到一个过渡时返回状态机需要成为的新状态
///
public StateID GetOutputState(Transition trans)
{
// Check if the map has this transition
if (map.ContainsKey(trans))
{
return map[trans];
}
return StateID.NullStateID;
}
///
/// This method is used to set up the State condition before entering it.
/// It is called automatically by the FSMSystem class before assigning it
/// to the current state.
/// 这个方法用来设立进入状态前的条件
/// 在状态机分配它到当前状态之前他会被自动调用
///
public virtual void DoBeforeEntering() { }
///
/// This method is used to make anything necessary, as reseting variables
/// before the FSMSystem changes to another one. It is called automatically
/// by the FSMSystem before changing to a new state.
/// 这个方法用来让一切都是必要的,例如在有限状态机变化的另一个时重置变量。
/// 在状态机切换到新的状态之前它会被自动调用。
///
public virtual void DoBeforeLeaving() { }
///
/// This method decides if the state should transition to another on its list
/// 动机-->这个方法用来决定当前状态是否需要过渡到列表中的其他状态
/// NPC is a reference to the object that is controlled by this class
/// NPC是被该类约束下对象的一个引用
///
public abstract void Reason(GameObject player, GameObject npc);
///
/// This method controls the behavior of the NPC in the game World.
/// 表现-->该方法用来控制NPC在游戏世界中的行为
/// Every action, movement or communication the NPC does should be placed here
/// NPC的任何动作,移动或者交流都需要防止在这儿
/// NPC is a reference to the object that is controlled by this class
/// NPC是被该类约束下对象的一个引用
///
public abstract void Act(GameObject player, GameObject npc);
} // class FSMState
///
/// FSMSystem class represents the Finite State Machine class.
/// It has a List with the States the NPC has and methods to add,
/// delete a state, and to change the current state the Machine is on.
/// 该类便是有限状态机类
/// 它持有者NPC的状态集合并且有添加,删除状态的方法,以及改变当前正在执行的状态
///
public class FSMSystem
{
private List states;
// The only way one can change the state of the FSM is by performing a transition
// Don't change the CurrentState directly
//通过预装一个过渡的唯一方式来盖面状态机的状态
//不要直接改变当前的状态
private StateID currentStateID;
public StateID CurrentStateID { get { return currentStateID; } }
private FSMState currentState;
public FSMState CurrentState { get { return currentState; } }
public FSMSystem()
{
states = new List();
}
///
/// This method places new states inside the FSM,
/// or prints an ERROR message if the state was already inside the List.
/// First state added is also the initial state.
/// 这个方法为有限状态机置入新的状态
/// 或者在该状态已经存在于列表中时打印错误信息
/// 第一个添加的状态也是最初的状态!
///
public void AddState(FSMState s)
{
// Check for Null reference before deleting
//在添加前检测空引用
if (s == null)
{
Debug.LogError("FSM ERROR: Null reference is not allowed");
}
// First State inserted is also the Initial state,
// the state the machine is in when the begins
//被装在的第一个状态也是初始状态
//这个状态便是状态机开始时的状态
if (states.Count == 0)
{
states.Add(s);
currentState = s;
currentStateID = s.ID;
return;
}
// Add the state to the List if it's not inside it
//如果该状态未被添加过,则加入集合
foreach (FSMState state in states)
{
if (state.ID == s.ID)
{
Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() +
" because state has already been added");
return;
}
}
states.Add(s);
}
///
/// This method delete a state from the FSM List if it exists,
/// or prints an ERROR message if the state was not on the List.
/// 该方法删除一个已存在以状态几个中的状态
/// 在它不存在时打印错误信息
///
public void DeleteState(StateID id)
{
// Check for NullState before deleting
//在删除前检查其是否为空状态
if (id == StateID.NullStateID)
{
Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
return;
}
// Search the List and delete the state if it's inside it
//遍历集合如果存在该状态则删除它
foreach (FSMState state in states)
{
if (state.ID == id)
{
states.Remove(state);
return;
}
}
Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
". It was not on the list of states");
}
///
/// This method tries to change the state the FSM is in based on
/// the current state and the transition passed. If current state
/// doesn't have a target state for the transition passed,
/// an ERROR message is printed.
/// 该方法基于当前状态和过渡是否通过来尝试改变状态机的状态,当当前的状态没有目标状态用来过渡(叫通道应该更合适吧)时通过时则打印错误消息
///
public void PerformTransition(Transition trans)
{
// Check for NullTransition before changing the current state
//在改变当前状态前检测NullTransition
if (trans == Transition.NullTransition)
{
Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
return;
}
// Check if the currentState has the transition passed as argument
//在改变当前状态前检测当前状态是否可作为过渡的参数
StateID id = currentState.GetOutputState(trans);
if (id == StateID.NullStateID)
{
Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " +
" for transition " + trans.ToString());
return;
}
// Update the currentStateID and currentState
//更新当前的状态个和状态编号
currentStateID = id;
foreach (FSMState state in states)
{
if (state.ID == currentStateID)
{
// Do the post processing of the state before setting the new one
//在状态变为新状态前执行后处理
currentState.DoBeforeLeaving();
currentState = state;
// Reset the state to its desired condition before it can reason or act
//在状态可以使用Reason(动机)或者Act(行为)之前为它的的决定条件重置它自己
currentState.DoBeforeEntering();
break;
}
}
} // PerformTransition()
} //class FSMSystem
NPCControl:
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class NPCControl : MonoBehaviour
{
public GameObject player;
public Transform[] path;
private FSMSystem fsm;
public void SetTransition(Transition t)
{
//该方法用来改变有限状态机的状体,有限状态机基于当前的状态和通过的过渡状态。
//如果当前的状态没有用来通过的过度状态,则会抛出错误
fsm.PerformTransition(t);
}
public void Start()
{
MakeFSM();
}
public void FixedUpdate()
{
fsm.CurrentState.Reason(player, gameObject);
fsm.CurrentState.Act(player, gameObject);
}
//NPC有两个状态分别是在路径中巡逻和追逐玩家
//如果他在第一个状态并且SawPlayer 过度状态被出发了,它就转变到ChasePlayer状态
//如果他在ChasePlayer状态并且LostPlayer状态被触发了,它就转变到FollowPath状态
private void MakeFSM()//建造状态机
{
FollowPathState follow = new FollowPathState(path);
follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);
ChasePlayerState chase = new ChasePlayerState();
chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);
fsm = new FSMSystem();
fsm.AddState(follow);//添加状态到状态机,第一个添加的状态将作为初始状态
fsm.AddState(chase);
}
}
public class FollowPathState : FSMState
{
private int currentWayPoint;
private Transform[] waypoints;
//构造函数装填自己
public FollowPathState(Transform[] wp)
{
waypoints = wp;
currentWayPoint = 0;
stateID = StateID.FollowingPath;//别忘设置自己的StateID
}
public override void DoBeforeEntering()
{
Debug.Log("FollowingPath BeforeEntering--------");
}
public override void DoBeforeLeaving()
{
Debug.Log("FollowingPath BeforeLeaving---------");
}
//重写动机方法
public override void Reason(GameObject player, GameObject npc)
{
// If the Player passes less than 15 meters away in front of the NPC
RaycastHit hit;
if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15F))
{
if (hit.transform.gameObject.tag == "Player")
npc.GetComponent().SetTransition(Transition.SawPlayer);
}
}
//重写表现方法
public override void Act(GameObject player, GameObject npc)
{
// Follow the path of waypoints
// Find the direction of the current way point
Vector3 vel = npc.GetComponent().velocity;
Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;
if (moveDir.magnitude < 1)
{
currentWayPoint++;
if (currentWayPoint >= waypoints.Length)
{
currentWayPoint = 0;
}
}
else
{
vel = moveDir.normalized * 10;
// Rotate towards the waypoint
npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
Quaternion.LookRotation(moveDir),
5 * Time.deltaTime);
npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);
}
// Apply the Velocity
npc.GetComponent().velocity = vel;
}
} // FollowPathState
public class ChasePlayerState : FSMState
{
//构造函数装填自己
public ChasePlayerState()
{
stateID = StateID.ChasingPlayer;
}
public override void DoBeforeEntering()
{
Debug.Log("ChasingPlayer BeforeEntering--------");
}
public override void DoBeforeLeaving()
{
Debug.Log("ChasingPlayer BeforeLeaving---------");
}
public override void Reason(GameObject player, GameObject npc)
{
// If the player has gone 30 meters away from the NPC, fire LostPlayer transition
if (Vector3.Distance(npc.transform.position, player.transform.position) >= 3)
npc.GetComponent().SetTransition(Transition.LostPlayer);
}
public override void Act(GameObject player, GameObject npc)
{
// Follow the path of waypoints
// Find the direction of the player
Vector3 vel = npc.GetComponent().velocity;
Vector3 moveDir = player.transform.position - npc.transform.position;
// Rotate towards the waypoint
npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
Quaternion.LookRotation(moveDir),
5 * Time.deltaTime);
npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);
vel = moveDir.normalized * 10;
// Apply the new Velocity
npc.GetComponent().velocity = vel;
}
} // ChasePlayerState
引用:https://www.cnblogs.com/Firepad-magic/p/6185201.html
https://www.cnblogs.com/benxintuzi/p/4931258.html
https://blog.csdn.net/poem_qianmo/article/details/52824776