使用Javascript和HTML5,观看AI建立自己的军队并相互战斗!飞机,坦克,基地和医务人员每次都进行一场有趣的战斗。部队将撤退并得到修理,喷气机混战,猛oth的坦克统治地面。
https://github.com/blitzxion/Tanks.js
https://github.com/ZeroChiLi/TanksPluggableAI
Unity 有限状态机(Finite State Machine)的理解 与 实现简单的可插拔(Pluggable)AI脚本对象。
#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);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
状态名 说明 配置
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);
}
1
2
3
4
5
6
7
动作名 说明 原理
PatrolAction 巡逻动作。从StateController搞来巡逻点列表。在这些巡逻点之间巡逻移动。 设置巡逻点为导航目标点,到达后设置下一个巡逻点为导航点。
ChaseAction 追随动作。最短距离到达目标。 设置目标为导航目标点,导航过去就是了。
AttackAction 攻击动作。攻击目标。 射出检测射线(红色的),检测到目标就发炮攻击。
Decision(决定)
using UnityEngine;
public abstract class Decision : ScriptableObject
{
//通过这个方法的返回值来判断决定的选择。
public abstract bool Decide(StateController controller);
}
1
2
3
4
5
6
7
决定名 说明 原理
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
2
3
4
5
6
7
8
9
测试结果
第一种,巡逻->发现目标->追杀->目标死亡->巡逻。(颜色配合下图样例使用。)
1. 起始 Patrol Chase 状态,巡逻点之间移动,还没有检测到目标,眼里射出的是清澈的绿色。
2. 发现了目标,转为Chase Chaser 状态,射出了充满愤怒的红色射线,并且一直攻击目标。
3. 当目标化为灰烬,转回 Patrol Chase 状态。
第二种,巡逻->发现目标->追杀->目标死亡->旋转扫描目标->{两种情况:1. 发现目标,回到第二步。2. 超过扫描时间,回到第一步(巡逻)。(颜色配合下图样例使用。)
1. 起始 Patrol Scanner 状态,巡逻,眼里射出的是原谅的绿色。
2. 发现刚刚杀了蓝孩子的小红,转为 Chase Scanner 状态,追杀之。
3. 杀掉小红之后,转为 Alert Scanner 状态。旋转扫描周围是否有敌人。
4. 没有扫到,回到 Patrol Scanner 状态。