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

有限状态机[第3部分]

这是我们针对FSM的最终博客教程。我们将回顾我们在第一部分中讨论的内容,并实现我们之前所做的FSM系统。

只是回顾一下前面的部分:

在这里插入图片描述

这将是我们FSM的循环。首先,我们初始化FSM,创建状态,创建动作并将它们全部映射在一起。映射它们之后,我们现在将启动FSM并指示AI将开始的状态。现在,AI将更改为特定状态,FSM将初始化动作,对其进行更新,直到动作完成,并发送一个指示动作完成的事件。最后,FSM将返回并更改状态。

现在是实现它们的时候了。

文字动作

让我们创建一个TextAction,以便我们第一手看到FSM系统。

在这里插入图片描述

首先,导入Common.FSM,以便可以使用我们的FSM系统,并让此类继承FSMAction类。

using UnityEngine;
using System.Collections;
using Common.FSM;

public class TextAction : FSMAction
{
	
}

现在让我们为该特定操作编写变量和构造函数。

private string textToShow;
private float duration;
private float cachedDuration;
private string finishEvent;

public TextAction (FSMState owner) : base (owner)
{
}

public void Init (string textToShow, float duration, string finishEvent)
{
	this.textToShow = textToShow;
	this.duration = duration;
	this.cachedDuration = duration;
	this.finishEvent = finishEvent;
}

textToShow是更新时将在控制台中打印出的字符串。Duration是action的持续时间,而finishEvent是过渡到另一个状态的调用。

让我们重写FSMAction的虚函数,以便FSM可以调用它们。

public override void OnEnter ()
{
	if (duration <= 0) {
		Finish ();
		return;
	}
}

public override void OnUpdate ()
{
	duration -= Time.deltaTime;
	
	if (duration <= 0) {
		Finish ();
		return;
	}
	
	Debug.Log (textToShow);
}

public override void OnExit ()
{

}

public void Finish ()
{
	if (!string.IsNullOrEmpty (finishEvent)) {
		GetOwner ().SendEvent (finishEvent);
	}
	duration = cachedDuration;
}

OnEnter将处理我们在开始操作时想要做的所有事情。就像MonoBehaviour中的Start()一样,我们将在此阶段初始化动作。FSM的动作处理器将调用Update,FSM的Update函数将调用该更新,而AI的类将调用该更新。当然,在退出时,将是完成动作后将要执行的操作。就个人而言,我宁愿有一个完成功能来发送事件。

人工智能实施

现在我们已经采取了行动,让我们创建一个人工智能。

在这里插入图片描述

让我们创建一个包含AI脚本的空白。

在这里插入图片描述
创建一个空文件后,让我们创建一个脚本,该脚本将实现我们的FSM系统以及我们执行的操作。

让我们为AITest创建两个FSMStates和两个FSMAction。不要忘记导入Common.FSM,否则我们将无法使用我们制造的FSM系统。

using UnityEngine;
using System.Collections;
using Common.FSM;

public class AITest : MonoBehaviour
{
	private FSM fsm;
	private FSMState PatrolState;
	private FSMState IdleState;
	private TextAction PatrolAction;
	private TextAction IdleAction;
}

同样,fsm将成为我们状态机的引擎,我们将拥有两种状态,即PatrolState和IdleState。命名方式由您决定。最后,我们将有两个文本操作,每个状态一个,分别是PatrolAction和IdleAction。

现在,让我们在Start()函数中创建FSM,状态和操作。

private void Start ()
{
	fsm = new FSM ("AITest FSM");
	IdleState = fsm.AddState ("IdleState");
	PatrolState = fsm.AddState ("PatrolState");
	PatrolAction = new TextAction (PatrolState);
	IdleAction = new TextAction (IdleState);
}

首先,我们将初始化FSM,然后添加新状态。请记住,FSM.AddState()返回FSMStates,这样我们就不必声明新的FSMState并将其添加到FSM中。

现在我们有了状态和动作,现在让我们映射所有内容,并为每个转换添加事件ID。

//This adds the actions to the state and add state to it's transition map
PatrolState.AddAction (PatrolAction);
IdleState.AddAction (IdleAction);

PatrolState.AddTransition ("ToIdle", IdleState);
IdleState.AddTransition ("ToPatrol", PatrolState);

当事件被发送到状态时,我们将PatrolState映射到IdleState,反之亦然。现在让我们初始化动作。

//This initializes the actions
PatrolAction.Init ("AI on patrol", 3.0f, "ToIdle");
IdleAction.Init ("AI on Idle", 2.0f, "ToPatrol");

第一个属性是字符串,将是操作的输出,第二个属性将确定操作的持续时间,最后一个属性是操作完成后发出的事件。

请记住,这里的所有内容都在启动功能内。现在让我们通过调用FSM.Start()和FSM.Update()来使FSM工作。

//Starts the FSM
fsm.Start ("IdleState");
private void Update ()
{
	fsm.Update ();
}

同样,在Start()下进行所有初始化之后,将调用fsm.Start()。现在,将AITest附加到我们之前创建的空白游戏对象上,然后在编辑器中按“播放”。

现在终于可以看到自动化发生了!

在这里插入图片描述
将每2秒钟打印一次Idle,然后每3秒钟巡逻一次。

运动动作

在这里插入图片描述
现在让我们进入另一个层次。让我们制作一个移动的对象,同时在控制台中打印出字符串。但是我们不会一蹴而就。我们将创建另一个将添加到相同状态的动作。

让我们制作另一个名为AITestTwo的脚本。

在这里插入图片描述

让我们创建另一个动作,称为MoveAction。这将是移动物体的一般动作。

打开MoveAction.cs,让我们编写动作。

