【AI】行为树(Behaviour Tree)

接上篇,AI预定义逻辑可以把条件和行为逻辑看成一个Task,一个AI有多个Task,行为树把单个Task中条件和行为、多个Task之间的关系进行细分成节点关系,通过树形结构来分层,系统每帧从树的根向下遍历,根据各节点的功能来执行子节点。

行为树主要由以下四种节点抽象而成组合节点、装饰节点、条件节点、行为节点。

组合节点(Composites)

主要包含:Sequence顺序条件,Selector选择条件,Parallel平行条件以及他们之间相互组合的条件。

②修饰节点(Decorator)

连接树叶的树枝,就是各种类型的修饰节点,这些节点决定了 AI 如何从树的顶端根据不同的情况,来沿着不同的路径来到最终的叶子这一过程。
如让子节点循环操作(LOOP)或者让子task一直运行直到其返回某个运行状态值(Util),或者将task的返回值取反(NOT)等等

③条件节点(Conditinals)

用于判断某条件是否成立。目前看来,是Behavior Designer为了贯彻职责单一的原则,将判断专门作为一个节点独立处理,比如判断某目标是否在视野内,其实在攻击的Action里面也可以写,但是这样Action就不单一了,不利于视野判断处理的复用。一般条件节点出现在Sequence控制节点中,其后紧跟条件成立后的Action节点。

④行为节点(Action)

行为节点是真正做事的节点,行为节点在树的最末端,都是叶子节点(注意叶子节点是没有子节点的),就是这些 AI 实际上去做事情的命令;

通过使用行为树内的节点之间的关联来驱动角色的行为,比直接用具体的代码告诉一个角色去做什么事情,要来得有意思得多,这也是行为树最让人兴奋的一点。这样我们只要抽象好行为,就不用去理会战斗中具体发生了什么。

修饰节点条件节点可以放在组合节点中进行处理,因此组成三个节点:

1.根节点Root

2.逻辑节点:负责子节点的执行顺序和子节点能否执行。

              Sequence          : 选择第一个成功计算的子对象作为活动子对象。

              PrioritySelector  :计算算当前活动子节点,或者第一个子节点(如果没有活动子节点)。如果通过计算,标记当前活动子节点,或者第一个子节点(如果没有可用的活动子节点),如果结果是结束,那么将活动子节点更改为下一个。

              Parallel               : 计算所有子节点,如果其中任何子节点计算失败,则当前节点失败。

3.行为节点 

 


 

案例:一个怪物在指定路径巡逻,当玩家怪物与玩家距离小于等于5米时会追逐玩家,当大于5米时会继续巡逻。

条件:大于5五米、小于等于5米

行为:巡逻、追逐玩家

 所有节点的根节点:NodeBase

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace VAE.BehaviourTree
{
    public abstract class NodeBase
    {
        public enum NodeResult              //节点返回状态 
        {
            Ended = 1,
            Running = 2,
        }


        public PreconditionBase preconditionBase;    // 用来检查节点是否可以输入
        public Database database;

        public bool activated;                                      //节点激活状态
        public float interval;                                        //节点更新间隔

        private float lastTimeEvaluated = 0;

        protected List<NodeBase> children;
        public List<NodeBase> Children { get { return this.children; } }

        public NodeBase() : this(null) { }
        public NodeBase(PreconditionBase preconditionBase)
        {
            this.preconditionBase = preconditionBase;
        }

        //激活节点
        public virtual void Active(Database database)
        {
            if (this.activated) return;

            this.database = database;

            if (this.preconditionBase != null)
            {
                this.preconditionBase.Active(database);
            }

            if (this.children != null)
            {
                foreach (var child in this.children)
                {
                    child.Active(database);
                }
            }

            this.activated = true;

        }

        //没帧进行校验
        public virtual NodeResult Update()
        {
            return NodeResult.Ended;
        }

        protected virtual bool DoEvaluate() { return true; }

        public bool Evaluate()
        {
            bool coolDownOK = this.CheckTimer();

            return this.activated && coolDownOK && (this.preconditionBase == null || preconditionBase.Check()) && this.DoEvaluate();
        }


        public virtual void Clear() { }

        public virtual void AddChild(NodeBase nodeBase)
        {
            if (this.children == null)
            {
                this.children = new List<NodeBase>();
            }
            if (nodeBase != null)
            {
                this.children.Add(nodeBase);
            }
        }

        public virtual void RemoveChild(NodeBase nodeBase)
        {
            if (this.children != null && nodeBase != null)
            {
                this.children.Remove(nodeBase);
            }
        }

        //用来处理每个节点的检查间隔
        private bool CheckTimer()
        {
            if (Time.time - this.lastTimeEvaluated > this.interval)
            {
                this.lastTimeEvaluated = Time.time;
                return true;
            }
            return false;
        }


    }
}

