# 观前提醒

项目会在 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: 巡逻

# 重要结构 - 栈

树的遍历经常需要使用栈这个结构,行为树也是如此

行为树的执行过程在结构上非常接近树的先根遍历

  • 因为行为树每一帧的执行流程,大致是:
    1. 先访问当前节点(父节点)
    2. 然后依次遍历并执行它的每一个子节点(从左到右)
    3. 遇到某些特殊控制节点(如 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);
        }
    }
}

# 类图

img

# 测试

配置

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]}

添加组件
img

设置参数
img

结果演示
img

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Maikire 微信支付

微信支付

Maikire 支付宝

支付宝

Maikire 贝宝

贝宝