MoveAction.cs是不同的,我们必须引用对象的变换,以便动作可以移动它。

using UnityEngine;
using System.Collections;
using Common.FSM;

public class MoveAction : FSMAction
{
	private Transform transform;
	private Vector3 positionFrom;
	private Vector3 positionTo;
	private float duration;
	private float cachedDuration;
	private string finishEvent;
	private float journeyLength;
	private float polledTime;


	public MoveAction (FSMState owner) : base (owner)
	{
	}

	public void Init (Transform transform, Vector3 from, Vector3 to, float duration, string finishEvent = null)
	{
		this.transform = transform;
		this.positionFrom = from;
		this.positionTo = to;
		this.duration = duration;
		this.cachedDuration = duration;
		this.finishEvent = finishEvent;
		this.journeyLength = Vector3.Distance (this.positionFrom, this.positionTo);
		this.polledTime = 0;
	}
}

让我们讨论属性。如前所述,我们需要在此操作中引用对象的变换,因为我们希望此操作移动对象,而不是AI类。同样很容易理解,从Vector3到操作的持续时间和完成事件。

如您所见,Init()属性中没有包含两个变量,即tripageLength和polledTime。我们稍后将在Vector3.Lerp函数中使用它们。稍后将把轮询时间包括在我们的计算中。

public override void OnEnter ()
{

	if (duration <= 0) {
		Finish ();
		return;
	}

	SetPosition (this.positionFrom);
}

public override void OnUpdate ()
{
	polledTime += Time.deltaTime;
	duration -= Time.deltaTime;

	if (duration <= 0) {
		Finish ();
		return;
	}

	SetPosition (Vector3.Lerp (this.positionFrom, this.positionTo, Mathf.Clamp (polledTime / cachedDuration, 0, 1)));
}

private void Finish ()
{
	if (!string.IsNullOrEmpty (finishEvent)) {
		GetOwner ().SendEvent (finishEvent);
	}

	SetPosition (this.positionTo);
	this.polledTime = 0;
	duration = cachedDuration;
	this.journeyLength = Vector3.Distance (this.positionFrom, this.positionTo);
}

private void SetPosition (Vector3 position)
{
	this.transform.position = position;
}

现在,我们为位置添加了一个辅助函数,该函数是SetPosition(),它使我们更容易进行操作。再次,OnEnter()初始化动作,OnUpdate()将是执行Lerp函数的函数。这也是我们将计算Lerp函数所需比率的地方。操作完成后,我们将对象设置到所需的最终位置,并重置变量。

多动作实施

现在,让我们开始设置第二个AI。

using UnityEngine;
using System.Collections;
using Common.FSM;

public class AITestTwo : MonoBehaviour
{
	private FSM fsm;
	private FSMState MoveLeftState;
	private FSMState MoveRightState;
	private TextAction MoveLeftTextAction;
	private TextAction MoveRightTextAction;
	private MoveAction MoveLeftAction;
	private MoveAction MoveRightAction;

	private void Start ()
	{
		fsm = new FSM ("AITest FSM Two");
		MoveLeftState = fsm.AddState ("MoveLeftState");
		MoveRightState = fsm.AddState ("MoveRightState");
		MoveLeftTextAction = new TextAction (MoveLeftState);
		MoveRightTextAction = new TextAction (MoveRightState);
		MoveLeftAction = new MoveAction (MoveLeftState);
		MoveRightAction = new MoveAction (MoveRightState);

		//This adds the actions to the state and add state to it's transition map
		MoveLeftState.AddAction (MoveLeftTextAction);
		MoveLeftState.AddAction (MoveLeftAction);
		MoveRightState.AddAction (MoveRightTextAction);
		MoveRightState.AddAction (MoveRightAction);

		MoveLeftState.AddTransition ("ToRight", MoveRightState);
		MoveRightState.AddTransition ("ToLeft", MoveLeftState);

		//This initializes the actions
		MoveLeftTextAction.Init ("AI moving left", 1.0f, "");
		MoveRightTextAction.Init ("AI moving right", 1.0f, "");
		MoveLeftAction.Init (this.transform, new Vector3 (1, 0, 0), new Vector3 (-1, 0, 0), 1.0f, "ToRight");
		MoveRightAction.Init (this.transform, new Vector3 (-1, 0, 0), new Vector3 (1, 0, 0), 1.0f, "ToLeft");

		//Starts the FSM
		fsm.Start ("MoveLeftState");
	}

	private void Update ()
	{
		fsm.Update ();
	}
}

现在,每个状态有两个动作。FSM现在将在一种状态下更新两个动作。我们在这里所做的与在TextAction.cs上所做的相同。我们创建了类的实例,将操作添加到状态,并添加了对这些状态的转换和调用,并初始化了操作。

现在,让我们开始游戏,并希望它能正常运行。
在这里插入图片描述
在这里插入图片描述
如您所见,AI现在正在同时执行操作。您可以混合使用不同的操作,然后将其动态添加到所需的状态。

这样做的好处是,您可以添加许多动作并将它们包含在一个状态中并同时执行。尽管如果使用移动设备,这会导致一些性能问题。

至此,我们已经完成了Dynamic FSM教程。我希望这将对您的游戏有所帮助。您可以在任何游戏中以任何方式使用此功能。无论是在UI,GameObjects还是任何适合您的需求上使用它。

FSM是一个非常大的主题,分为三个部分,因此,如果您想更好地理解它,那么这里很少有与FSM相关的文章和实现。我相信这些将进一步解释FSM:

来自Unity3D Wiki的FSM
Unity3d-Finite-State-Machine
维基的FSM
Unity Gem

如果您想查看整个项目,这是unity软件包的链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值