您的位置:首页 > 移动开发 > Unity3D

Unity 有限状态机(Finite State Machine)的理解 与 实现简单的可插拔(Pluggable)AI脚本对象。

2017-06-10 22:20 921 查看
#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;
}


测试结果

第一种,巡逻->发现目标->追杀->目标死亡->巡逻。(颜色配合下图样例使用。)



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



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



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



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



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



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



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



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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: