有限状态机[第2部分]
深入FSM
有限状态机是一种非常低级的顺序逻辑。它们可以用于简单的决策逻辑。让我们举一个FSM的人类示例:
- 输入:拨动开关
- 状态:灯泡处于“开”状态
- 输出:灯泡现在将为房间发光
输入是来自用户的任何形式的刺激,可以根据执行状态所需的条件触发状态更改。考虑打开灯:
如您所见,状态描述了FSM的当前状况。在用户再次输入之前,灯泡不会将其状态更改为“关”。
根据编程人员对FSM的编程方式,输出将链接到FSM的状态。
FSM实施
这是我在Unity中实现动态FSM的方式。这将要求我们对每个持有状态的对象都拥有一个主要的FSM类。持有动作的对象的FSM状态;以及执行状态输出的FSM动作。
现在我们已经向您介绍了FSM,现在让我们做一些脚本编写。让我们一个接一个地做。首先,让我们组织我们的统一项目。
由您决定将它们放置在何处。我总是将我的仅比赛资产与其他资产分开。我们将把这个FSM放在我们的公共文件夹中(我总是有“ Common”文件夹,因为我有我自己的库,我在每个游戏中都使用该库),因为这个FSM系统将是动态的,并且您将能够使用所有内容来每场比赛。
之所以拥有动作文件夹,是为了编译我们所做的所有动作,并为其他类型的游戏创建动作库。
现在创建主FSM类,并将其放在common下的FSM文件夹中,并将其命名为FSM.cs。
FSM.cs
using UnityEngine;
using System;
using System.Collections.Generic;
namespace Common.FSM
{
///<summary>
///This is the main engine of our FSM, without this, you won't be
///able to use FSM States and FSM Actions.
///</summary>
public class FSM
{
}
}
几件事,我是一个名称空间的狂热者,如果您不使用任何代码,则最好隔离它们。同样,我有我的通用库,这就是为什么我将其放在通用下。您可以根据需要创建自己的名称空间。
如您所见,我们没有继承MonoBehaviour类,因为我们将拥有一个更新函数,该函数将在AI的更新下被调用,以更新FSM。
现在让我们为FSM做一些变量和函数。
private readonly string name;
public string Name {
get {
return name;
}
}
///<summary>
/// This is the constructor that will initialize the FSM and give it
/// a unique name or id.
///</summary>
public FSM (string name) {
this.name = name;
}
///<summary>
/// This initializes the FSM. We can indicate the starting State of
/// the Object that has an FSM.
///</summary>
public void Start()
{
}
///<summary>
/// This changes the state of the Object. This also calls the exit
/// state before doing the next state.
///</summary>
public void ChangeToState()
{
}
///<summary>
/// This changes the state of the Object. It is not advisable to
/// call this to change state.
///</summary>
public void EnterState()
{
}
private void ExitState()
{
}
///<summary>
/// Call this under a MonoBehaviour's Update.
///</summary>
public void Update()
{
}
///<summary>
/// This handles the events that is bound to a state and changes
/// the state.
///</summary>
public void SendEvent()
{
}
只是一个提示,如果您由于所有摘要而认为代码开始变得肮脏,只需启用monodevelop的代码冷却即可。只需转到工具/选项/文本编辑器/常规,然后选中“启用代码折叠”
现在,我们可以在代码本身中折叠摘要。
在继续之前,我们先创建FSMState和FSMAction,因为我们需要在FSM中创建将返回它们的函数。让我们从FSMAction开始。
FSM行动实施
FSMAction将是我们操作的基类,因此是虚拟功能。如果您不熟悉虚函数,那么这里有一个谈论多态的Unity教程。
using UnityEngine;
using System.Collections;
namespace Common.FSM
{
public class FSMAction
{
private readonly FSMState owner;
public FSMAction (FSMState owner)
{
this.owner = owner;
}
public FSMState GetOwner ()
{
return owner;
}
///<summary>
/// Starts the action.
///</summary>
public virtual void OnEnter ()
{
}
///<summary>
/// Updates the action.
///</summary>
public virtual void OnUpdate ()
{
}
///<summary>
/// Finishes the action.
///</summary>
public virtual void OnExit ()
{
}
}
}
最初的FSM状态实施
现在启动FSMState并将其称为FSMState.cs:
using UnityEngine;
using System;
using System.Collections.Generic;
namespace Common.FSM
{
public class FSMState
{
private List<FSMAction> actions;
/// <summary>
/// Initializes a new instance of the <see cref="Common.FSM.FSMState"/> class.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="owner">Owner.</param>
public FSMState (string name, FSM owner)
{
}
/// <summary>
/// Adds the transition.
/// </summary>
public void AddTransition (string id, FSMState destinationState)
{
}
/// <summary>
/// Gets the transition.
/// </summary>
public FSMState GetTransition (string eventId)
{
}
/// <summary>
/// Adds the action.
/// </summary>
public void AddAction (FSMAction action)
{
}
/// <summary>
/// This gets the actions of this state
/// </summary>
/// <returns>The actions.</returns>
public IEnumerable<FSMAction> GetActions ()
{
return actions;
}
/// <summary>
/// Sends the event.
/// </summary>
public void SendEvent (string eventId)
{
}
}
}
现在我们已经开始了一切,让我们结束FSM类。
完成FSM实施
让我们在类中添加ff变量。
private readonly string name;
private FSMState currentState;
private readonly Dictionary<string, FSMState> stateMap;
当然,名称将是FSM的名称。stateMap将包含一个密钥,该密钥将是FSMState的事件ID,以及FSMState本身,以便我们可以将它们绑定在一起,并通过SendEvent()函数转换到另一个状态。
public string Name {
get {
return name;
}
}
当然是FSM的名称。
private delegate void StateActionProcessor (FSMAction action);
/// <summary>
/// This gets all the actions that is inside the state and loop them.
/// </summary>
/// <param name="state">State.</param>
/// <param name="actionProcessor">Action processor.</param>
private void ProcessStateAction (FSMState state, StateActionProcessor actionProcessor)
{
FSMState currentStateOnInvoke = this.currentState;
IEnumerable<FSMAction> actions = state.GetActions ();
foreach (FSMAction action in actions) {
if (this.currentState != currentStateOnInvoke) {
break;
}
actionProcessor (action);
}
}
我们将创建一个动作处理器。这将使我们能够动态调用状态内部的不同动作,并执行该动作。如果您不熟悉委托,请参考this。
现在让我们填写之前做过的构造函数,并为我们的FSM做一个初始化程序。
///<summary>
/// This is the constructor that will initialize the FSM and give it
/// a unique name or id.
///</summary>
public FSM (string name)
{
this.name = name;
this.currentState = null;
stateMap = new Dictionary<string, FSMState> ();
}
///<summary>
/// This initializes the FSM. We can indicate the starting State of
/// the Object that has an FSM.
///</summary>
public void Start (string stateName)
{
if (!stateMap.ContainsKey (stateName)) {
Debug.LogWarning ("The FSM doesn't contain: " + stateName);
return;
}
ChangeToState (stateMap [stateName]);
}
如您所知,构造函数使类的实例创建更容易,更简洁,并且耦合更少,因为此类的变量设置为私有。使其他程序员看不到不必要的变量。
现在,我们将FSMState参数添加到ChangeToState(),EnterState()和ExitState()
///<summary>
/// This changes the state of the Object. This also calls the exit
/// state before doing the next state.
///</summary>
public void ChangeToState (FSMState state)
{
if (this.currentState != null) {
ExitState (this.currentState);
}
this.currentState = state;
EnterState (this.currentState);
}
现在,重要的是要检查状态,以免使状态混乱并避免错误。
Enter,Exit和Update将相同,并将由我们的动作处理器处理。
///<summary>
/// This changes the state of the Object. It is not advisable to
/// call this to change state.
///</summary>
public void EnterState (FSMState state)
{
ProcessStateAction (state, delegate(FSMAction action) {
action.OnEnter ();
});
}
private void ExitState (FSMState state)
{
FSMState currentStateOnInvoke = this.currentState;
ProcessStateAction (state, delegate(FSMAction action) {
if (this.currentState != currentStateOnInvoke)
Debug.LogError ("State cannont be changed on exit of the specified state");
action.OnExit ();
});
}
///<summary>
/// Call this under a MonoBehaviour's Update.
///</summary>
public void Update ()
{
if (this.currentState == null)
return;
ProcessStateAction (this.currentState, delegate(FSMAction action) {
action.OnUpdate ();
});
}
完成FSM状态执行
现在,我们已经创建了循环。让我们开始编写我们的FSMState。首先是它的构造函数。
public FSMState (string name, FSM owner)
{
this.name = name;
this.owner = owner;
this.transitionMap = new Dictionary<string, FSMState> ();
this.actions = new List<FSMAction> ();
}
在继续之前,让我们谈谈过渡。转换的名称,是两种状态之间的转换。我们将使用字符串作为键进行更改之间的状态。所有这些都包含在词典中。
/// <summary>
/// Adds the transition.
/// </summary>
public void AddTransition (string id, FSMState destinationState)
{
if (transitionMap.ContainsKey (id)) {
Debug.LogError (string.Format ("state {0} already contains transition for {1}", this.name, id));
return;
}
transitionMap [id] = destinationState;
}
/// <summary>
/// Gets the transition.
/// </summary>
public FSMState GetTransition (string eventId)
{
if (transitionMap.ContainsKey (eventId)) {
return transitionMap [eventId];
}
return null;
}
现在,让我们创建一个将动作添加到状态的函数。
/// <summary>
/// Adds the action.
/// </summary>
public void AddAction (FSMAction action)
{
if (actions.Contains (action)) {
Debug.LogWarning ("This state already contains " + action);
return;
}
if (action.GetOwner () != this) {
Debug.LogWarning ("This state doesn't own " + action);
}
actions.Add (action);
}
现在,我们为FSM和FSMState添加事件处理程序。让我们首先回到FSM.cs并将新函数添加到该类。
///<summary>
/// This handles the events that is bound to a state and changes
/// the state.
///</summary>
public void SendEvent (string eventId)
{
FSMState transitonState = ResolveTransition (eventId);
if (transitonState == null)
Debug.LogWarning ("The current state has no transition for event " + eventId);
else
ChangeToState (transitonState);
}
/// <summary>
/// This gets the next state from the current state.
/// </summary>
/// <returns>The transition.</returns>
/// <param name="eventId">Event identifier.</param>
private FSMState ResolveTransition (string eventId)
{
FSMState transitionState = this.currentState.GetTransition (eventId);
if (transitionState == null)
return null;
else
return transitionState;
}
让我们通过添加事件处理的实现来最终确定FSMState。
public void SendEvent (string eventId)
{
this.owner.SendEvent (eventId);
}
最后,让我们创建一个函数,该函数将在FSM.cs下自动为我们创建状态。
public FSMState AddState (string name)
{
if (stateMap.ContainsKey (name)) {
Debug.LogWarning ("The FSM already contains: " + name);
return null;
}
FSMState newState = new FSMState (name, this);
stateMap [name] = newState;
return newState;
}
通过调用此函数,这将使我们能够创建状态而无需在AI类本身上进行创建。
所以你有它。我们的FSM系统现已完成。查找有关实现的下一个Blog教程。
在本教程的第二部分和最后一部分中,我们将为我们制作的FSM引擎进行两种实现。我们将创建一个AI类,该类将无限期地写入控制台,另一个AI类将无限期地写入并写入控制台。