条件基类:PreconditionBase        条件会注册到逻辑节点进行判定当前节点是否可以进行

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace VAE.BehaviourTree
{
    public abstract class PreconditionBase : NodeBase
    {
        public PreconditionBase() : base(null) { }

        public abstract bool Check();       // 条件检测

        public override NodeResult Update()
        {
            bool success = this.Check();
            if (success)
            {
                return NodeResult.Ended;
            }
            else
            {
                return NodeResult.Running;
            }
        }

    }
}

逻辑节点:PrioritySelector

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace VAE.BehaviourTree
{
    /*
     * 优先级选择器
        选择第一个成功计算的子对象作为活动子对象。
     */
    public class PrioritySelector : NodeBase
    {
        private NodeBase activeChild;       //活动子对象

        public PrioritySelector(PreconditionBase preconditionBase = null) : base(preconditionBase)
        {

        }

        protected override bool DoEvaluate()
        {
            foreach (var child in this.children)
            {
                if (child.Evaluate())       //选择计算成功的子对象
                {
                    if (this.activeChild != null && this.activeChild != child)
                    {
                        this.activeChild.Clear();
                    }
                    this.activeChild = child;
                    return true;
                }
            }
            this.activeChild = null;
            return false;
        }

        public override NodeResult Update()
        {
            if (this.activeChild == null)
            {
                return NodeResult.Ended;
            }

            var result = this.activeChild.Update();
            if (result != NodeResult.Running)
            {
                this.activeChild.Clear();
                this.activeChild = null;
            }
            return result;
        }


    }

}

行为节点:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace VAE.BehaviourTree
{
    /*
        行为节点基类
        该节点不能进行增加和删除子节点,只用来处理行为
     */
    public class ActionBase : NodeBase
    {
        private enum ActionStatus
        {
            Ready = 1,
            Running = 2,
        }

        private ActionStatus actionStatus = ActionStatus.Ready;

        public ActionBase(PreconditionBase preconditionBase = null) : base(preconditionBase)
        {

        }

        protected virtual void Enter()
        {
            // 当行为进入时触发的函数   一般用来处理:播放动画等
        }

        protected virtual void Exit()
        {
            // 当行为退出时触发的函数

        }

        // 执行逻辑
        protected virtual NodeResult Execute()
        {
            return NodeResult.Running;
        }


        public override NodeResult Update()
        {
            NodeResult result = NodeResult.Ended;

            if (this.actionStatus == ActionStatus.Ready)
            {
                this.Enter();                                                       // 进入执行行为
                this.actionStatus = ActionStatus.Running;
            }

            if (this.actionStatus == ActionStatus.Running)
            {
                result = this.Execute();
                if (result != NodeResult.Running)       //代表执行结束
                {
                    this.Exit();
                    this.actionStatus = ActionStatus.Ready;
                }
            }
            return result;
        }


        public override void Clear()
        {
            if (this.actionStatus != ActionStatus.Ready)
            {
                this.Exit();
                this.actionStatus = ActionStatus.Ready;
            }
        }


        public override void AddChild(NodeBase nodeBase)
        {
            Debug.LogError("Action: Cannot add a node into Action.");
        }

        public override void RemoveChild(NodeBase nodeBase)
        {
            Debug.LogError("Action: Cannot remove a node into Action.");
        }


    }
}

 

用于AI行为树,各节点之间数据修改与获取的:Database

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

namespace VAE.BehaviourTree
{
    public class Database : MonoBehaviour
    {

        private List<object> _database = new List<object>();
        private List<string> _dataNames = new List<string>();

        public T GetData<T>(string dataName)
        {
            int dataId = IndexOfDataId(dataName);
            if (dataId == -1) Debug.LogError("Database: Data for " + dataName + " does not exist!");

            return (T)_database[dataId];
        }

        // Should use this function to get data!
        public T GetData<T>(int dataId)
        {
            return (T)_database[dataId];
        }

