#Unity 有限状态机(Finite State Machine)的理解 与 实现简单的可插拔AI脚本对象。
参考官方教程:Pluggable AI With Scriptable Objects
Github完整项目:TanksPluggableAI
大神Blog:【游戏设计模式】之三 状态模式、有限状态机 & Unity版本实现
一般的游戏AI都是使用状态机的设计模式来实现的。发现官方有教程,就跟了一遍,这里就总结一下。
先简单说一下状态模式。就是根据当前状态和对应条件,选则需要转换的下一个状态。例如本文的例子,坦克默认状态是巡逻(状态),在发现有敌人时(条件),转换成追杀(状态),在敌人逃脱或死掉了之后(条件),又变成巡逻(状态)。
游戏场景:绿色标签为巡逻点。蓝色标签为玩家出生点。红色为AI出生点。(如图玩家被两个AI追杀。)
AI对象结构
每个AI对象都有一个状态控制器(StateController)脚本组件,包含一个当前状态(State),且状态包含需要执行动作(Action),还有状态转换的条件(Transition)。
Unity中文件分类层级如下。其中DefaultEnemyStats是默认AI的一些配置。包括攻击距离,移动速度,旋转速度等等。
State (状态)
using UnityEngine;
[CreateAssetMenu (menuName = "PluggableAI/State")]
public class State : ScriptableObject
{
public Action[] actions; //动作
public Transition[] transitions; //通过决定,选择下一种动作决定
public Color sceneGizmoColor = Color.gray; //拿来渲染eyes的Gizmos颜色
//每一帧更新状态,在StateController的OnUpdate中调用。
public void UpdateState(StateController controller)
{
DoActions(controller); //执行动作
CheckTransition(controller); //检测转换状态
}
//顺序执行动作列表的动作。
private void DoActions(StateController controller)
{
for (int i = 0; i < actions.Length; i++)
actions[i].Act(controller);
}
//检查所有转换状态,并改变状态。
private void CheckTransition(StateController controller)
{
for (int i = 0; i < transitions.Length; i++)
{
//这里条件转换只有两个,所以直接用Bool类型来判断。当然也可以有多种条件转换。
bool decisionSucceeded = transitions[i].decision.Decide(controller);
if (decisionSucceeded)
controller.TransitionToState(transitions[i].trueState);
else
controller.TransitionToState(transitions[i].falseState);
}
}
}
状态名 | 说明 | 配置 |
---|---|---|
Remain State | 保持原来状态。不包含任何属性。 | 略。 |
PatrolChaser | 巡逻者状态。在巡逻点之间巡逻,如果检测到敌人,转为追杀状态。 | ![]() |
ChaseChaser | 追杀者状态。导航到目标一定距离并持续攻击,直到目标死掉,转回巡逻者模式。 | ![]() |
PatrolScanner | 同Patrol Chaser。巡逻到敌人,转为追杀。 | ![]() |
ChaseChaser | 同Chase Chaser 。追杀敌人,但到目标死掉,转换成警觉模式。 | ![]() |
AlertScanner | 警觉模式。其实就是旋转扫描敌人,直到扫描到目标或超过了一定时间。 | ![]() |
通过上表其实可以发现有两个非常相似的动作,只是为了演示Alert Scanner 的。其实是可以优化的,就是修改Transition的结构,不再是只有两种状态选择,而是多种,这样就可以更好的重用状态。
Action(动作)
using UnityEngine;
public abstract class Action : ScriptableObject
{
//State直接调用这个方法来执行动作。
public abstract void Act(StateController controller);
}
动作名 | 说明 | 原理 |
---|---|---|
PatrolAction | 巡逻动作。从StateController搞来巡逻点列表。在这些巡逻点之间巡逻移动。 | 设置巡逻点为导航目标点,到达后设置下一个巡逻点为导航点。 |
ChaseAction | 追随动作。最短距离到达目标。 | 设置目标为导航目标点,导航过去就是了。 |
AttackAction | 攻击动作。攻击目标。 | 射出检测射线(红色的),检测到目标就发炮攻击。 |
Decision(决定)
using UnityEngine;
public abstract class Decision : ScriptableObject
{
//通过这个方法的返回值来判断决定的选择。
public abstract bool Decide(StateController controller);
}
决定名 | 说明 | 原理 |
---|---|---|
LookDecision | 检测决定。检测到目标就设置为追踪目标,并且返回True。 | 射出检测球体射线(绿色的)来检测。 |
ActiveStateDecision | 活动状态决定。就是判断追踪的目标是否active(激活状态)。 | 直接返回追踪目标的active的bool值。 |
ScanDecision | 扫描决定。说是扫描其实并没有扫描,只是在转圈而已。一直转到外部条件改变状态,或者超过了扫描限定的时间。 | 停止导航。配合Time.deltaTime旋转自己。返回是否超过扫描限定时间。 |
Transitions(状态转换)
//状态转换。通过决定的返回值,选则两种状态中其中一个
[System.Serializable]
public class Transition
{
public Decision decision;
public State trueState;
public State falseState;
}