FSM(Finite State Machines):有限状态机[第2部分]

有限状态机[第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类将无限期地写入并写入控制台。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值