        public void SetData<T>(string dataName, T data)
        {
            int dataId = GetDataId(dataName);
            _database[dataId] = (object)data;
        }

        public void SetData<T>(int dataId, T data)
        {
            _database[dataId] = (object)data;
        }

        public int GetDataId(string dataName)
        {
            int dataId = IndexOfDataId(dataName);
            if (dataId == -1)
            {
                _dataNames.Add(dataName);
                _database.Add(null);
                dataId = _dataNames.Count - 1;
            }

            return dataId;
        }

        private int IndexOfDataId(string dataName)
        {
            for (int i = 0; i < _dataNames.Count; i++)
            {
                if (_dataNames[i].Equals(dataName)) return i;
            }

            return -1;
        }

        public bool ContainsData(string dataName)
        {
            return IndexOfDataId(dataName) != -1;
        }
    }
}

 

行为树驱动脚本:BTTreeBase     处理行为树初始化,条件判定和更新

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace VAE.BehaviourTree
{
    /*
        
     */
    public class BTTreeBase : MonoBehaviour
    {
        protected NodeBase root = null;

        [HideInInspector]
        public Database database;

        [HideInInspector]
        public bool isRunning = true;

        public const string RESET = "RESET";

        private static int resetId;

        private void Awake()
        {
            this.Init();

            //当所有 限定节点 逻辑条件 添加完毕 再进行数据激活
            this.root.Active(this.database);
        }

        private void Update()
        {
            if (!this.isRunning) return;

            if (this.database.GetData<bool>(BTTreeBase.RESET))
            {
                this.Reset();
                this.database.SetData<bool>(BTTreeBase.RESET, false);
            }

            if (this.root.Evaluate())
            {
                this.root.Update();
            }

        }

        protected virtual void Init()
        {
            this.database = this.GetComponent<Database>();
            if (this.database == null)
            {
                this.database = this.gameObject.AddComponent<Database>();
            }

            BTTreeBase.resetId = this.database.GetDataId(BTTreeBase.RESET);
            this.database.SetData<bool>(BTTreeBase.resetId, false);
        }

        protected void Reset()
        {
            this.root.Clear();
        }

    }
}

 

外部逻辑:

处理限制条件:大于5五米、小于等于5米

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using VAE.BehaviourTree;

public class Precdontion_TransformDistance : PreconditionBase
{
    public enum precdFunction
    {
        LessThan = 1,
        GreaterThan = 2,
    }

    private string minChaseDistanceStr;
    private int minChaseDistanceId;
    private Transform itemTran;                     // 目标对象
    private Transform enemyAI;

    private precdFunction func;

    public Precdontion_TransformDistance(string minChaseDistanceStr, Transform itemTran, precdFunction func)
    {
        this.minChaseDistanceStr = minChaseDistanceStr;
        this.itemTran = itemTran;
        this.func = func;
    }

    public override void Active(Database database)
    {
        base.Active(database);


        this.minChaseDistanceId = this.database.GetDataId(this.minChaseDistanceStr);

        this.enemyAI = this.database.transform;
    }


    public override bool Check()
    {
        if (itemTran == null) return false;

        Vector3 offset = itemTran.position - this.enemyAI.position;

        var minDistance = this.database.GetData<float>(this.minChaseDistanceId);

        if (this.func == precdFunction.GreaterThan)
        {
            return (offset.sqrMagnitude >= minDistance);
        }
        else
        {
            return (offset.sqrMagnitude <= minDistance);
        }

    }


}

 

行为逻辑:

巡逻:Action_Partoll

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VAE.BehaviourTree;

public class Action_Partoll : ActionBase
{
    private List<Transform> partollPos;
    private int index = 0;
    private Transform enemyAI;
    private float moveSpeed;

    public Action_Partoll(List<Transform> partollPos, float speed)
    {
        this.partollPos = partollPos;
        this.moveSpeed = speed;
    }

    public override void Active(Database database)
    {
        base.Active(database);

        this.enemyAI = this.database.transform;
    }

    protected override NodeResult Execute()
    {
        var distance = Vector3.Distance(this.enemyAI.position, this.partollPos[this.index].position);
        if (distance <= 0.1f)
        {
            this.index++;
            this.index %= this.partollPos.Count;
        }

        Vector3 direction = (this.partollPos[this.index].position - this.enemyAI.position).normalized;
        this.enemyAI.position += direction * this.moveSpeed * Time.deltaTime;

        return NodeResult.Running;
    }

}

