当前位置: 代码迷 >> 综合 >> FSM(Finite State Machines):有限状态机[第3部分]
  详细解决方案

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

热度:97   发布时间:2024-01-26 04:31:10.0

这是我们针对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是更新时将在控制台中打印出的字符串。持续时间是此操作的持续时间,而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工作。

//This initializes the actions
PatrolAction.Init ("AI on patrol", 3.0f, "ToIdle");
IdleAction.Init ("AI on Idle", 2.0f, "ToPatrol");
private void Update ()
{fsm.Update ();
}

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

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

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

运动动作

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

让我们制作另一个名为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 mapMoveLeftState.AddAction (MoveLeftTextAction);MoveLeftState.AddAction (MoveLeftAction);MoveRightState.AddAction (MoveRightTextAction);MoveRightState.AddAction (MoveRightAction);MoveLeftState.AddTransition ("ToRight", MoveRightState);MoveRightState.AddTransition ("ToLeft", MoveLeftState);//This initializes the actionsMoveLeftTextAction.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 FSMfsm.Start ("MoveLeftState");}private void Update (){fsm.Update ();}
}

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

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

这样做的好处是,您可以添加许多动作并将它们包含在一个状态中并同时执行。尽管如果使用移动设备,这会导致一些性能问题。我的建议是,当您将其用于移动设备时,为了安全起见,请至少同时执行至少1或2个操作。不过,我还没有针对移动设备进行过真正的测试。

至此,我们已经完成了Dynamic FSM教程。我希望这将对您的游戏有所帮助。您可以在任何游戏中以任何方式使用此功能。无论是在UI,GameObjects还是任何适合您的需求上使用它。如有任何疑问,请务必发表评论,我们很乐意尽快回复!

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

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

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

  相关解决方案