简介
行为树(Behavior Trees)是一种许多游戏都很流行的AI 技术(类似人工智能)。halo2(微软的一款射击游戏)是第一款使用行为树的主流游戏,微软发布了halo2游戏中行为树的具体实现之后,行为树在游戏应用中开始流行起来。
行为树是多种AI 技术的组合:
- 分层状态机
- 时间控制
- 计划任务
- 动作执行
它的主要优势是容易理解以及可以使用 可视化编辑器 去编辑行为树。
一个简单的行为树就像下图:
最简单的的行为树是一群任务(Task)的集合。在上面的图中
任务组成
有两个条件任务
- Within Sight – 检查敌人是否在视野范围内
- Enough Bullets – 检查是否有足够的子弹
两个动作任务,这两个条件任务中,如果两个条件任务都满足的话,就开始运行两个动作任务。
- Shoot – 举起武器
- Shoot Animation – 播放射击动画。
行为树的强大之处在于,当一个动作任务运行失败之后,可以继续运行另外一个动作任务,假如还有一个动作任务是逃跑,当开枪失败后执行逃跑。
执行控制
顺序执行任务(Sequence ) – 一个一个的运行他的子任务,一次运行一个任务。首先检查敌人是否在视野中(Within Sight),然后检查自己是否有足够的子弹(Enough Bullets)。
并发执行任务(Parallel) – 一次运行所有任务。如果两个子任务都运行成功就开始运行并发执行任务(Shoot和Shoot Animation)。
在上图中没有出现装饰任务,装饰任务的一个重要用处是进行任务终止。
- 例如一个机器人正在采矿,当机器人发现敌人的时候终止采矿。
- 用于返回子任务的某个值或者继续运行子任务直到完全成功为止。
任务一般有四种不同类型:
- 动作任务(Action) – 通过某种操作改变游戏或者游戏物体状态的任务(例如移动物体,旋转物体)。
- 条件任务(Conditional) – 设定其他任务执行的条件(例如设定一个参数等于2的时候执行其他任务)。
- 复合任务(Composite) – 是一群子任务的父节点。
- 装饰任务(Decorator) – 可以拥有一个子任务,他的功能是改变子任务的值。
行为树还有一个重要的特性,就是任务状态的返回值。很多任务都不是一帧就运行完毕的,比如大多数动画在一帧内都不能运行完毕。另外条件任务需要告诉父节点(比如复合任务)条件是否成立,以便父节点判定是否继续运行子节点。刚才提到的情况都可以使用任务状态来解决。
一个任务具有三种状态:运行中、成功、失败。
在上图例子中,射击动画任务按只要是射击动画在运行,他就会一直处于运行状态。而条件任务中,敌人处于可视范围后,条件任务将在一帧的时间内返回成功或者失败。
组件介绍
行为树组件(行为树创建的时候自动添加的组件,在被附加行为树的GameObject上)在 Behavior Designer 和任务之前之间的扮演了一个连接作用的角色。
通过下面的代码可以开始和终止一个行为树:
public void EnableBehavior(); public void DisableBehavior(bool pause = false);
你可以找到任务使用了下面其中的方法:
TaskType FindTask< TaskType >(); List< TaskType > FindTasks< TaskType >(); Task FindTaskWithName(string taskName); List< Task > FindTasksWithName(string taskName);
行为树当前的执行状态可以使用以下方法获得:
behaviorTree.ExecutionStatus;
当行为树运行的时候将返回运行状态。当行为树执行完毕将返回成功或者失败,这个取决与任务完成情况。
接下来的事件也可以被描述为:OnBehaviorStart OnBehaviorRestart OnBehaviorEnd
行为树组件有以下属性:
Behavior Name:行为树名称;
Behavior Description:行为树描述;
External Behavior:指定运行的外部行为树;
Group:行为树组,用于更加容易的找到行为树。
Start When Enabled:开始运行游戏时立刻执行;
Pause When Disabled:当游戏暂停时,行为树暂停,该项未打勾时暂停游戏行为树将直接结束;
Restart When Complete:循环执行行为树。
Reset Values On Restart:游戏开始时重设变量值;
Log Task Changes:输出行为树日志;
代码形式创建行为树组件
在某些情况下需要使用代码创建。比如:你已经创建一个外部行为树,你想动态的加载它。你可以使用以下代码来加载:
using UnityEngine; using BehaviorDesigner.Runtime; public class CreateTree : MonoBehaviour { public ExternalBehaviorTree behaviorTree; void Start () { var bt = gameObject.AddComponent<BehaviorTree>(); bt.ExternalBehavior = behaviorTree; bt.StartWhenEnabled = false; } }
说明
公共变量
behaviorTree
引用了一个外部行为树。当新建的行为树加载的时候也会加载外部行为树。设置startWhenEnabled
为flase
是为了阻止外部行为树加载后立刻运行,这样我们可以在任何时候使用bt.enableBehavior()
去手动的运行外部行为树。
行为管理器
行为管理器(Behavior Manager)是在运行行为树插件的时候自动创建的组件,他用于管理场景中的所有行为树。
你可以改变行为树的 更新间隔属性(Update Interval) 。
- “Every Frame” – 行为树将每帧进行更新。
- “Specify Seconds” – 按照指定时间进行更新。
- “Manual” – 手动更新行为树。你可以使用以下方法:
BehaviorManager.instance.Tick();
如果你想不同行为树拥有不同的更新频率,你可以手动的设置他们:
BehaviorManager.instance.Tick(BehaviorTree);
任务执行类型(Task Execution Type) 允许你指定一个行为树是否应该继续运行任务直到他遇到一个已经运行完毕的任务或者继续运行到一个指定的最大任务运行数量。
例如下面例子:
循环执行任务(Repeater)设置为重复5次。如果任务执行类型设置为No Duplicates,Play Sound任务在一个周期中将只运行一次。如果任务执行类型设置为Count,并设置一个最大值为5,那么他将在一个周期中执行5次。
任务
行为树是一组任务的集合。任务拥有和unity MonoBehaviour 相似的API,所以他很容易的让你创建一个任务。
任务类有如下API:
// OnAwake is called once when the behavior tree is enabled. Think of it as a constructor public virtual void OnAwake(); // OnStart is called immediately before execution. It is used to setup any variables that need to be reset from the previous run public virtual void OnStart(); // OnUpdate runs the actual task public virtual TaskStatus OnUpdate(); // OnEnd is called after execution on a success or failure. public virtual void OnEnd(); // OnPause is called when the behavior is paused and resumed public virtual void OnPause(bool paused); // The priority select will need to know this tasks priority of running public virtual float GetPriority(); // OnBehaviorComplete is called after the behavior tree finishes executing public virtual void OnBehaviorComplete(); // OnReset is called by the inspector to reset the public properties public virtual void OnReset(); // Allow OnDrawGizmos to be called from the tasks public virtual void OnDrawGizmos(); // Keep a reference to the behavior that owns this task public Behavior Owner;
任务有三个共有的属性:
- 名称
- 注释
- 瞬时状态。
在一个相同的循环周期中,一个任务返回成功或者失败后,任务将立刻运行下一个任务,先前的任务将处于等待状态。这种模式有利于减少行为树性能消耗。
任务运行的流程: