# 观前提醒
项目会在 GitHub 中开源,链接:https://github.com/Maikire/Unity/tree/main/UnityFramework/BehaviorTree
# 行为树(Behavior Tree)
行为树(Behavior Tree, BT)是一种在游戏开发(尤其是 AI)、机器人控制、自动化系统等领域广泛使用的任务建模和执行框架。它以其高度模块化、可读性强、易于设计和调试的特性,成为替代或补充传统状态机(FSM)和分层状态机(HFSM)的流行选择。
行为树是一种树状的结构,从根节点开始执行,自顶向下,从左到右:执行流通常从根节点开始,按照特定的顺序遍历子节点。控制节点根据其类型和子节点的返回状态决定下一步执行哪个节点。
行为树与有限状态机的对比
特性 | 行为树 (BT) | 状态机 (FSM) |
---|---|---|
结构 | 层次化树形结构 | 节点(状态)和边(转换)构成的图 |
逻辑组织 | 控制流(顺序、选择、并行) | 状态和转换条件 |
复用性 | 高 (节点、子树高度复用) | 中 / 低 (状态逻辑容易粘连) |
可读性 | 高 (树形结构直观,尤其可视化后) | 中 (状态多时转换线可能混乱) |
调试 | 较易 (运行时可视化执行路径) | 较难 (跟踪状态转换历史) |
动态响应 | 高 (每次 Tick 重新评估条件) | 中 (依赖状态转换条件触发) |
长任务 | 内建支持 (Running 状态) | 需手动管理 (状态内维护计时器等) |
复杂度 | 节点类型多,但逻辑分散 | 状态数量可能爆炸,转换条件复杂 |
适用场景 | 复杂决策逻辑、任务导向 AI、需要高度模块化和复用性 | 状态明确、转换相对简单、对性能要求苛刻 |
# 行为树设计
# 核心节点
行为树的核心节点类型
- Composite 组合节点
- 这些节点负责控制子节点的执行流程
- 包括:选择节点、顺序节点、并行节点、优先级节点
- 例如:选择(Selector)节点,实现 “或” 的逻辑
- Decorator 装饰节点
- 这些节点只包含一个子节点,用来修改它的行为或结果
- 包括:反转节点、重复节点、计时节点、限制节点、过滤节点、成功节点、失败节点
- 例如:过滤(Filter)节点,需要先检查条件,以此决定是否执行子节点
- Condition 条件节点
- 条件节点检查某个条件是否成立,例如 “是否看到敌人”、“是否血量小于 50%” 等
- Behavior 行为节点
- 这些是真正执行动作的节点,比如:“移动到某个点”、“播放动画”、“攻击” 等
- 根节点
- 树的起点,唯一的节点(通常是 Selector)
每个节点在执行后会返回四个结果之一,行为树系统会根据这些返回值来判断下一步该做什么:
- Success:节点任务成功完成
- Failure:节点任务失败
- Running:节点任务需要更多时间才能完成(正在执行中)
- 下次会继续从这个节点开始执行(或由其父节点决定),而不是每次都从根节点开始。这是行为树管理长时间任务的核心机制。
- Abort:强制中断所有节点,用于处理紧急情况或高优先级节点
假设有个敌人 AI,它的行为逻辑是:
- 如果看见玩家,就追击
- 否则就在原地巡逻
用行为树来描述就是:
Selector | |
├── Filter | |
│ ├── Condition: 看见玩家 | |
│ └── Behavior: 追击 | |
└── Behavior: 巡逻 |
# 重要结构 - 栈
树的遍历经常需要使用栈这个结构,行为树也是如此
行为树的执行过程在结构上非常接近树的先根遍历
- 因为行为树每一帧的执行流程,大致是:
- 先访问当前节点(父节点)
- 然后依次遍历并执行它的每一个子节点(从左到右)
- 遇到某些特殊控制节点(如 Selector 或 Sequence),根据子节点返回值决定是否继续往下遍历或中止
- 这和先根遍历(父节点 → 子节点)逻辑一致
# 重要结构 - 黑板
它是一个共享的数据存储区域(通常是键值对容器或结构体,但是在 CSharp 中使用类会更方便),被行为树中的所有节点访问和修改。用于存储和传递各类数据
- 行为树节点数据(节点栈、根节点、当前节点)
- AI 实体的状态(位置、生命值、目标)
- 感知信息(最近看到的敌人、听到的声音位置)
- 临时任务数据(移动的目标点、要攻击的对象)
- 节点间通信,避免了节点之间直接耦合
# 代码
# 数据类
节点状态
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 行为树节点返回的状态 | |
/// </summary> | |
public enum BTState | |
{ | |
/// <summary> | |
/// 成功 | |
/// </summary> | |
Success, | |
/// <summary> | |
/// 失败 | |
/// </summary> | |
Failure, | |
/// <summary> | |
/// 运行 | |
/// </summary> | |
Running, | |
/// <summary> | |
/// 中断 | |
/// </summary> | |
Abort, | |
} | |
} |
自动寻路的模式
using System; | |
using UnityEngine; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 寻路模式 | |
/// </summary> | |
[Serializable] | |
public enum BTPatrolModes | |
{ | |
[Tooltip("无(在巡逻节点返回 失败)")] | |
None, | |
[Tooltip("单次")] | |
Once, | |
[Tooltip("循环")] | |
Loop, | |
[Tooltip("往返")] | |
RoundTrip, | |
} | |
} |
过滤结构
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 过滤结构 | |
/// </summary> | |
public struct BTFilter | |
{ | |
/// <summary> | |
/// 条件节点 | |
/// </summary> | |
public ConditionNode condition; | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
public BTNode child; | |
} | |
} |
黑板(Blackboard)
using Character; | |
using SkillSystem; | |
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 行为树数据 | |
/// </summary> | |
[Serializable] | |
public class BTBlackboard | |
{ | |
[Tooltip("行为树数据")] | |
public BTAsset nodeAsset; | |
[Tooltip("移动速度")] | |
public float moveSpeed; | |
[Tooltip("视野距离")] | |
public float viewDistance; | |
[Tooltip("视野角度")] | |
public float viewAngle; | |
[Tooltip("可以被发现的目标的标签")] | |
public string[] targetTags = { "Player" }; | |
[Tooltip("进入攻击节点的距离")] | |
public float attackNodeDistance; | |
[Tooltip("攻击时间间隔")] | |
public float attackTimeInterval; | |
[Tooltip("判断是否到达路点的偏移量")] | |
public float patrolEffect; | |
[Tooltip("寻路模式")] | |
public BTPatrolModes patrolMode = BTPatrolModes.None; | |
[Tooltip("巡逻的路点")] | |
public Transform[] wayPoints; | |
[HideInInspector] | |
[Tooltip("外部中断条件")] | |
public List<Func<bool>> interruptions; | |
[HideInInspector] | |
[Tooltip("节点栈")] | |
public Stack<BTNode> nodeStack; | |
[HideInInspector] | |
[Tooltip("优先级节点")] | |
public BTNode priorityNode; | |
[HideInInspector] | |
[Tooltip("根节点")] | |
public BTNode root; | |
/// <summary> | |
/// 当前节点 | |
/// </summary> | |
public BTNode Current | |
{ | |
get | |
{ | |
if (nodeStack.Count == 0) | |
{ | |
return null; | |
} | |
return nodeStack.Peek(); | |
} | |
} | |
[HideInInspector] | |
[Tooltip("角色的信息")] | |
public CharacterStatus character; | |
[HideInInspector] | |
[Tooltip("角色的动画")] | |
public Animator anim; | |
[HideInInspector] | |
[Tooltip("发现的目标")] | |
public Transform[] foundTargets; | |
[HideInInspector] | |
[Tooltip("技能系统")] | |
public CharacterSkillSystemNPC npcSkill; | |
[HideInInspector] | |
[Tooltip("当前的技能")] | |
public SkillData currentSkill; | |
} | |
} |
配置文件采用 ScriptableObject
using UnityEngine; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 行为树配置 | |
/// </summary> | |
[CreateAssetMenu(menuName = "Behavior Tree Asset")] | |
public class BTAsset : ScriptableObject | |
{ | |
[Tooltip("行为树的节点配置")] | |
[TextArea(10, 80)] | |
public string config; | |
} | |
} |
# 节点基类
- BTNode 作为所有节点的基类,包括共有的属性和方法
- 提供节点状态属性
- 提供进入节点、运行节点、退出节点、中断节点的方法
- 统一控制入栈和出栈
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 行为树节点的基类 | |
/// </summary> | |
public abstract class BTNode | |
{ | |
/// <summary> | |
/// 当前节点的执行状态 | |
/// </summary> | |
public BTState State { get; protected set; } = BTState.Failure; | |
/// <summary> | |
/// 进入节点时执行一次 | |
/// </summary> | |
public virtual void EnterNode(BehaviorTree bt) | |
{ | |
bt.blackboard.nodeStack.Push(this); | |
} | |
/// <summary> | |
/// 行为树节点的执行方法 | |
/// </summary> | |
/// <returns></returns> | |
public abstract BTState TickNode(BehaviorTree bt); | |
/// <summary> | |
/// 运行结束时执行一次 | |
/// </summary> | |
public virtual void ExitNode(BehaviorTree bt) | |
{ | |
bt.blackboard.nodeStack.Pop(); | |
} | |
/// <summary> | |
/// 中断节点的执行 | |
/// </summary> | |
public void Abort(BehaviorTree bt) | |
{ | |
ExitNode(bt); | |
State = BTState.Abort; | |
} | |
} | |
} |
- 根据核心节点的类型进一步细分为三类:控制节点、行为节点、条件节点
- 控制节点:组合节点和装饰节点的父类。提供添加子节点的方法,便于构建行为树
- 行为节点:提供行为节点特有的字段、属性、方法
- 条件节点:提供特有的条件属性,简化具体条件节点的代码
控制节点
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 控制节点(用于控制子节点的执行) | |
/// </summary> | |
public abstract class ControlNodes : BTNode | |
{ | |
/// <summary> | |
/// 添加子节点 | |
/// </summary> | |
/// <param name="child"></param> | |
public virtual void AddChild(BTNode child) { } | |
/// <summary> | |
/// 添加过滤器 | |
/// </summary> | |
/// <param name="condition"></param> | |
/// <param name="child"></param> | |
public virtual void AddChild(BTFilter filter) { } | |
} | |
} |
行为节点
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 行为节点 | |
/// </summary> | |
public abstract class BehaviorNode : BTNode | |
{ | |
/// <summary> | |
///true: 搜索目标时忽略墙体 | |
/// </summary> | |
public virtual bool IgnoreWalls { get => false; } | |
} | |
} |
条件节点
using System; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 条件节点 | |
/// </summary> | |
public abstract class ConditionNode : BTNode | |
{ | |
/// <summary> | |
/// 条件 | |
/// </summary> | |
protected abstract Func<BehaviorTree, bool> Condition { get; } | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
if (Condition.Invoke(bt)) | |
{ | |
return State = BTState.Success; | |
} | |
else | |
{ | |
return State = BTState.Failure; | |
} | |
} | |
} | |
} |
# 组合节点
- 选择节点:像 “或者”,从头到尾尝试子节点,直到有一个成功就返回成功
- 顺序节点:像 “并且”,从头到尾依次执行子节点,一旦失败就返回失败
- 并行节点:所有子节点都会依次执行一次
- 优先级节点:独立于行为树,不在根节点及其子节点下,不可中断,每轮都会检测,成功时中断其他所有节点,执行优先级节点
- 需要中断栈中所有的节点,所以它本身不能入栈
- 不论上次返回的状态是什么,优先级节点每帧都会从头开始依次执行过滤,如果优先级节点有多个子节点(过滤结构),那么先判断的节点会将后判断的节点打断(例如:死亡节点和攻击节点都在优先级节点中,并且死亡节点排在前。此时,即使攻击节点处于 Running 状态,死亡节点也会先判断,并在通过时将攻击节点中断)
选择节点
using System.Collections.Generic; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 选择节点(只要一个子节点成功就算成功) | |
/// </summary> | |
public class SelectorNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private List<BTNode> children; | |
/// <summary> | |
/// 当前子节点索引 | |
/// </summary> | |
private int currentIndex = 0; | |
public SelectorNode() | |
{ | |
children = new List<BTNode>(); | |
} | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
children.Add(child); | |
} | |
public override void EnterNode(BehaviorTree bt) | |
{ | |
base.EnterNode(bt); | |
currentIndex = 0; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
BTNode child; | |
while (currentIndex < children.Count) | |
{ | |
child = children[currentIndex]; | |
if (State != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
State = child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Success: | |
child.ExitNode(bt); | |
currentIndex = 0; | |
return BTState.Success; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
child.Abort(bt); | |
currentIndex = 0; | |
return BTState.Abort; | |
case BTState.Failure: | |
child.ExitNode(bt); | |
currentIndex++; | |
break; | |
} | |
} | |
currentIndex = 0; | |
return State = BTState.Failure; | |
} | |
public override void ExitNode(BehaviorTree bt) | |
{ | |
base.ExitNode(bt); | |
currentIndex = 0; | |
} | |
} | |
} |
顺序节点
using System.Collections.Generic; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 顺序节点(全部成功才算成功) | |
/// </summary> | |
public class SequenceNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private List<BTNode> children; | |
/// <summary> | |
/// 当前子节点索引 | |
/// </summary> | |
private int currentIndex = 0; | |
public SequenceNode() | |
{ | |
children = new List<BTNode>(); | |
} | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
children.Add(child); | |
} | |
public override void EnterNode(BehaviorTree bt) | |
{ | |
base.EnterNode(bt); | |
currentIndex = 0; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
BTNode child; | |
while (currentIndex < children.Count) | |
{ | |
child = children[currentIndex]; | |
if (State != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
State = child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Failure: | |
child.ExitNode(bt); | |
currentIndex = 0; | |
return BTState.Failure; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
child.Abort(bt); | |
currentIndex = 0; | |
return BTState.Abort; | |
case BTState.Success: | |
child.ExitNode(bt); | |
currentIndex++; | |
break; | |
} | |
} | |
currentIndex = 0; | |
return State = BTState.Success; | |
} | |
public override void ExitNode(BehaviorTree bt) | |
{ | |
base.ExitNode(bt); | |
currentIndex = 0; | |
} | |
} | |
} |
并行节点
using System.Collections.Generic; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 并行节点(所有子节点都会依次执行一次) | |
/// </summary> | |
public class ParallelNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private List<BTNode> children; | |
/// <summary> | |
/// 当前子节点索引 | |
/// </summary> | |
private int currentIndex = 0; | |
public ParallelNode() | |
{ | |
children = new List<BTNode>(); | |
} | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
children.Add(child); | |
} | |
public override void EnterNode(BehaviorTree bt) | |
{ | |
base.EnterNode(bt); | |
currentIndex = 0; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
BTNode child; | |
while (currentIndex < children.Count) | |
{ | |
child = children[currentIndex]; | |
if (State != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
State = child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Success: | |
child.ExitNode(bt); | |
currentIndex++; | |
break; | |
case BTState.Failure: | |
child.ExitNode(bt); | |
currentIndex++; | |
break; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
child.Abort(bt); | |
currentIndex = 0; | |
return BTState.Abort; | |
} | |
} | |
currentIndex = 0; | |
return State = BTState.Success; | |
} | |
public override void ExitNode(BehaviorTree bt) | |
{ | |
base.ExitNode(bt); | |
currentIndex = 0; | |
} | |
} | |
} |
优先级节点
using System.Collections.Generic; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 优先级节点(独立于行为树,不在根节点及其子节点下,不可中断,每轮都会检测,成功时中断其他所有节点,执行优先级节点) | |
/// </summary> | |
public class PriorityNode : ControlNodes | |
{ | |
/// <summary> | |
/// 过滤结构列表 | |
/// </summary> | |
private List<BTFilter> filters; | |
/// <summary> | |
/// 是否执行过子节点 | |
/// </summary> | |
private bool isTickedChild = false; | |
public PriorityNode() | |
{ | |
filters = new List<BTFilter>(); | |
} | |
public override void AddChild(BTFilter filter) | |
{ | |
base.AddChild(filter); | |
filters.Add(filter); | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
foreach (var item in filters) | |
{ | |
if (item.condition.TickNode(bt) == BTState.Success) | |
{ | |
if (State != BTState.Running) | |
{ | |
bt.AbortAll(); | |
item.child.EnterNode(bt); | |
isTickedChild = true; | |
} | |
State = item.child.TickNode(bt); | |
// 优先级节点不会被低级节点打断,所以这里不需要处理中断 | |
if (State != BTState.Running) | |
{ | |
item.child.ExitNode(bt); | |
isTickedChild = false; | |
} | |
return State; | |
} | |
else | |
{ | |
if (isTickedChild) | |
{ | |
item.child.Abort(bt); | |
isTickedChild = false; | |
} | |
} | |
} | |
isTickedChild = false; | |
return State = BTState.Failure; | |
} | |
} | |
} |
# 装饰节点
- 反转节点:如果子节点成功,返回失败;如果子节点失败,返回成功
- 重复节点:重复执行子节点
- 计时节点:用于控制子节点处于 Running 状态的时间
- 限制节点:限制子节点的总执行次数
- 过滤节点:每次执行都会先进行条件判断,以此决定是否执行子节点
- 成功节点:即使子节点返回失败,该节点也会返回成功
- 失败节点:即使子节点返回成功,该节点也会返回失败
反转节点
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 反转节点(如果子节点成功,返回失败;如果子节点失败,返回成功) | |
/// </summary> | |
public class InverterNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private BTNode child; | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
this.child = child; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
if (State != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
State = child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Success: | |
child.ExitNode(bt); | |
return State = BTState.Failure; | |
case BTState.Failure: | |
child.ExitNode(bt); | |
return State = BTState.Success; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
child.Abort(bt); | |
return BTState.Abort; | |
default: | |
child.ExitNode(bt); | |
return State = BTState.Failure; | |
} | |
} | |
} | |
} |
重复节点
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 重复节点(重复执行子节点) | |
/// </summary> | |
public class RepeaterNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private BTNode child; | |
/// <summary> | |
/// 子节点状态 | |
/// </summary> | |
private BTState childState; | |
public RepeaterNode() | |
{ | |
childState = BTState.Success; | |
} | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
this.child = child; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
if (childState != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
childState = child.TickNode(bt); | |
switch (childState) | |
{ | |
case BTState.Failure: | |
child.ExitNode(bt); | |
return State = BTState.Failure; | |
case BTState.Abort: | |
child.Abort(bt); | |
return State = BTState.Abort; | |
default: | |
break; | |
} | |
return State = BTState.Running; | |
} | |
} | |
} |
计时节点
using UnityEngine; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 计时节点(用于控制子节点处于 Running 状态的时间) | |
/// </summary> | |
public class TimingNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private BTNode child; | |
/// <summary> | |
/// 最大时间 | |
/// </summary> | |
private float maxTime; | |
/// <summary> | |
/// 当前时间 | |
/// </summary> | |
private float currentTime = 0; | |
public TimingNode(float time) | |
{ | |
this.maxTime = time; | |
} | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
this.child = child; | |
} | |
public override void EnterNode(BehaviorTree bt) | |
{ | |
base.EnterNode(bt); | |
currentTime = 0; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
currentTime += Time.deltaTime; | |
if (currentTime < maxTime) | |
{ | |
if (State != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
State = child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Success: | |
child.ExitNode(bt); | |
currentTime = 0; | |
return BTState.Success; | |
case BTState.Failure: | |
child.ExitNode(bt); | |
currentTime = 0; | |
return BTState.Failure; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
child.Abort(bt); | |
currentTime = 0; | |
return BTState.Abort; | |
default: | |
child.ExitNode(bt); | |
currentTime = 0; | |
return State = BTState.Success; | |
} | |
} | |
else | |
{ | |
child.ExitNode(bt); | |
currentTime = 0; | |
return State = BTState.Success; | |
} | |
} | |
public override void ExitNode(BehaviorTree bt) | |
{ | |
base.ExitNode(bt); | |
currentTime = 0; | |
} | |
} | |
} |
限制节点
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 限制节点(限制子节点的总执行次数) | |
/// </summary> | |
public class LimiterNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private BTNode child; | |
/// <summary> | |
/// 次数 | |
/// </summary> | |
private int limitCount; | |
/// <summary> | |
/// 当前次数 | |
/// </summary> | |
private int currentCount = 0; | |
public LimiterNode(int limitCount) | |
{ | |
this.limitCount = limitCount; | |
currentCount = 0; | |
} | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
this.child = child; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
while (currentCount < limitCount) | |
{ | |
if (State != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
State = child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Success: | |
currentCount++; | |
return BTState.Success; | |
case BTState.Failure: | |
currentCount++; | |
return BTState.Failure; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
child.Abort(bt); | |
return BTState.Abort; | |
} | |
} | |
return State = BTState.Success; | |
} | |
} | |
} |
过滤节点
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 过滤节点(每次执行都会先进行条件判断) | |
/// </summary> | |
public class FilterNode : ControlNodes | |
{ | |
/// <summary> | |
/// 过滤结构 | |
/// </summary> | |
private BTFilter filter; | |
/// <summary> | |
/// 是否执行过子节点 | |
/// </summary> | |
private bool isTickedChild = false; | |
public override void AddChild(BTFilter filter) | |
{ | |
base.AddChild(filter); | |
this.filter = filter; | |
} | |
public override void EnterNode(BehaviorTree bt) | |
{ | |
base.EnterNode(bt); | |
isTickedChild = false; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
if (filter.condition.TickNode(bt) == BTState.Success) | |
{ | |
if (State != BTState.Running) | |
{ | |
filter.child.EnterNode(bt); | |
isTickedChild = true; | |
} | |
State = filter.child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Success: | |
filter.child.ExitNode(bt); | |
isTickedChild = false; | |
return BTState.Success; | |
case BTState.Failure: | |
filter.child.ExitNode(bt); | |
isTickedChild = false; | |
return BTState.Failure; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
filter.child.Abort(bt); | |
isTickedChild = false; | |
return BTState.Abort; | |
default: | |
filter.child.ExitNode(bt); | |
isTickedChild = false; | |
return State = BTState.Failure; | |
} | |
} | |
else | |
{ | |
if (isTickedChild) | |
{ | |
filter.child.Abort(bt); | |
isTickedChild = false; | |
return State = BTState.Abort; | |
} | |
return State = BTState.Failure; | |
} | |
} | |
public override void ExitNode(BehaviorTree bt) | |
{ | |
base.ExitNode(bt); | |
isTickedChild = false; | |
} | |
} | |
} |
成功节点
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 成功节点(即使子节点返回失败,该节点也会返回成功) | |
/// </summary> | |
public class SuccessNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private BTNode child; | |
public SuccessNode() | |
{ | |
State = BTState.Success; | |
} | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
this.child = child; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
if (State != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
State = child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Success: | |
child.ExitNode(bt); | |
return BTState.Success; | |
case BTState.Failure: | |
child.ExitNode(bt); | |
return State = BTState.Success; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
child.Abort(bt); | |
return BTState.Abort; | |
default: | |
child.ExitNode(bt); | |
return State = BTState.Success; | |
} | |
} | |
} | |
} |
失败节点
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 失败节点(即使子节点返回成功,该节点也会返回失败) | |
/// </summary> | |
public class FailureNode : ControlNodes | |
{ | |
/// <summary> | |
/// 子节点 | |
/// </summary> | |
private BTNode child; | |
public FailureNode() | |
{ | |
State = BTState.Failure; | |
} | |
public override void AddChild(BTNode child) | |
{ | |
base.AddChild(child); | |
this.child = child; | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
if (State != BTState.Running) | |
{ | |
child.EnterNode(bt); | |
} | |
State = child.TickNode(bt); | |
switch (State) | |
{ | |
case BTState.Success: | |
child.ExitNode(bt); | |
return State = BTState.Failure; | |
case BTState.Failure: | |
child.ExitNode(bt); | |
return BTState.Failure; | |
case BTState.Running: | |
return BTState.Running; | |
case BTState.Abort: | |
child.Abort(bt); | |
return BTState.Abort; | |
default: | |
child.ExitNode(bt); | |
return State = BTState.Failure; | |
} | |
} | |
} | |
} |
# 创建工厂
配置规则
- 用 [number] 标识一个节点,例如:
SelectorNode[0]
- 行为节点命名规则:{name} Behavior,例如:
IdleBehavior
- 条件节命名规则:{name} Condition,例如:
FoundTargetCondition
- 单独出现的节点(后面没有箭头)表示根节点,例如:
SelectorNode[0]
- SelectorNode 配置示例(ParallelNode、SequenceNode 同理)
SelectorNode[0]->SelectorNode[1]
SelectorNode[1]->IdleBehavior[0]
- PriorityNode 包含多个过滤结构,不能作为其他节点的子节点,不需要单独声明,并且唯一,配置示例(FilterNode 仅有一个过滤结构)
PriorityNode[0]->{DeathCondition[0],DeadBehavior[0]}
PriorityNode[0]->{FoundTargetCondition[0],AttackBehavior[0]}
- InverterNode 配置示例(其他常规装饰节点同理)
InverterNode[0]->FoundTargetCondition[0]
InverterNode[1]->IdleBehavior[0]
- LimiterNode 在第一次声明时需要输入参数,格式 {5},配置示例(TimingNode 同理)
SelectorNode[0]->LimiterNode[0]{5}
LimiterNode[0]->IdleBehavior[0] 或 LimiterNode[0]{5}->IdleBehavior[0]
- 想要共用同一个节点,只需要在配置时保持节点名和标记都相同即可
创建工厂
- 使用正则表达式检查配置是否正确
- 使用反射创建对象
using Common; | |
using System; | |
using System.Collections.Generic; | |
using System.Text.RegularExpressions; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 节点创建工厂 | |
/// </summary> | |
public static class BTCreateFactory | |
{ | |
/// <summary> | |
/// 节点映射表,存储所有节点实例 | |
/// </summary> | |
private static Dictionary<string, BTNode> nodeDic; | |
/// <summary> | |
/// Type 缓存 | |
/// </summary> | |
private static Dictionary<string, Type> typeCache; | |
/// <summary> | |
/// 根节点的键值 | |
/// </summary> | |
private static string rootID; | |
/// <summary> | |
/// 优先级节点的键值 | |
/// </summary> | |
private static string priorityID; | |
static BTCreateFactory() | |
{ | |
nodeDic = new Dictionary<string, BTNode>(); | |
typeCache = new Dictionary<string, Type>(); | |
rootID = null; | |
priorityID = null; | |
} | |
/// <summary> | |
/// 构建行为树 | |
/// </summary> | |
/// <param name="config"> 配置 & lt;/param> | |
/// <param name="root"> 根节点 & lt;/param> | |
/// <param name="priorityNode"> 优先级节点 & lt;/param> | |
public static void BuildBehaviorTree(string config, out BTNode root, out BTNode priorityNode) | |
{ | |
nodeDic.Clear(); | |
typeCache.Clear(); | |
rootID = null; | |
priorityID = null; | |
ConfigReader.ReadConfig(config, ReadLine); | |
root = nodeDic[rootID]; | |
if (priorityID == null) | |
{ | |
priorityNode = new PriorityNode(); | |
} | |
else | |
{ | |
priorityNode = nodeDic[priorityID]; | |
} | |
} | |
/// <summary> | |
/// 读取一行配置 | |
/// </summary> | |
/// <param name="line"></param> | |
private static void ReadLine(string line) | |
{ | |
line = line.Trim(); | |
if (string.IsNullOrEmpty(line)) | |
{ | |
return; | |
} | |
if (line.Contains("->")) | |
{ | |
string[] parts = line.Split("->"); | |
if (parts[0].StartsWith("PriorityNode")) | |
{ | |
if (priorityID == null) | |
{ | |
priorityID = parts[0]; | |
} | |
else if (priorityID != parts[0]) | |
{ | |
throw new Exception($"多个优先级节点: {line}"); | |
} | |
} | |
ControlNodes control = CreateNode(parts[0]) as ControlNodes; | |
if (parts[1].StartsWith("PriorityNode")) | |
{ | |
throw new Exception($"优先级节点不能作为子节点: {line}"); | |
} | |
else if (parts[1].StartsWith("{")) | |
{ | |
BTFilter filter = CreateFilter(parts[1]); | |
control.AddChild(filter); | |
} | |
else | |
{ | |
BTNode node = CreateNode(parts[1]); | |
control.AddChild(node); | |
} | |
} | |
else | |
{ | |
if (rootID != null) | |
{ | |
throw new Exception($"多个根节点: {line}"); | |
} | |
rootID = line; | |
CreateNode(line); | |
} | |
} | |
/// <summary> | |
/// 获取类型 | |
/// </summary> | |
/// <param name="typeName"></param> | |
/// <returns></returns> | |
/// <exception cref="Exception"></exception> | |
private static Type GetType(string typeName) | |
{ | |
if (typeCache.ContainsKey(typeName)) | |
{ | |
return typeCache[typeName]; | |
} | |
Type type = Type.GetType(typeName); | |
if (type == null) | |
{ | |
throw new Exception($"找不到类型: {typeName}"); | |
} | |
typeCache.Add(typeName, type); | |
return type; | |
} | |
/// <summary> | |
/// 创建节点 | |
/// </summary> | |
/// <param name="token"></param> | |
/// <exception cref="Exception"></exception> | |
private static BTNode CreateNode(string token) | |
{ | |
if (nodeDic.ContainsKey(token)) | |
{ | |
return nodeDic[token]; | |
} | |
Match match; | |
Type type; | |
BTNode node = null; | |
string nodeToken; | |
if ((match = Regex.Match(token, "^([A-Za-z]+)\\[\\d+\\]$")).Success) | |
{ | |
nodeToken = match.Groups[1].Value; | |
type = GetType($"BehaviorTree.{nodeToken}"); | |
node = Activator.CreateInstance(type) as BTNode; | |
nodeDic.Add(token, node); | |
return node; | |
} | |
else if ((match = Regex.Match(token, "^([A-Za-z]+)(\\[\\d+\\])\\{([0-9]+(?:[.][0-9]+){0,1})\\}$")).Success) | |
{ | |
nodeToken = match.Groups[1].Value; | |
string tagToken = match.Groups[2].Value; | |
string parameterToken = match.Groups[3].Value; | |
type = GetType($"BehaviorTree.{nodeToken}"); | |
if (int.TryParse(parameterToken, out int i)) | |
{ | |
node = Activator.CreateInstance(type, new object[] { i }) as BTNode; | |
} | |
else if (float.TryParse(parameterToken, out float f)) | |
{ | |
node = Activator.CreateInstance(type, new object[] { f }) as BTNode; | |
} | |
nodeDic.Add(nodeToken + tagToken, node); | |
return node; | |
} | |
throw new Exception($"无效的格式: {token}"); | |
} | |
/// <summary> | |
/// 创建过滤结构 | |
/// </summary> | |
/// <param name="token"></param> | |
/// <returns></returns> | |
private static BTFilter CreateFilter(string token) | |
{ | |
Match match = Regex.Match(token, "^\\{([A-Za-z]+)\\[\\d+\\],([A-Za-z]+\\[\\d+\\](?:\\{(?:[0-9]+(?:[.][0-9]+){0,1})\\})?)\\}$"); | |
if (!match.Success) | |
{ | |
throw new Exception($"无效的格式: {token}"); | |
} | |
Type type = GetType($"BehaviorTree.{match.Groups[1].Value}"); | |
ConditionNode cond = Activator.CreateInstance(type) as ConditionNode; | |
BTNode node = CreateNode(match.Groups[2].Value); | |
return new BTFilter | |
{ | |
condition = cond, | |
child = node | |
}; | |
} | |
} | |
} |
# 行为树脚本
using Character; | |
using Common; | |
using SkillSystem; | |
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.AI; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 行为树 | |
/// </summary> | |
[RequireComponent(typeof(NavMeshAgent), typeof(CharacterStatus), typeof(CharacterSkillSystemNPC))] | |
public class BehaviorTree : MonoBehaviour | |
{ | |
// 本文使用的是手动填写配置 | |
// 想要通过配置表自动填写数据,只需要额外写一个类实现读取和配置数据,然后在 ConfigBT () 中调用即可 | |
/// <summary> | |
/// 行为树数据(黑板) | |
/// </summary> | |
public BTBlackboard blackboard; | |
/// <summary> | |
/// 导航 | |
/// </summary> | |
private NavMeshAgent navigation; | |
private void Awake() | |
{ | |
blackboard.character = this.GetComponent<CharacterStatus>(); | |
blackboard.anim = this.GetComponentInChildren<Animator>(); | |
blackboard.npcSkill = this.GetComponent<CharacterSkillSystemNPC>(); | |
navigation = GetComponent<NavMeshAgent>(); | |
blackboard.interruptions = new List<Func<bool>>(); | |
} | |
private void Start() | |
{ | |
blackboard.nodeStack = new Stack<BTNode>(); | |
SetParameters(); | |
ConfigBT(); | |
} | |
private void Update() | |
{ | |
SearchTarget(); | |
Tick(); | |
} | |
/// <summary> | |
/// 设置参数 | |
/// </summary> | |
private void SetParameters() | |
{ | |
blackboard.foundTargets = null; | |
blackboard.currentSkill = null; | |
} | |
/// <summary> | |
/// 配置行为树 | |
/// </summary> | |
private void ConfigBT() | |
{ | |
BTCreateFactory.BuildBehaviorTree( | |
blackboard.nodeAsset.config, | |
out blackboard.root, | |
out blackboard.priorityNode | |
); | |
} | |
/// <summary> | |
/// 执行节点 | |
/// </summary> | |
private void Tick() | |
{ | |
// 外部中断 | |
foreach (var item in blackboard.interruptions) | |
{ | |
if (item.Invoke()) | |
{ | |
AbortAll(); | |
return; | |
} | |
} | |
BTState state; | |
// 优先级节点 | |
state = blackboard.priorityNode.TickNode(this); | |
if (state == BTState.Success || state == BTState.Running) | |
{ | |
return; | |
} | |
// 根节点 | |
if (blackboard.root.State != BTState.Running) | |
{ | |
blackboard.root.EnterNode(this); | |
} | |
state = blackboard.root.TickNode(this); | |
if (state != BTState.Running) | |
{ | |
blackboard.root.ExitNode(this); | |
} | |
} | |
/// <summary> | |
/// 中断所有节点 | |
/// </summary> | |
public void AbortAll() | |
{ | |
BTNode node = blackboard.Current; | |
while (node != null) | |
{ | |
node.Abort(this); | |
node = blackboard.Current; | |
} | |
} | |
/// <summary> | |
/// 搜索目标 | |
/// </summary> | |
public void SearchTarget() | |
{ | |
bool ignoreWalls = false; | |
BTNode node = blackboard.Current; | |
if (node != null && node is BehaviorNode) | |
{ | |
ignoreWalls = (node as BehaviorNode).IgnoreWalls; | |
} | |
List<Transform> targets = this.transform.SelectTargets(blackboard.viewDistance, this.transform.forward, blackboard.viewAngle, blackboard.targetTags); | |
targets = targets.FindAll(t => t.GetComponent<CharacterStatus>().HP > 0); | |
if (targets.Count == 0) | |
{ | |
blackboard.foundTargets = null; | |
return; | |
} | |
if (ignoreWalls) | |
{ | |
blackboard.foundTargets = targets.ToArray(); | |
return; | |
} | |
RaycastHit hit; | |
List<Transform> validTargets = new List<Transform>(); | |
foreach (var item in targets) | |
{ | |
Physics.Raycast(this.transform.position, item.position + Vector3.up * 0.5f - this.transform.position, out hit, blackboard.viewDistance); | |
if (hit.transform != null && hit.transform.gameObject == item.gameObject) | |
{ | |
validTargets.Add(item); | |
} | |
} | |
if (validTargets.Count != 0) | |
{ | |
blackboard.foundTargets = validTargets.ToArray(); | |
} | |
else | |
{ | |
blackboard.foundTargets = null; | |
} | |
} | |
/// <summary> | |
/// 自动寻路 | |
/// </summary> | |
/// <param name="targetPosition"> 目标位置 & lt;/param> | |
/// <param name="stoppingDistance"> 停止移动的距离 & lt;/param> | |
/// <param name="speed"> 移动速度 & lt;/param> | |
public void MoveToTarget(Vector3 targetPosition, float stoppingDistance, float speed) | |
{ | |
navigation.stoppingDistance = stoppingDistance; | |
navigation.speed = speed; | |
navigation.SetDestination(targetPosition); | |
} | |
} | |
} |
# 使用方法
# 具体的条件节点
发现目标
using System; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 发现目标 | |
/// </summary> | |
public class FoundTargetCondition : ConditionNode | |
{ | |
protected override Func<BehaviorTree, bool> Condition => (bt) => | |
{ | |
return bt.blackboard.foundTargets != null && bt.blackboard.foundTargets.Length > 0; | |
}; | |
} | |
} |
丢失目标
using System; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 丢失目标 | |
/// </summary> | |
public class LostTargetCondition : ConditionNode | |
{ | |
protected override Func<BehaviorTree, bool> Condition => (bt) => | |
{ | |
return bt.blackboard.foundTargets == null || bt.blackboard.foundTargets.Length == 0; | |
}; | |
} | |
} |
死亡条件
using System; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 死亡条件 | |
/// </summary> | |
public class DeathCondition : ConditionNode | |
{ | |
protected override Func<BehaviorTree, bool> Condition => (bt) => | |
{ | |
return bt.blackboard.character.HP <= 0; | |
}; | |
} | |
} |
# 具体的行为节点
追逐行为
using UnityEngine; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 追逐行为 | |
/// </summary> | |
public class PursuitBehavior : BehaviorNode | |
{ | |
public override bool IgnoreWalls => true; | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
if (Vector3.Distance(bt.transform.position, bt.blackboard.foundTargets[0].position) <= bt.blackboard.attackNodeDistance) | |
{ | |
return BTState.Success; | |
} | |
bt.blackboard.anim.SetBool(bt.blackboard.character.PlayerAnimationParameter.Run, true); | |
bt.MoveToTarget(bt.blackboard.foundTargets[0].position, bt.blackboard.attackNodeDistance, bt.blackboard.moveSpeed); | |
return BTState.Running; | |
} | |
public override void ExitNode(BehaviorTree bt) | |
{ | |
base.ExitNode(bt); | |
bt.MoveToTarget(bt.transform.position, 1, bt.blackboard.moveSpeed); | |
} | |
} | |
} |
巡逻行为
using System; | |
using UnityEngine; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 巡逻行为 | |
/// </summary> | |
public class PatrolBehavior : BehaviorNode | |
{ | |
public override bool IgnoreWalls => false; | |
/// <summary> | |
/// 路点数组的索引 | |
/// </summary> | |
private int Index = 0; | |
public override void EnterNode(BehaviorTree bt) | |
{ | |
base.EnterNode(bt); | |
bt.blackboard.anim.SetBool(bt.blackboard.character.PlayerAnimationParameter.Idle, true); | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
switch (bt.blackboard.patrolMode) | |
{ | |
case BTPatrolModes.None: | |
return NoneMode(bt); | |
case BTPatrolModes.Once: | |
return OnceMode(bt); | |
case BTPatrolModes.Loop: | |
return LoopMode(bt); | |
case BTPatrolModes.RoundTrip: | |
return RoundTripMode(bt); | |
default: | |
return BTState.Failure; | |
} | |
} | |
public override void ExitNode(BehaviorTree bt) | |
{ | |
base.ExitNode(bt); | |
bt.MoveToTarget(bt.transform.position, 1, bt.blackboard.moveSpeed); | |
} | |
/// <summary> | |
/// 无巡逻模式 | |
/// </summary> | |
private BTState NoneMode(BehaviorTree bt) | |
{ | |
return BTState.Failure; | |
} | |
/// <summary> | |
/// 单次模式 | |
/// </summary> | |
private BTState OnceMode(BehaviorTree bt) | |
{ | |
if (Index >= bt.blackboard.wayPoints.Length) | |
{ | |
return BTState.Success; | |
} | |
if (Vector3.Distance(bt.transform.position, bt.blackboard.wayPoints[Index].position) <= bt.blackboard.patrolEffect) | |
{ | |
Index++; | |
} | |
bt.MoveToTarget(bt.blackboard.wayPoints[Index].position, 0, bt.blackboard.moveSpeed); | |
return BTState.Running; | |
} | |
/// <summary> | |
/// 循环模式 | |
/// </summary> | |
private BTState LoopMode(BehaviorTree bt) | |
{ | |
if (Vector3.Distance(bt.transform.position, bt.blackboard.wayPoints[Index].position) <= bt.blackboard.patrolEffect) | |
{ | |
Index = (Index + 1) % bt.blackboard.wayPoints.Length; | |
} | |
bt.MoveToTarget(bt.blackboard.wayPoints[Index].position, 0, bt.blackboard.moveSpeed); | |
return BTState.Running; | |
} | |
/// <summary> | |
/// 往返模式 | |
/// </summary> | |
private BTState RoundTripMode(BehaviorTree bt) | |
{ | |
if (Vector3.Distance(bt.transform.position, bt.blackboard.wayPoints[Index].position) <= bt.blackboard.patrolEffect) | |
{ | |
if (Index >= bt.blackboard.wayPoints.Length - 1) | |
{ | |
Array.Reverse(bt.blackboard.wayPoints); | |
Index++; | |
} | |
Index = (Index + 1) % bt.blackboard.wayPoints.Length; | |
} | |
bt.MoveToTarget(bt.blackboard.wayPoints[Index].position, 0, bt.blackboard.moveSpeed); | |
return BTState.Running; | |
} | |
} | |
} |
闲置行为
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 闲置行为 | |
/// </summary> | |
public class IdleBehavior : BehaviorNode | |
{ | |
public override bool IgnoreWalls => false; | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
bt.blackboard.anim.SetBool(bt.blackboard.character.PlayerAnimationParameter.Idle, true); | |
return BTState.Success; | |
} | |
} | |
} |
死亡行为
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 死亡行为 | |
/// </summary> | |
public class DeadBehavior : BehaviorNode | |
{ | |
public override bool IgnoreWalls => true; | |
public override void EnterNode(BehaviorTree bt) | |
{ | |
base.EnterNode(bt); | |
bt.blackboard.anim.SetBool(bt.blackboard.character.PlayerAnimationParameter.Die, true); | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
return BTState.Running; | |
} | |
} | |
} |
攻击行为
using UnityEngine; | |
namespace BehaviorTree | |
{ | |
/// <summary> | |
/// 攻击行为 | |
/// </summary> | |
public class AttackBehavior : BehaviorNode | |
{ | |
public override bool IgnoreWalls => true; | |
/// <summary> | |
/// 计时器 | |
/// </summary> | |
private float timer = 0; | |
/// <summary> | |
/// 用于判断攻击距离 | |
/// </summary> | |
private float judgeAttackDistance = 0; | |
/// <summary> | |
/// 已经初始化 | |
/// </summary> | |
private bool isInit = false; | |
public override void EnterNode(BehaviorTree bt) | |
{ | |
base.EnterNode(bt); | |
if (isInit == false) | |
{ | |
Init(bt); | |
} | |
if (bt.blackboard.currentSkill == null) | |
{ | |
GetSkill(bt); | |
} | |
} | |
public override BTState TickNode(BehaviorTree bt) | |
{ | |
return Attack(bt); | |
} | |
public override void ExitNode(BehaviorTree bt) | |
{ | |
base.ExitNode(bt); | |
bt.MoveToTarget(bt.transform.position, 1, bt.blackboard.moveSpeed); | |
} | |
/// <summary> | |
/// 初始化 | |
/// </summary> | |
/// <param name="bt"></param> | |
private void Init(BehaviorTree bt) | |
{ | |
timer = bt.blackboard.attackTimeInterval; | |
isInit = true; | |
} | |
/// <summary> | |
/// 获取技能 | |
/// </summary> | |
/// <param name="bt"></param> | |
private void GetSkill(BehaviorTree bt) | |
{ | |
bt.blackboard.currentSkill = bt.blackboard.npcSkill.GetRandomSkillData(); | |
if (bt.blackboard.currentSkill == null) | |
{ | |
return; | |
} | |
if (bt.blackboard.currentSkill.MoveDistance != 0) | |
{ | |
judgeAttackDistance = bt.blackboard.currentSkill.MoveDistance; | |
} | |
else | |
{ | |
judgeAttackDistance = bt.blackboard.currentSkill.AttackDistance; | |
} | |
bt.blackboard.attackNodeDistance = judgeAttackDistance; | |
} | |
/// <summary> | |
/// 攻击 | |
/// </summary> | |
/// <param name="bt"></param> | |
private BTState Attack(BehaviorTree bt) | |
{ | |
// 看向目标 | |
bt.transform.LookAt(bt.blackboard.foundTargets[0]); | |
timer += Time.deltaTime; | |
if (timer >= bt.blackboard.attackTimeInterval) | |
{ | |
if (bt.blackboard.currentSkill == null) | |
{ | |
return BTState.Failure; | |
} | |
if (Vector3.Distance(bt.transform.position, bt.blackboard.foundTargets[0].position) <= judgeAttackDistance) | |
{ | |
// 放技能 | |
bt.blackboard.npcSkill.UseSkill(bt.blackboard.currentSkill.SkillID); | |
SetPosition(bt); | |
GetSkill(bt); | |
timer = 0; | |
return BTState.Success; | |
} | |
else | |
{ | |
return BTState.Failure; | |
} | |
} | |
bt.blackboard.anim.SetBool(bt.blackboard.character.PlayerAnimationParameter.Idle, true); | |
return BTState.Running; | |
} | |
/// <summary> | |
/// 设置释放位置 | |
/// </summary> | |
/// <param name="bt"></param> | |
private void SetPosition(BehaviorTree bt) | |
{ | |
bt.blackboard.npcSkill.SetPosition(bt.transform.position, bt.transform.rotation); | |
} | |
} | |
} |
# 类图
# 测试
配置
SelectorNode[0] | |
SelectorNode[0]->FilterNode[0] | |
SelectorNode[0]->FilterNode[1] | |
SelectorNode[0]->FilterNode[2] | |
SelectorNode[0]->IdleBehavior[0] | |
PriorityNode[0]->{DeathCondition[0],DeadBehavior[0]} | |
FilterNode[0]->{FoundTargetCondition[0],AttackBehavior[0]} | |
FilterNode[1]->{FoundTargetCondition[1],PursuitBehavior[0]} | |
FilterNode[2]->{LostTargetCondition[0],PatrolBehavior[0]} |
添加组件
设置参数
结果演示