Unity有限状态机的理解与实现简单的可插拔AI脚本对象
发表于2018-01-04
先简单说一下状态模式。就是根据当前状态和对应条件,选则需要转换的下一个状态。例如本文的例子,坦克默认状态是巡逻(状态),在发现有敌人时(条件),转换成追杀(状态),在敌人逃脱或死掉了之后(条件),又变成巡逻(状态)。
游戏场景:绿色标签为巡逻点。蓝色标签为玩家出生点。红色为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 | 警觉模式。其实就是旋转扫描敌人,直到扫描到目标或超过了一定时间。 |
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;
}
测试结果
第一种,巡逻->发现目标->追杀->目标死亡->巡逻。(颜色配合下图样例使用。)

1.起始 Patrol Chase 状态,巡逻点之间移动,还没有检测到目标,眼里射出的是清澈的绿色。

2.发现了目标,转为Chase Chaser 状态,射出了充满愤怒的红色射线,并且一直攻击目标。

3.当目标化为灰烬,转回 Patrol Chase 状态。

第二种,巡逻->发现目标->追杀->目标死亡->旋转扫描目标->{两种情况:1. 发现目标,回到第二步。2. 超过扫描时间,回到第一步(巡逻)。(颜色配合下图样例使用。)

1.起始 Patrol Scanner 状态,巡逻,眼里射出的是原谅的绿色。

2.发现刚刚杀了蓝孩子的小红,转为 Chase Scanner 状态,追杀之。

3.杀掉小红之后,转为 Alert Scanner 状态。旋转扫描周围是否有敌人。

4.没有扫到,回到 Patrol Scanner 状态。