追逐指定目标:Action_ChaseItem

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VAE.BehaviourTree;


public class Action_ChaseItem : ActionBase
{
    private Transform itemTran;      //目标

    private float moveSpeed;

    private Transform enemyAI;

    private string minChaseDistanceStr;
    private int minChaseDistanceId;

    public Action_ChaseItem(string minChaseDistanceStr, Transform itemTran, float speed)
    {
        this.itemTran = itemTran;
        this.moveSpeed = speed;
        this.minChaseDistanceStr = minChaseDistanceStr;
    }


    public override void Active(Database database)
    {
        base.Active(database);      // 基类函数一定要执行

        this.minChaseDistanceId = this.database.GetDataId(this.minChaseDistanceStr);

        this.enemyAI = this.database.transform;
    }


    protected override NodeResult Execute()
    {
        if (this.CheckArrived())
        {
            return NodeResult.Ended;
        }

        Vector3 direction = (this.itemTran.position - this.enemyAI.position).normalized;
        this.enemyAI.position += direction * this.moveSpeed * Time.deltaTime ;

        return NodeResult.Running;
    }

    //检验是否应超出追逐范围
    private bool CheckArrived()
    {
        Vector3 offset = this.itemTran.position - this.enemyAI.position;       // 怪物AI -》目的地的向量

        float tmpMinDistance = this.database.GetData<float>(this.minChaseDistanceId);

        return offset.sqrMagnitude > tmpMinDistance * tmpMinDistance;
    }

}

EnemyAI:AI初始化

using System.Collections;
using System.Collections.Generic;
using VAE.BehaviourTree;
using UnityEngine;

public class EnemyAI : BTTreeBase
{
    public Transform itemTran;
    public float speed = 3;
    public List<Transform> partollPos;
    public float partollSpeed = 4;

    public float minChaseDistance = 5;      // 最小追逐距离

    public const string MINCHASEDISTANCE = "MINCHASEDISTANCE";

    protected override void Init()
    {
        base.Init();        // 一定要调用基类函数

        this.root = new PrioritySelector();         // 创建行为树根节点

        //一个怪物在指定路径巡逻,当玩家怪物与玩家距离小于等于5米时会追逐玩家,当大于5米时会继续巡逻。这里就分为了巡逻、追逐两种状态。
        //
        this.database.SetData<float>(MINCHASEDISTANCE, this.minChaseDistance);

        Precdontion_TransformDistance precd_TransformDistance_greater = new Precdontion_TransformDistance(MINCHASEDISTANCE, itemTran, Precdontion_TransformDistance.precdFunction.GreaterThan);
        Precdontion_TransformDistance precd_TransformDistance_less = new Precdontion_TransformDistance(MINCHASEDISTANCE, itemTran, Precdontion_TransformDistance.precdFunction.LessThan);

        
      
        PrioritySelector tree2_1 = new PrioritySelector(precd_TransformDistance_greater);
        Action_Partoll action_Partoll = new Action_Partoll(this.partollPos, this.partollSpeed);
        tree2_1.AddChild(action_Partoll);

        PrioritySelector tree2_2 = new PrioritySelector(precd_TransformDistance_less);
        Action_ChaseItem action_ChaseItem = new Action_ChaseItem(MINCHASEDISTANCE, itemTran, this.speed);
        tree2_2.AddChild(action_ChaseItem);

        this.root.AddChild(tree2_1);
        this.root.AddChild(tree2_2);


    }
}

 


 像上述,条件节点,行为节点还可以继续进行细划分,分的越详细代码复用越高。比如:AI巡逻行为接着可划分为朝指定位置移动行为,这就需要新的逻辑节点来处理,从而达到复用追逐和巡逻两个地方的行为功能。

行为树缺点:为树是依赖设计者的固定架构的,很不灵活,做的选择不一定是最优选择,而且每次都要经过大量的逻辑判断,性能消耗严重。

而后另一种AI算法:GOAP(目标导向型行动计划)更好的解决问题。

 

目前对行为树理解还不太深刻,后续会继续增加。。。

 

主要参考:

https://zhuanlan.zhihu.com/p/94850561

https://www.jianshu.com/p/23f79a365c10

 

 

 

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值