精华内容
下载资源
问答
  • 行为树代码

    2019-04-27 16:30:39
    代码是一个完整的例子,行为树的具体实现,可解码运行
  • C#/Unity 行为树 简单实现

    千次阅读 2018-07-25 21:02:08
     行为树的概念, 各种 Unity 插件 都没时间介绍。 代码是看了 Unity的 2D Game Kit , 提炼出来,测试! 对于学习和理解行为树会很有帮助!!! 纯代码, 没有Unity插件的节点编辑导出配置等内容。  using ...

    孙广东   2018.7.25    

              行为树的概念, 各种 Unity 插件 都没时间介绍。 代码是看了 Unity的 2D Game Kit , 提炼出来,测试!   对于学习和理解行为树会很有帮助!!!   纯代码, 没有Unity插件的节点编辑导出配置等内容。 

    using BTAI;
    using UnityEngine;
    
    public class TestBT : MonoBehaviour, BTAI.IBTDebugable
    {
        Root aiRoot = BT.Root();
    
    
        private void OnEnable()
        {
            aiRoot.OpenBranch(
                    BT.If(TestVisibleTarget).OpenBranch(
                    BT.Call(Aim),
                    BT.Call(Shoot)
                     ),
                    BT.Sequence().OpenBranch(
                    BT.Call(Walk),
                    BT.Wait(5.0f),
                    BT.Call(Turn),
                    BT.Wait(1.0f),
                    BT.Call(Turn)
                 )
            );
        }
    
        private void Turn()
        {
            Debug.Log("执行了 Turn");
        }
    
        private void Walk()
        {
            Debug.Log("执行了 Walk");
        }
    
        private void Shoot()
        {
            Debug.Log("执行了 Shoot");
        }
    
        private void Aim()
        {
            Debug.Log("执行了 Aim");
        }
    
        private bool TestVisibleTarget()
        {
            var isSuccess = UnityEngine.Random.Range(0, 2) == 1;
            Debug.Log("执行了 TestVisibleTarget    Result:" + isSuccess);
    
            return isSuccess;
        }
    
        private void Update()
        {
            aiRoot.Tick();
        }
    
        public Root GetAIRoot()
        {
            return aiRoot;
        }
    }
    

    using System.Collections.Generic;
    using UnityEngine;
    
    /// <summary>
    /// 这只是脚本系统
    /// 行为树会从Root节点开始遍历子节点。Update中执行
    /// 每个节点都有相关的操作,但是基本上就是返回三种状态
    /// ● Success​: 节点成功完成任务
    /// ● Failure​: 节点未通过任务
    /// ● Continue​:节点尚未完成任务。
    /// 但是每个节点的父节点对子节点的结果处理方式还不同。  例如
    /// ● Test 节点: 测试节点将调用其子节点并在测试为真时返回子节点状态,如果测试为假,则返回Failure而不调用其子节点。
    /// 行为树的一种构造方式如下:
    /// Root aiRoot = BT.Root(); 
    /// aiRoot.Do(  
    /// BT.If(TestVisibleTarget).Do(
    ///  BT.Call(Aim),
    ///  BT.Call(Shoot)
    ///  ),
    ///  BT.Sequence().Do(
    ///  BT.Call(Walk),
    ///  BT.Wait(5.0f),
    ///  BT.Call(Turn),
    ///  BT.Wait(1.0f),
    ///  BT.Call(Turn)
    ///  )
    /// ); 
    ///然后在Update中 调用   ​aiRoot.Tick()​ 。  刚刚构造的行为树是怎么样的检查过程呢?  
    ///1、首先检查TestVisibleTarget是否返回Ture,如果是继续执行子节点执行Aim函数和Shoot函数
    ///2、TestVisibleTarget是否返回false,if节点将返回Failure, 然后Root 将转向下一个子节点。这是个Sequence节点,它从执行第一个子节点开始。
    ///   1)将调用Walk函数,直接返回 Success,以便Sequence将下一个子节点激活并执行它。
    ///   2)执行Wait 节点,只是要等待5秒,还是第一次调用,所以肯定返回Running状态, 当Sequence从子节点上得到Running状态时,不会更改激活的子节点索引,下次Update的时候还是从这个节点开始执行
    ///3、Update的执行,当Wait节点等待的时间到了的时候,将会返回Success, 以便序列将转到下一个孩子。
    ///脚本中的Node列表 
    /// Sequence:
    //一个接一个地执行子节点。如果子节点返回:
    //●Success:Sequence将选择下一帧的下一个孩子开始。
    //●Failure:Sequence将返回到下一帧的第一个子节点(从头开始)。
    //●Continue:Sequence将在下一帧再次调用该节点。
    //RandomSequence:
    // 每次调用时,从子列表中执行一个随机子节点。您可以在构造函数中指定要应用于每个子项的权重列表作为int数组,以使某些子项更有可能被选中。
    //Selector :
    //按顺序执行所有子项,直到一个返回Success,然后退出而不执行其余子节点。如果没有返回Success,则此节点将返回Failure。
    
    // Condition :
    // 如果给定函数返回true,则此节点返回Success;如果为false,则返回Failure。
    // 与其他依赖于子节点结果的节点链接时很有用(例如,Sequence,Selector等)
    // If :
    //调用给定的函数。
    // ●如果返回true,则调用当前活动的子级并返回其状态。
    // ●否则,它将在不调用其子项的情况下返回Failure
    // While:
    //只要给定函数返回true,就返回Continue(因此,下一帧将再次从该节点开始,而不会评估所有先前的节点)。
    //子节点们将陆续被执行。
    //当函数返回false并且循环中断时,将返回Failure。
    // Call 
    //调用给定的函数,它将始终返回Success。是动作节点!
    //Repeat 
    //将连续执行给定次数的所有子节点。
    //始终返回Continue,直到达到计数,并返回Success。
    //Wait
    //将返回Continue,直到达到给定时间(首次调用时开始),然后返回Success。
    //Trigger 
    //允许在给定的动画师animator中设置Trigger参数(如果最后一个参数设置为false,则取消设置触发器)。始终返回成功。
    //SetBool
    //允许在给定的animator中设置布尔参数的值。始终返回成功
    //SetActive 
    //设置给定GameObject的活动/非活动状态。始终返回成功。
    /// </summary>
    namespace BTAI
    {
        public enum BTState
        {
            Failure,
            Success,
            Continue,
            Abort
        }
    
        /// <summary>
        /// 节点 对象工厂
        /// </summary>
        public static class BT
        {
            public static Root Root() { return new Root(); }
            public static Sequence Sequence() { return new Sequence(); }
            public static Selector Selector(bool shuffle = false) { return new Selector(shuffle); }
            public static Action RunCoroutine(System.Func<IEnumerator<BTState>> coroutine) { return new Action(coroutine); }
            public static Action Call(System.Action fn) { return new Action(fn); }
            public static ConditionalBranch If(System.Func<bool> fn) { return new ConditionalBranch(fn); }
            public static While While(System.Func<bool> fn) { return new While(fn); }
            public static Condition Condition(System.Func<bool> fn) { return new Condition(fn); }
            public static Repeat Repeat(int count) { return new Repeat(count); }
            public static Wait Wait(float seconds) { return new Wait(seconds); }
            public static Trigger Trigger(Animator animator, string name, bool set = true) { return new Trigger(animator, name, set); }
            public static WaitForAnimatorState WaitForAnimatorState(Animator animator, string name, int layer = 0) { return new WaitForAnimatorState(animator, name, layer); }
            public static SetBool SetBool(Animator animator, string name, bool value) { return new SetBool(animator, name, value); }
            public static SetActive SetActive(GameObject gameObject, bool active) { return new SetActive(gameObject, active); }
            public static WaitForAnimatorSignal WaitForAnimatorSignal(Animator animator, string name, string state, int layer = 0) { return new WaitForAnimatorSignal(animator, name, state, layer); }
            public static Terminate Terminate() { return new Terminate(); }
            public static Log Log(string msg) { return new Log(msg); }
            public static RandomSequence RandomSequence(int[] weights = null) { return new BTAI.RandomSequence(weights); }
    
        }
    
        /// <summary>
        /// 节点抽象类
        /// </summary>
        public abstract class BTNode
        {
            public abstract BTState Tick();
        }
    
        /// <summary>
        /// 包含子节点的组合 节点基类
        /// </summary>
        public abstract class Branch : BTNode
        {
            protected int activeChild;
            protected List<BTNode> children = new List<BTNode>();
            public virtual Branch OpenBranch(params BTNode[] children)
            {
                for (var i = 0; i < children.Length; i++)
                    this.children.Add(children[i]);
                return this;
            }
    
            public List<BTNode> Children()
            {
                return children;
            }
    
            public int ActiveChild()
            {
                return activeChild;
            }
    
            public virtual void ResetChildren()
            {
                activeChild = 0;
                for (var i = 0; i < children.Count; i++)
                {
                    Branch b = children[i] as Branch;
                    if (b != null)
                    {
                        b.ResetChildren();
                    }
                }
            }
        }
    
        /// <summary>
        /// 装饰节点    只包含一个子节点,用于某种方式改变这个节点的行为
        /// 比如过滤器(用于决定是否允许子节点运行的,如:Until Success, Until Fail等),这种节点的子节点应该是条件节点,条件节点一直检测“视线中是否有敌人”,知道发现敌人为止。
        /// 或者 Limit 节点,用于指定某个子节点的最大运行次数
        /// 或者 Timer节点,设置了一个计时器,不会立即执行子节点,而是等一段时间,时间到了开始执行子节点
        /// 或者 TimerLimit节点,用于指定某个子节点的最长运行时间。
        /// 或者 用于产生某个返回状态,
        /// </summary>
        public abstract class Decorator : BTNode
        {
            protected BTNode child;
            public Decorator Do(BTNode child)
            {
                this.child = child;
                return this;
            }
        }
    
        /// <summary>
        /// 顺序节点 (从左到右依次执行所有子节点,只要子节点返回Success就继续执行后续子节点,直到遇到Failure或者Runing, 
        /// 停止后续执行,并把这个节点返回给父节点,只有它的所有子节点都是Success他才会向父节点返回Success)
        /// </summary>
        public class Sequence : Branch
        {
            public override BTState Tick()
            {
                var childState = children[activeChild].Tick();
                switch (childState)
                {
                    case BTState.Success:
                        activeChild++;
                        if (activeChild == children.Count)
                        {
                            activeChild = 0;
                            return BTState.Success;
                        }
                        else
                            return BTState.Continue;
                    case BTState.Failure:
                        activeChild = 0;
                        return BTState.Failure;
                    case BTState.Continue:
                        return BTState.Continue;
                    case BTState.Abort:
                        activeChild = 0;
                        return BTState.Abort;
                }
                throw new System.Exception("This should never happen, but clearly it has.");
            }
        }
    
        /// <summary>
        /// 选择节点从左到右依次执行所有子节点 ,只要遇到failure就继续执行后续子节点,直到遇到一个节点返回Success或Running为止。向父节点返回Success或Running
        /// 所有子节点都是Fail, 那么向父节点凡湖Fail
        /// 选择节点 用来在可能的行为集合中选择第一个成功的。 比如一个试图躲避枪击的AI角色, 它可以通过寻找隐蔽点, 或离开危险区域, 或寻找援助等多种方式实现目标。
        /// 利用选择节点,他会尝试寻找Cover,失败后在试图逃离危险区域。
        /// </summary>
        public class Selector : Branch
        {
            public Selector(bool shuffle)
            {
                if (shuffle)
                {
                    var n = children.Count;
                    while (n > 1)
                    {
                        n--;
                        var k = Mathf.FloorToInt(Random.value * (n + 1));
                        var value = children[k];
                        children[k] = children[n];
                        children[n] = value;
                    }
                }
            }
    
            public override BTState Tick()
            {
                var childState = children[activeChild].Tick();
                switch (childState)
                {
                    case BTState.Success:
                        activeChild = 0;
                        return BTState.Success;
                    case BTState.Failure:
                        activeChild++;
                        if (activeChild == children.Count)
                        {
                            activeChild = 0;
                            return BTState.Failure;
                        }
                        else
                            return BTState.Continue;
                    case BTState.Continue:
                        return BTState.Continue;
                    case BTState.Abort:
                        activeChild = 0;
                        return BTState.Abort;
                }
                throw new System.Exception("This should never happen, but clearly it has.");
            }
        }
    
        /// <summary>
        /// 行为节点  调用方法,或运行协程。完成实际工作, 例如播放动画,让角色移动位置,感知敌人,更换武器,播放声音,增加生命值等。
        /// </summary>
        public class Action : BTNode
        {
            System.Action fn;
            System.Func<IEnumerator<BTState>> coroutineFactory;
            IEnumerator<BTState> coroutine;
            public Action(System.Action fn)
            {
                this.fn = fn;
            }
            public Action(System.Func<IEnumerator<BTState>> coroutineFactory)
            {
                this.coroutineFactory = coroutineFactory;
            }
            public override BTState Tick()
            {
                if (fn != null)
                {
                    fn();
                    return BTState.Success;
                }
                else
                {
                    if (coroutine == null)
                        coroutine = coroutineFactory();
                    if (!coroutine.MoveNext())
                    {
                        coroutine = null;
                        return BTState.Success;
                    }
                    var result = coroutine.Current;
                    if (result == BTState.Continue)
                        return BTState.Continue;
                    else
                    {
                        coroutine = null;
                        return result;
                    }
                }
            }
    
            public override string ToString()
            {
                return "Action : " + fn.Method.ToString();
            }
        }
    
        /// <summary>
        /// 条件节点   调用方法,如果方法返回true则返回成功,否则返回失败。
        /// 用来测试当前是否满足某些性质或条件,例如“玩家是否在20米之内?”“是否能看到玩家?”“生命值是否大于50?”“弹药是否足够?”等
        /// </summary>
        public class Condition : BTNode
        {
            public System.Func<bool> fn;
    
            public Condition(System.Func<bool> fn)
            {
                this.fn = fn;
            }
            public override BTState Tick()
            {
                return fn() ? BTState.Success : BTState.Failure;
            }
    
            public override string ToString()
            {
                return "Condition : " + fn.Method.ToString();
            }
        }
    
        /// <summary>
        /// 当方法为True的时候 尝试执行当前  子节点
        /// </summary>
        public class ConditionalBranch : Block
        {
            public System.Func<bool> fn;
            bool tested = false;
            public ConditionalBranch(System.Func<bool> fn)
            {
                this.fn = fn;
            }
            public override BTState Tick()
            {
                if (!tested)
                {
                    tested = fn();
                }
                if (tested)
                {
                    // 当前子节点执行完就进入下一个节点(超上限就返回到第一个)
                    var result = base.Tick();
                    // 没执行完
                    if (result == BTState.Continue)
                        return BTState.Continue;
                    else
                    {
                        tested = false;
                        // 最后一个子节点执行完,才会为Ture
                        return result;
                    }
                }
                else
                {
                    return BTState.Failure;
                }
            }
    
            public override string ToString()
            {
                return "ConditionalBranch : " + fn.Method.ToString();
            }
        }
    
        /// <summary>
        /// While节点   只要方法  返回True 就执行所有子节点, 否则返回 Failure
        /// </summary>
        public class While : Block
        {
            public System.Func<bool> fn;
    
            public While(System.Func<bool> fn)
            {
                this.fn = fn;
            }
    
            public override BTState Tick()
            {
                if (fn())
                    base.Tick();
                else
                {
                    //if we exit the loop
                    ResetChildren();
                    return BTState.Failure;
                }
    
                return BTState.Continue;
            }
    
            public override string ToString()
            {
                return "While : " + fn.Method.ToString();
            }
        }
    
        /// <summary>
        /// 阻塞节点  如果当前子节点是Continue 说明没有执行完,阻塞着,执行完之后在继续它后面的兄弟节点 不管成功失败。
        /// 如果当前结点是最后一个节点并执行完毕,说明成功!否则就是处于Continue状态。 
        /// 几个基本上是抽象节点, 像是让所有子节点都执行一遍, 当前子节点执行完就进入下一个节点(超上限就返回到第一个)
        /// </summary>
        public abstract class Block : Branch
        {
            public override BTState Tick()
            {
                switch (children[activeChild].Tick())
                {
                    case BTState.Continue:
                        return BTState.Continue;
                    default:
                        activeChild++;
                        if (activeChild == children.Count)
                        {
                            activeChild = 0;
                            return BTState.Success;
                        }
                        return BTState.Continue;
                }
            }
        }
    
        public class Root : Block
        {
            public bool isTerminated = false;
    
            public override BTState Tick()
            {
                if (isTerminated) return BTState.Abort;
                while (true)
                {
                    switch (children[activeChild].Tick())
                    {
                        case BTState.Continue:
                            return BTState.Continue;
                        case BTState.Abort:
                            isTerminated = true;
                            return BTState.Abort;
                        default:
                            activeChild++;
                            if (activeChild == children.Count)
                            {
                                activeChild = 0;
                                return BTState.Success;
                            }
                            continue;
                    }
                }
            }
        }
    
        /// <summary>
        /// 多次运行子节点(一个子节点执行一次就算一次)
        /// </summary>
        public class Repeat : Block
        {
            public int count = 1;
            int currentCount = 0;
            public Repeat(int count)
            {
                this.count = count;
            }
            public override BTState Tick()
            {
                if (count > 0 && currentCount < count)
                {
                    var result = base.Tick();
                    switch (result)
                    {
                        case BTState.Continue:
                            return BTState.Continue;
                        default:
                            currentCount++;
                            if (currentCount == count)
                            {
                                currentCount = 0;
                                return BTState.Success;
                            }
                            return BTState.Continue;
                    }
                }
                return BTState.Success;
            }
    
            public override string ToString()
            {
                return "Repeat Until : " + currentCount + " / " + count;
            }
        }
    
    
        /// <summary>
        /// 随机的顺序  执行子节点 
        /// </summary>
        public class RandomSequence : Block
        {
            int[] m_Weight = null;
            int[] m_AddedWeight = null;
    
            /// <summary>
            /// 每次再次触发时,将选择一个随机子节点
            /// </summary>
            /// <param name="weight">保留null,以便所有子节点具有相同的权重。
            /// 如果权重低于子节点, 则后续子节点的权重都为1</param>
            public RandomSequence(int[] weight = null)
            {
                activeChild = -1;
    
                m_Weight = weight;
            }
    
            public override Branch OpenBranch(params BTNode[] children)
            {
                m_AddedWeight = new int[children.Length];
    
                for (int i = 0; i < children.Length; ++i)
                {
                    int weight = 0;
                    int previousWeight = 0;
    
                    if (m_Weight == null || m_Weight.Length <= i)
                    {//如果没有那个权重, 就将权重 设置为1
                        weight = 1;
                    }
                    else
                        weight = m_Weight[i];
    
                    if (i > 0)
                        previousWeight = m_AddedWeight[i - 1];
    
                    m_AddedWeight[i] = weight + previousWeight;
                }
    
                return base.OpenBranch(children);
            }
    
            public override BTState Tick()
            {
                if (activeChild == -1)
                    PickNewChild();
    
                var result = children[activeChild].Tick();
    
                switch (result)
                {
                    case BTState.Continue:
                        return BTState.Continue;
                    default:
                        PickNewChild();
                        return result;
                }
            }
    
            void PickNewChild()
            {
                int choice = Random.Range(0, m_AddedWeight[m_AddedWeight.Length - 1]);
    
                for (int i = 0; i < m_AddedWeight.Length; ++i)
                {
                    if (choice - m_AddedWeight[i] <= 0)
                    {
                        activeChild = i;
                        break;
                    }
                }
            }
    
            public override string ToString()
            {
                return "Random Sequence : " + activeChild + "/" + children.Count;
            }
        }
    
    
        /// <summary>
        /// 暂停执行几秒钟。
        /// </summary>
        public class Wait : BTNode
        {
            public float seconds = 0;
            float future = -1;
            public Wait(float seconds)
            {
                this.seconds = seconds;
            }
    
            public override BTState Tick()
            {
                if (future < 0)
                    future = Time.time + seconds;
    
                if (Time.time >= future)
                {
                    future = -1;
                    return BTState.Success;
                }
                else
                    return BTState.Continue;
            }
    
            public override string ToString()
            {
                return "Wait : " + (future - Time.time) + " / " + seconds;
            }
        }
    
        /// <summary>
        /// 设置动画  trigger 参数
        /// </summary>
        public class Trigger : BTNode
        {
            Animator animator;
            int id;
            string triggerName;
            bool set = true;
    
            //如果 set == false, 则重置trigger而不是设置它。
            public Trigger(Animator animator, string name, bool set = true)
            {
                this.id = Animator.StringToHash(name);
                this.animator = animator;
                this.triggerName = name;
                this.set = set;
            }
    
            public override BTState Tick()
            {
                if (set)
                    animator.SetTrigger(id);
                else
                    animator.ResetTrigger(id);
    
                return BTState.Success;
            }
    
            public override string ToString()
            {
                return "Trigger : " + triggerName;
            }
        }
    
        /// <summary>
        /// 设置动画 boolean 参数
        /// </summary>
        public class SetBool : BTNode
        {
            Animator animator;
            int id;
            bool value;
            string triggerName;
    
            public SetBool(Animator animator, string name, bool value)
            {
                this.id = Animator.StringToHash(name);
                this.animator = animator;
                this.value = value;
                this.triggerName = name;
            }
    
            public override BTState Tick()
            {
                animator.SetBool(id, value);
                return BTState.Success;
            }
    
            public override string ToString()
            {
                return "SetBool : " + triggerName + " = " + value.ToString();
            }
        }
    
        /// <summary>
        /// 等待animator达到一个状态。
        /// </summary>
        public class WaitForAnimatorState : BTNode
        {
            Animator animator;
            int id;
            int layer;
            string stateName;
    
            public WaitForAnimatorState(Animator animator, string name, int layer = 0)
            {
                this.id = Animator.StringToHash(name);
                if (!animator.HasState(layer, this.id))
                {
                    Debug.LogError("The animator does not have state: " + name);
                }
                this.animator = animator;
                this.layer = layer;
                this.stateName = name;
            }
    
            public override BTState Tick()
            {
                var state = animator.GetCurrentAnimatorStateInfo(layer);
                if (state.fullPathHash == this.id || state.shortNameHash == this.id)
                    return BTState.Success;
                return BTState.Continue;
            }
    
            public override string ToString()
            {
                return "Wait For State : " + stateName;
            }
        }
    
        /// <summary>
        /// 设置 GameObject 的激活状态
        /// </summary>
        public class SetActive : BTNode
        {
    
            GameObject gameObject;
            bool active;
    
            public SetActive(GameObject gameObject, bool active)
            {
                this.gameObject = gameObject;
                this.active = active;
            }
    
            public override BTState Tick()
            {
                gameObject.SetActive(this.active);
                return BTState.Success;
            }
    
            public override string ToString()
            {
                return "Set Active : " + gameObject.name + " = " + active;
            }
        }
    
        /// <summary>
        /// 等待animator从SendSignal状态机行为 接收信号。   SendSignal : StateMachineBehaviour
        /// </summary>
        public class WaitForAnimatorSignal : BTNode
        {
            // 进入或退出动画都为 False, 只有执行中为True
            internal bool isSet = false;
            string name;
            int id;
    
            public WaitForAnimatorSignal(Animator animator, string name, string state, int layer = 0)
            {
                this.name = name;
                this.id = Animator.StringToHash(name);
                if (!animator.HasState(layer, this.id))
                {
                    Debug.LogError("The animator does not have state: " + name);
                }
                else
                {
                    SendSignal.Register(animator, name, this);
                }
            }
    
            public override BTState Tick()
            {
                if (!isSet)
                    return BTState.Continue;
                else
                {
                    isSet = false;
                    return BTState.Success;
                }
    
            }
    
            public override string ToString()
            {
                return "Wait For Animator Signal : " + name;
            }
        }
    
        /// <summary>
        /// 终止节点  切换到中止 状态
        /// </summary>
        public class Terminate : BTNode
        {
    
            public override BTState Tick()
            {
                return BTState.Abort;
            }
    
        }
    
        /// <summary>
        /// Log  输出Log 的节点
        /// </summary>
        public class Log : BTNode
        {
            string msg;
    
            public Log(string msg)
            {
                this.msg = msg;
            }
    
            public override BTState Tick()
            {
                Debug.Log(msg);
                return BTState.Success;
            }
        }
    
    }
    
    #if UNITY_EDITOR
    namespace BTAI
    {
        public interface IBTDebugable
        {
            Root GetAIRoot();
        }
    }
    #endif

     

     

    using BTAI;
    using System.Collections.Generic;
    using UnityEditor;
    using UnityEngine;
    
    namespace Gamekit2D
    {
    
        /// <summary>
        /// 运行是查看  行为树中所有节点的状态 
        /// </summary>
        public class BTDebug : EditorWindow
        {
            protected BTAI.Root _currentRoot = null;
    
    
            [MenuItem("Kit Tools/Behaviour Tree Debug")]
            static void OpenWindow()
            {
                BTDebug btdebug = GetWindow<BTDebug>();
                btdebug.Show();
            }
    
            private void OnGUI()
            {
                if (!Application.isPlaying)
                {
                    EditorGUILayout.HelpBox("Only work during play mode.", MessageType.Info);
                }
                else
                {
                    if (_currentRoot == null)
                        FindRoot();
                    else
                    {
                        RecursiveTreeParsing(_currentRoot, 0, true);
                    }
                }
            }
    
            void Update()
            {
                Repaint();
            }
    
            void RecursiveTreeParsing(Branch branch, int indent, bool parentIsActive)
            {
                List<BTNode> nodes = branch.Children();
    
                for (int i = 0; i < nodes.Count; ++i)
                {
                    EditorGUI.indentLevel = indent;
    
                    bool isActiveChild = branch.ActiveChild() == i;
                    GUI.color = (isActiveChild && parentIsActive) ? Color.green : Color.white;
                    EditorGUILayout.LabelField(nodes[i].ToString());
    
                    if (nodes[i] is Branch)
                        RecursiveTreeParsing(nodes[i] as Branch, indent + 1, isActiveChild);
                }
            }
    
            void FindRoot()
            {
                if (Selection.activeGameObject == null)
                {
                    _currentRoot = null;
                    return;
                }
    
                IBTDebugable debugable = Selection.activeGameObject.GetComponentInChildren<IBTDebugable>();
    
                if (debugable != null)
                {
                    _currentRoot = debugable.GetAIRoot();
                }
            }
    
        }
    }

               就是在  菜单    “Kit Tools/Behaviour Tree Debug" 可以查看     TestBT.cs 对象所在行为树

     

     

    展开全文
  • AI之用行为树实现逻辑

    千次阅读 2017-03-03 12:32:12
    行为树是一种在游戏中常用的实现AI的方式,通过行为树可行图形化实现常用的程序结构。一 行为树节点类型每次执行AI时,从根节点(root)或running节点开始遍历,父节点执行子节点,子节点执行完后将结果返回父节点,父...

    行为树是一种在游戏中常用的实现AI的方式,通过行为树可行图形化实现常用的程序结构。

    一 行为树节点类型

    每次执行AI时,从根节点(root)或running节点开始遍历,父节点执行子节点,子节点执行完后将结果返回父节点,父节点根据子节点的结果来决定接下来怎么做。行为树的执行过程就是查找要执行的叶子节点的过程,查找的依据就是先执行的叶子节点的结果。通常每个节点有三个状态:成功(success),失败(failure),执行中(running)。行为树的遍历实际上是一次函数调用,立即完成的,但是动作节点的执行确实一个过程,不能立即完成。这就导致动作节点在为完成之前会被多次执行,因此引入了running状态,用于表示节点正在执行,每次执行时,跳过之前已经执行完毕的节点,从上次未执行完毕的节点开始执行。但同时需要注意的是,running状态下AI不会遍历行为树,不能影响环境的变化,running节点必须是一个不可打断动作节点。

    1 顺序节点(sequence)

    组合节点(Composite),从左往右依次执行,类似逻辑与。
    1.子节点返回failure,则停止执行,返回failure,下次从根节点开始执行;
    2.子节点返回running,则停止执行,返回running,下次从running节点开始执行;
    3.子节点返回success,则执行下一个节点,所有节点都返回success,则返回success,下次从根节点开始执行。

    其伪代码流程如下:

    def sequence_node(owner, node_name):
        run_index = get_run_index(node_name)
        while run_index < len(child_list):
            res = child_list[run_index](owner)
            if res == FAILURE:
                set_run_index(node_name, 0)
                return res
            elif res == RUNNING:
                set_run_index(node_name, run_index)
                return res
            else:
                run_index += 1
        set_run_index(node_name, 0)
        return SUCCESS
    

    2 选择节点(selector)

    组合节点(Composite),从左往右依次执行,类似逻辑与。
    1.子节点返回success,则停止执行,返回success,下次从根节点开始执行;
    2.子节点返回running,则停止执行,返回running,下次从running节点开始执行;
    3.子节点返回failure,则执行下一个节点,所有节点都返回failure,则返回failure,下次从根节点开始执行。

    其伪代码流程如下:

    def selector_node(owner, node_name):
        run_index = get_run_index(node_name)
        while run_index < len(child_list):
            res = child_list[run_index](owner)
            if res == SUCCESS:
                set_run_index(node_name, 0)
            elif res == RUNNING:
                set_run_index(node_name, run_index)
            else:
                run_index += 1
        set_run_index(node_name, 0)
        return FAILURE
    

    3 并行节点(parallel)

    组合节点(Composite),会依次执行其全部子节点,然后根据所有子节点的返回值,指定该节点的返回值。
    其伪代码流程如下:

    def parallel(owner, node_name):
        run_index_list = get_run_index(node_name)
        if len(run_index_list) > 0:
            for index in run_index_list:
                state_dic[index] = child_list[index](owner)
        else:
            run_index = 0
            while run_index < len(child_list):
                state_dic[run_index] = child_list[run_index](owner)
                run_index += 1
        if state_dic.count(SUCCESS) >= MIN_SUCCESS:
            return SUCCESS
        elif state_dic.count(FAILURE) >= MIN_FAILURE:
            return FAILURE
        else:
            return RUNNING
    

    4 概率节点(probability)

    组合节点,根据填写的概率权重,随机执行其一个子节点。

    其伪代码流程如下:

    def probability(owner, node_name):
        run_index = get_run_index(node_name)
        if get_exe_state(node_name) == STATE_NO_RUNNING:
            weight = random.uniform(0, total_weight)
            run_index = 0
            for i, w in enumerate(weights):
                if weight < w:
                    run_index = i
                    break
                else:
                    weight -= w
        res = child_list[run_index](owner)
        if res == RUNNING:
            set_run_index(node_name, run_index)
            set_exe_state(node_name, STATE_RUNNING)
        else:
            set_run_index(node_name, 0)
            set_exe_state(node_name, STATE_NO_RUNNING)
        return res
    

    5 装饰节点(decorator)

    中间节点,对子节点的返回值做相应的装饰,因此一定会有一个子节点。

    6 条件节点(condition)

    叶子节点(Leaf)
    条件成立返回success,失败返回failure。

    7 动作节点(acation)

    叶子节点(Leaf)
    返回success

    组合节点都有行为树的解析器实现,使用者需要自己实现的是叶子节点。

    二 实现逻辑结构

    下面使用行为树节来实现一个怪物的AI,来演示各逻辑结构如何实现。

    1 顺序

    顺序逻辑结构推挤用顺序节点实现,每个子节点都是动作节点。
    假设,要实现的AI,先查找目标,然后攻击。

    这里写图片描述

    2 选择

    if结构:
    假设,要实现的AI,如果发现目标,则进行攻击。

    这里写图片描述

    if-else结构:
    例如,要实现的AI,如果发现目标,则进行攻击,否则巡逻。

    这里写图片描述

    if-elseif结构:
    例如,要实现的AI,如果发现目标,则进行攻击,如果发现掉落装备,则捡装备,否则巡逻。

    这里写图片描述

    3 循环

    例如,要实现的AI,如果发现目标,则进行攻击,如果发现掉落装备,则捡装备,否则巡逻。
    捡装备的过程是,移动到装备所在位置,然后捡装备。

    这里写图片描述

    在移动到装备位置的过程中,此节点返回running,直达移动到该点,才返回success。在此过程中,循环的执行该节点,判断是否移动到了指定位置。

    展开全文
  •  由于项目的需要,所以实现了一个非常简单的行为树,来应对我们的需求。之所以说简单,是因为我并没有实现很多控制节点,而只是实现了基础的业务的三个节点而已。至于其他的你觉得有用的控制节点,可以自己修改...
  • 行为树JavaScript实现。 它们对于实现AI很有用。 如果您需要有关行为树的更多信息,请查看 , 不错的。 产品特点 需要的:序列,选择器,任务 扩展:装饰器 安装 如果使用npm: npm install behaviortree 或使用...
  • 行为树 Behavior Tree C#实现

    千次阅读 2018-01-18 16:38:50
    行为树 Behavior Tree C#实现 前篇介绍了 行为树 Behavior Tree 的原理。 本篇通过前篇原理完成 Behavior Tree 的实现。 1、节点类型枚举 NodeType namespace BehaviorTree { /// /// 行为树节点类型 /// ...

    行为树 Behavior Tree C#实现 二

    前篇介绍了 行为树 Behavior Tree 的原理。
    本篇通过前篇原理完成 Behavior Tree 的实现。

    1、节点类型枚举

    namespace BehaviorTree
    {
        /// <summary>
        /// 行为树节点类型
        /// </summary>
        public enum NODE_TYPE
        {
            /// <summary>
            /// 选择节点
            /// </summary>
            [EnumAttirbute("选择节点")]
            SELECT = 0,
    
            /// <summary>
            /// 顺序节点
            /// </summary>
            [EnumAttirbute("顺序节点")]
            SEQUENCE = 1,
    
            /// <summary>
            /// 随机节点
            /// </summary>
            [EnumAttirbute("随机节点")]
            RANDOM = 2,
    
            /// <summary>
            /// 随机顺序节点
            /// </summary>
            [EnumAttirbute("随机顺序节点")]
            RANDOM_SEQUEUECE = 3,
    
            /// <summary>
            /// 随机权重节点
            /// </summary>
            [EnumAttirbute("随机权重节点")]
            RANDOM_PRIORITY = 4,
    
            /// <summary>
            /// 并行节点
            /// </summary>
            [EnumAttirbute("并行节点")]
            PARALLEL = 5,
    
            /// <summary>
            /// 并行选择节点
            /// </summary>
            [EnumAttirbute("并行选择节点")]
            PARALLEL_SELECT = 6,
    
            /// <summary>
            /// 并行执行所有节点
            /// </summary>
            [EnumAttirbute("并行执行所有节点")]
            PARALLEL_ALL = 7,
    
            /// <summary>
            /// IF 判断节点
            /// </summary>
            [EnumAttirbute("IF 判断节点")]
            IF_JUDEG = 8,
    
            /// <summary>
            /// 修饰节点_取反
            /// </summary>
            [EnumAttirbute("修饰节点_取反")]
            DECORATOR_INVERTER = 100,
    
            /// <summary>
            /// 修饰节点_重复
            /// </summary>
            [EnumAttirbute("修饰节点_重复")]
            DECORATOR_REPEAT = 101,
    
            /// <summary>
            /// 修饰节点_返回Fail
            /// </summary>
            [EnumAttirbute("修饰_返回Fail")]
            DECORATOR_RETURN_FAIL = 102,
    
            /// <summary>
            /// 修饰节点_返回Success
            /// </summary>
            [EnumAttirbute("修饰_返回Success")]
            DECORATOR_RETURN_SUCCESS = 103,
    
            /// <summary>
            /// 修饰节点_直到Fail
            /// </summary>
            [EnumAttirbute("修饰_直到Fail")]
            DECORATOR_UNTIL_FAIL = 104,
    
            /// <summary>
            /// 修饰节点_直到Success
            /// </summary>
            [EnumAttirbute("修饰_直到Success")]
            DECORATOR_UNTIL_SUCCESS = 105,
    
            /// <summary>
            /// 条件节点
            /// </summary>
            [EnumAttirbute("条件节点")]
            CONDITION = 200,
    
            /// <summary>
            /// 行为节点
            /// </summary>
            [EnumAttirbute("行为节点")]
            ACTION = 300,
    
            /// <summary>
            /// 子树
            /// </summary>
            [EnumAttirbute("子树")]
            SUB_TREE = 1000,
        }
    }
    

    2、节点执行结果枚举

    namespace BehaviorTree
    {
        /// <summary>
        /// 节点执行结果
        /// </summary>
        public enum ResultType
        {
            /// <summary>
            /// 失败
            /// </summary>
            Fail        = 0,
    
            /// <summary>
            /// 成功
            /// </summary>
            Success     = 1,
    
            /// <summary>
            /// 执行中
            /// </summary>
            Running     = 2,
        }
    }
    

    **3、**添加节点超类 NodeBase
    分析:超类 NodeBase 只包含所有/大部分 节点需要的 属性、方法
    属性:
    节点需要有唯一 id :用于增删改查等
    节点需要有类型 :NODE_TYPE nodeType;
    部分节点需要有权重: int priority
    节点的执行状态:NODE_STATUS nodeStatus // 执行中/非执行

    方法:
    每个节点 执行包含三个阶段:开始执行、执行中、结束执行 所以需要三个接口
    节点 开始执行 进入接口

    public virtual void OnEnter()
    

    节点 执行中 接口(没帧调用的),且每次需要返回执行结果 ResultType

    public abstract ResultType Execute()
    

    节点 结束执行 退出接口

    public virtual void OnExit()
    

    代码:

    
    namespace BehaviorTree
    {
        /// <summary>
        /// 节点超类
        /// </summary>
        public abstract class NodeBase
        {
            /// <summary>
            /// 节点类型
            /// </summary>
            protected NODE_TYPE nodeType;
            /// <summary>
            /// 节点序列
            /// </summary>
            private int nodeIndex;
    
            /// <summary>
            /// 节点Id
            /// </summary>
            private int nodeId;
    
            /// <summary>
            /// EntityId
            /// </summary>
            private int entityId;
    
            /// <summary>
            /// 权重
            /// </summary>
            private int priority;
    
            protected NODE_STATUS nodeStatus = NODE_STATUS.READY;
    
            public NodeBase()
            {
            }
    
            protected void SetNodeType(NODE_TYPE nodeType)
            {
                this.nodeType = nodeType;
            }
    
            public NODE_TYPE NodeType
            {
                get { return nodeType; }
            }
    
            public int NodeIndex
            {
                get { return nodeIndex; }
                set { nodeIndex = value; }
            }
    
            public int NodeId
            {
                get { return nodeId; }
                set { nodeId = value; }
            }
    
            public int EntityId
            {
                get { return entityId; }
                set { entityId = value; }
            }
    
            public int Priority
            {
                get { return priority; }
                set { priority = value; }
            }
    
            /// <summary>
            /// 进入节点
            /// </summary>
            public virtual void OnEnter()
            {       }
    
            /// <summary>
            /// 执行节点抽象方法
            /// </summary>
            /// <returns>返回执行结果</returns>
            public abstract ResultType Execute();
    
            /// <summary>
            /// 退出节点
            /// </summary>
            public virtual void OnExit()
            {        }
    
            //执行 Execute 的前置方法,在 Execute() 方法的第一行调用
            public void Preposition()
            {
                if (nodeStatus == NODE_STATUS.READY)
                {
                    nodeStatus = NODE_STATUS.RUNNING;
                    OnEnter();
                }
            }
    
            /// <summary>
            ///  执行 Execute 的后置方法,在 Execute() 方法的 returen 前调用
            /// </summary>
            public void Postposition(ResultType resultType)
            {
                if (resultType != ResultType.Running)
                {
                    nodeStatus = NODE_STATUS.READY;
                    OnExit();
                }
            }
        }
    }
    

    **4、**给组合节点和子节点(叶子节点)分离出两个超类NodeCombiner 组合节点父类NodeLeaf 叶节点父类,分别继承于NodeBase

    组合节点需要有子节点: protected List<NodeBase> nodeChildList = new List<NodeBase>();
    添加子节点方法:public void AddNode(NodeBase node)
    获取子节点方法:public List<NodeBase> GetChilds()

    using System.Collections.Generic;
    
    namespace BehaviorTree
    {
        /// <summary>
        /// 组合节点
        /// </summary>
        public abstract class NodeComposite : NodeBase
        {
            // 保存子节点
            protected List<NodeBase> nodeChildList = new List<NodeBase>();
    
            public NodeComposite(NODE_TYPE nodeType) : base()
            {
                SetNodeType(nodeType);
            }
    
            public void AddNode(NodeBase node)
            {
                int count = nodeChildList.Count;
                node.NodeIndex = count;
                nodeChildList.Add(node);
            }
    
            public List<NodeBase> GetChilds()
            {
                return nodeChildList;
            }
    
            public override ResultType Execute()
            {
                return ResultType.Fail;
            }
        }
    }
    

    叶节点父类没有特殊属性或方法只需要继承 NodeBase 即可

    using System.Collections.Generic;
    
    namespace BehaviorTree
    {
        /// <summary>
        /// 叶节点
        /// </summary>
        [System.Serializable]
        public class NodeLeaf : NodeBase
        {
            public NodeLeaf():base()
            {
            }
    
            public override ResultType Execute()
            {
                return ResultType.Fail;
            }
        }
    }
    

    5、叶节点

    条件节点抽象类

    namespace BehaviorTree
    {
        /// <summary>
        /// 条件节点(叶节点)
        /// </summary>
        public abstract class NodeCondition : NodeLeaf
        {
            public NodeCondition() : base(NodeType.Condition)
            {
            }
        }
    }
    

    行为节点抽象类

    namespace BehaviorTree
    {
        /// <summary>
        /// 行为节点(叶节点)
        /// </summary>
        public abstract class NodeAction : NodeLeaf
        {
            public NodeAction() : base(NodeType.Action)
            {
            }
        }
    }
    

    实际应用只有条件节点和行为节点是根据不同情况创建的,所以需要将 条件节点、行为节点 做成一个抽象类,提供给实际派生类继承。

    6、组合节点
    6.1、选择节点

    using UnityEngine;
    
    namespace BehaviorTree
    {
        /// <summary>
        /// 选择节点(组合节点)
        /// </summary>
        public class NodeSelect : NodeComposite
        {
            private NodeBase lastRunningNode;
            public static string descript = "选择节点依次遍历所有子节点,如果都返回 Fail,\n" +
                                            "则向父节点返回 Fail直到一个节点返回 Success \n" +
                                            "或者Running,停止后续节点的执行,向父节点返回\n" +
                                            " Success 或者 Running注意:如果节点返回 Running\n" +
                                            " 需要记住这个节点,下次直接从此节点开始执行\n";
    
            public NodeSelect():base(NODE_TYPE.SELECT)
            {    }
    
            public override void OnEnter()
            {
                base.OnEnter();
            }
    
            public override void OnExit()
            {
                base.OnExit();
    
                if (null != lastRunningNode)
                {
                    lastRunningNode.Postposition(ResultType.Fail);
                    lastRunningNode = null;
                }
            }
    
            public override ResultType Execute()
            {
                int index = 0;
                if (lastRunningNode != null)
                {
                    index = lastRunningNode.NodeIndex;
                }
                lastRunningNode = null;
    
                ResultType resultType = ResultType.Fail;
                for (int i = index; i < nodeChildList.Count; ++i)
                {
                    NodeBase nodeBase = nodeChildList[i];
    
                    nodeBase.Preposition();
                    resultType = nodeBase.Execute();
                    nodeBase.Postposition(resultType);
    
                    if (resultType == ResultType.Fail)
                    {
                        continue;
                    }
    
                    if (resultType == ResultType.Success)
                    {
                        break;
                    }
    
                    if (resultType == ResultType.Running)
                    {
                        lastRunningNode = nodeBase;
                        break;
                    }
                }
    
                NodeNotify.NotifyExecute(EntityId, NodeId, resultType, Time.realtimeSinceStartup);
                return resultType;
            }
        }
    }
    
    

    6.2顺序节点

    using UnityEngine;
    
    namespace BehaviorTree
    {
        /// <summary>
        /// 顺序节点(组合节点)
        /// </summary>
        public class NodeSequence : NodeComposite
        {
            private NodeBase lastRunningNode;
            public static string descript = "顺序节点依次执行子节点,只要节点返回Success,就\n" +
                                             "继续执行后续节点,直到一个节点返回 Fail 或者 \n" +
                                             "Running,停止执行后续节点,向父节点返回 Fail \n" +
                                             "或者 Running,如果所有节点都返回 Success,则向\n" +
                                             "父节点返回 Success和选择节点一样,如果一个节点\n" +
                                             "返回 Running,则需要记录该节点,下次执行时直接\n" +
                                             "从该节点开始执行";
    
            public NodeSequence():base(NODE_TYPE.SEQUENCE)
            {  }
    
            public override void OnEnter()
            {
                base.OnEnter();
                lastRunningNode = null;
            }
    
            public override void OnExit()
            {
                base.OnExit();
    
                if (null != lastRunningNode)
                {
                    lastRunningNode.Postposition(ResultType.Fail);
                    lastRunningNode = null;
                }
            }
    
            public override ResultType Execute()
            {
                int index = 0;
                if (lastRunningNode != null)
                {
                    index = lastRunningNode.NodeIndex;
                }
                lastRunningNode = null;
    
                ResultType resultType = ResultType.Fail;
                for (int i = index; i < nodeChildList.Count; ++i)
                {
                    NodeBase nodeBase = nodeChildList[i];
    
                    nodeBase.Preposition();
                    resultType = nodeBase.Execute();
                    nodeBase.Postposition(resultType);
    
                    if (resultType == ResultType.Fail)
                    {
                        break;
                    }
    
                    if (resultType == ResultType.Success)
                    {
                        continue;
                    }
    
                    if (resultType == ResultType.Running)
                    {
                        lastRunningNode = nodeBase;
                        break;
                    }
                }
    
                NodeNotify.NotifyExecute(EntityId, NodeId, resultType, Time.realtimeSinceStartup);
                return resultType;
            }
        }
    }
    
    
    

    6.3 随机节点

    using System.Collections.Generic;
    using UnityEngine;
    
    namespace BehaviorTree
    {
        /// <summary>
        /// 随机节点(组合节点)
        /// </summary>
        public class NodeRandom : NodeComposite
        {
            private NodeBase lastRunningNode;
            private int[] idArr = null;
            private int _randomCount = 0;
            private System.Random random;
    
            public static string descript = "随机执行节点,只要有一个节点返回成功,它就会返\n" +
                                            "回成功,不再执行后续节点如果所有节点都返回 Fail,\n" +
                                            "则它返回 Fail,否则返回 Running";
    
            public NodeRandom():base(NODE_TYPE.RANDOM)
            {
                random = new System.Random();
            }
    
            public override void OnEnter()
            {
                base.OnEnter();
            }
    
            public override void OnExit()
            {
                base.OnExit();
    
                if (null != lastRunningNode)
                {
                    lastRunningNode.Postposition(ResultType.Fail);
                    lastRunningNode = null;
                }
            }
    
            public override ResultType Execute()
            {
                int index = -1;
                if (lastRunningNode != null)
                {
                    index = lastRunningNode.NodeIndex;
                }
                lastRunningNode = null;
    
                ResultType resultType = ResultType.Fail;
    
                for (int i = 0; i < nodeChildList.Count; ++i)
                {
                    if (index < 0)
                    {
                        index = GetRandom();
                    }
                    NodeBase nodeBase = nodeChildList[index];
                    index = -1;
    
                    nodeBase.Preposition();
                    resultType = nodeBase.Execute();
                    nodeBase.Postposition(resultType);
    
                    if (resultType == ResultType.Fail)
                    {
                        continue;
                    }
    
                    if (resultType == ResultType.Success)
                    {
                        break;
                    }
    
                    if (resultType == ResultType.Running)
                    {
                        lastRunningNode = nodeBase;
                        break;
                    }
                }
    
                NodeNotify.NotifyExecute(EntityId, NodeId, resultType, Time.realtimeSinceStartup);
                return resultType;
            }
    
            private int GetRandom()
            {
                if (null == idArr)
                {
                    idArr = new int[nodeChildList.Count];
                    for (int i = 0; i < idArr.Length; ++i)
                    {
                        idArr[i] = i;
                    }
                }
    
                int count = idArr.Length - 1;
                int index = random.Next(0, idArr.Length - _randomCount);
                int value = idArr[index];
                idArr[index] = idArr[count - _randomCount];
                ++_randomCount;
    
                return value;
            }
        }
    }
    

    6.4 修饰节点

    namespace BehaviorTree
    {
        /// <summary>
        /// 修饰节点(组合节点)
        /// </summary>
        public abstract class NodeDecorator : NodeComposite
        {
            public NodeDecorator(NODE_TYPE nodeType) :base(nodeType)
            {  }
    
            /// <summary>
            /// 修饰节点不能独立存在,其作用为对子节点进行修饰,以得到我们所希望的结果
            /// 修饰节点常用的几个类型如下:
            /// Inverter        对子节点执行结果取反
            /// Repeater        重复执行子节点 N 次
            /// Return Failure  执行到此节点时返回失败
            /// Return Success  执行到此节点时返回成功
            /// Unitl Failure   直到失败,一直执行子节点
            /// Until Success   直到成功,一直执行子节点
            /// </summary>
            /// <returns></returns>
            public override ResultType Execute()
            {
                return ResultType.Success;
            }
        }
    }
    

    6.5并行节点

    using UnityEngine;
    
    namespace BehaviorTree
    {
        /// <summary>
        /// 并行节点(组合节点)
        /// </summary>
        public class NodeParallel : NodeComposite
        {
            public static string descript = "并行节点同时执行所有节点,直到一个节点返回 Fail 或\n" +
                                           "者全部节点都返回 Success才向父节点返回 Fail 或者 \n" +
                                            "Success,并终止执行其他节点其他情况向父节点返回 Running";
    
    
            private int _runningNode = 0;
    
            public NodeParallel():base(NODE_TYPE.PARALLEL)
            { }
    
            public override void OnEnter()
            {
                base.OnEnter();
                _runningNode = 0;
            }
    
            public override void OnExit()
            {
                base.OnExit();
                for (int i = 0; i < nodeChildList.Count; ++i)
                {
                    int value = (1 << i);
                    if ((_runningNode & value) > 0)
                    {
                        NodeBase nodeBase = nodeChildList[i];
                        nodeBase.Postposition(ResultType.Fail);
                    }
                }
            }
    
            public override ResultType Execute()
            {
                ResultType resultType = ResultType.Fail;
    
                int successCount = 0;
                for (int i = 0; i < nodeChildList.Count; ++i)
                {
                    NodeBase nodeBase = nodeChildList[i];
    
                    nodeBase.Preposition();
                    resultType = nodeBase.Execute();
                    nodeBase.Postposition(resultType);
    
                    if (resultType == ResultType.Fail)
                    {
                        break;
                    }
    
                    if (resultType == ResultType.Success)
                    {
                        ++successCount;
                        continue;
                    }
    
                    if (resultType == ResultType.Running)
                    {
                        _runningNode |= (1 << i);
                        continue;
                    }
                }
    
                if (resultType != ResultType.Fail)
                {
                    resultType = (successCount >= nodeChildList.Count) ? ResultType.Success : ResultType.Running;
                }
    
                NodeNotify.NotifyExecute(EntityId, NodeId, resultType, Time.realtimeSinceStartup);
                return resultType;
            }
        }
    }
    
    

    12 个脚本实现 Behavior Tree 的核心功能。

    项目代码连接,一个Unity实现的可运行的Demo,行为树逻辑代码部分用 C#实现,如果没使用过Unity,可以只看行为树部分的逻辑

    下篇将实际运用该代码以测试其功能逻辑

    展开全文
  • 对于角色挂机自动战斗,Unity有行为树插件Behavior Designer可以实现,但不能实现战斗逻辑热更,所以我用Lua对着Behavior Designer重新实现了部分基础功能,这样,使用Lua版的行为树实现挂机自动战斗,就可以热更啦...

    在使用Unity开发手游项目中,用Lua作为热更脚本时,也许有的RPG项目会有连战斗也要求热更,对于角色挂机自动战斗,Unity有行为树插件Behavior Designer可以实现,但不能实现战斗逻辑热更,所以我用Lua对着Behavior Designer重新实现了部分基础功能,这样,使用Lua版的行为树实现挂机自动战斗,就可以热更啦!

    前提说明:
    1,本文假设读者对树插件Behavior Designer有些了解,因为我是对着它思路来实现的,不了解可以去看一下,这里可能不打算介绍行为树知识。
    2,我使用的时ulua的LuaFramework_UGUI来实现的,如果你使用xLua也不影响移植。
    3,这当然只是实现比较简单的基础功能,不能像Behavior Designer那样有丰富的配置,但也可以继续拓展呀,如遍历行为树时间间隔为每帧,不服可以改成0.02s的配置。

    实现思路始于此图:
    这里写图片描述

    行为树启动后,每帧tick一次,检测行为树的Task(行为树的每个节点都是一个Task)。
    然后基础Task大致可以分为几大类Composites、Decorator、Action等
    这里写图片描述

    然后得出代码结构:
    文件名和类(表)名尽量跟Behavior Designer一样。
    关于Lua行为树实现基础代码都在 “LuaFramework\Lua\BehaviorTree” 文件夹下
    大体代码结构如下:
    这里写图片描述

    系不系有点相似。

    看实现之前,不如先到过来看,完成了怎么使用,再去了解它的实现。
    使用方法,如,我们要完成Behavior Designer中这样的一颗行为树
    这里写图片描述

    行为树框架之外需要新建3个lua文件,2个自定义节点xxx.lua文件和一个拼接操作Test.lua文件,最后在游戏入口处Game.lua调用,3个文件即
    这里写图片描述

    TestConditional.lua:

    TestConditionalTask = BehTree.IConditional:New()
    local this = TestConditionalTask
    this.name = 'TestConditionalTask'
    testt = {}
    idnex = 1
    --
    function this:OnUpdate()
    	log('----------TestConditionalTask---------Running')
    	log(self:ToString())
    	--模拟Behavior Designer IsNullOrEmpty节点
    	--IsNullOrEmpty == false
    	return BehTree.TaskStatus.Failure
    end
    

    ActionLogTask.lua

    ActionLogTask = BehTree.IAction:New()
    local this = ActionLogTask
    this.name = 'ActionLogTask'
    -- 模拟Behavior Designer Log节点
    function this:OnUpdate()
    	log('-----------ActionLogTask Success')
    	return BehTree.TaskStatus.Success
    end
    

    Test.lua

    require 'BehaviorTree/Test/TestConditionalTask'
    require 'BehaviorTree/Test/ActionLogTask'
    
    --[[
    代码拼接行为树有代码结构顺序要求,
    代码顺序也遵从行为树的图示,上到下,从左到右拼接
    上层或者本节点的前一个节点完成才能进行下一个
    ]]
    local function BuildTree()
    	local root = BehTree.TaskRoot:New()
    
    	--这里直接使用Repeater作为入口并且检测,相当于Entry
    	local entry = BehTree.Repeater:New()
    	entry.name = '第0个复合节点repeat == Entry '
    	--根节点添加layer:1
    	root:PushTask(entry)
    
    --------layer:2
    	local selector1 = BehTree.Selector:New()
    	selector1.name = '第1个复合节点selector == Selector '
    	entry:AddChild(selector1)
    	
    	-----layer3
    	local sequence2 = BehTree.Sequence:New()
    	sequence2.name = '第2个复合节点sequence == Sequence'
    	selector1:AddChild(sequence2)
    
    	--layer:4,并行
    	local testConditionalTask = TestConditionalTask:New()
    	testConditionalTask.name = '并行第3个叶子节点 == Is Null Or Empty'
    	local actionLogTask = ActionLogTask:New()
    	actionLogTask.name = '并行第3个叶子节点 == Log'
    	--添加
    	sequence2:AddChild(testConditionalTask)--child:1
    	sequence2:AddChild(actionLogTask)--child:2
    
    	return root
    end
    
    return BuildTree()
    
    
    
    

    最后启动游戏时调用,在Game.lua中加入这3行代码,初始化和启动行为树

    require 'BehaviorTree/BehaviorTreeManager'
    local tree = require 'BehaviorTree/Test/Test'
    BehTree.BehaviorTreeManager.RunTree(tree)
    

    再启动游戏就能看到行为树的打印了Log了
    这里写图片描述

    最基本的用法就这样完成了!

    那,实现代码呢?
    关于行为树的实现,从BehaviorTreeManager.lua看起,看到Gmae.lua中启动的方法BehTree.BehaviorTreeManager.RunTree(tree)
    BehaviorTreeManager.lua

    BehTree={}
    require 'BehaviorTree/Base/Enum'
    require 'BehaviorTree/Base/StackList'
    require 'BehaviorTree/Base/TaskRoot'
    require 'BehaviorTree/Base/ITask'
    require 'BehaviorTree/Base/IParent'
    require 'BehaviorTree/Base/IAction'
    require 'BehaviorTree/Base/IComposite'
    require 'BehaviorTree/Base/IConditional'
    require 'BehaviorTree/Base/IDecorator'
    --复合节点()
    require 'BehaviorTree/Composite/Selector'
    require 'BehaviorTree/Composite/Sequence'
    --修饰节点
    require 'BehaviorTree/Decorator/Repeater'
    require 'BehaviorTree/Decorator/ReturnFailure'
    require 'BehaviorTree/Decorator/ReturnSuccess'
    require 'BehaviorTree/Decorator/UntilFailure'
    require 'BehaviorTree/Decorator/Inverter'
    --Action节点
    require 'BehaviorTree/Action/Wait'
    
    
    BehTree.BehaviorTreeManager={}
    local this = BehTree.BehaviorTreeManager
    function this.Init()
    end
    --从这里开始启动一颗行为树的入口跟节点
    function this.RunTree(enter)
    	this.bhTree =enter
    	coroutine.start(this.OnUpdate)
    end
    
    --重置树下所有Action
    function this.ResetTreeActions()
    	local treeRoot = this.GetCurTreeRoot()
    	treeRoot:ResetAllActionsState()
    end
    
    function this.OnUpdate() 
    	while true do
    		coroutine.step()
    		this.UpdateTask()
    	end
    end
    function this.UpdateTask()
    	local status = this.bhTree:OnUpdate()
    	if status ~= BehTree.TaskStatus.Running then
    		table.remove(this.curTrees, key)
    	end
    	
    end
    
    

    总的核心思想就这样,不停的每帧去遍历自己拼装好的行为树节点,剩下的也就是节点之间的层级等关系的实现。
    回到最初说的,每个节点都是一个Task,所以上面看到的Selector.lua、Sequence.lua、IComposite.lua等都是ITask.lua的子类,如此思路,举例Sequence.lua:基类->IComposite.lua:基类->IParent.lua:基类->ITask.lua

    BehTree.Sequence = BehTree.IComposite:New()
    local this = BehTree.Sequence
    --初始默认未激活
    this.curReturnStatus = BehTree.TaskStatus.Inactive
    this.name = 'Sequence'
    function this:OnUpdate()
    	if self:HasChildren() == false then
    		logError(self.name..'父节点类型没有子节点!!')
    		return BehTree.TaskStatus.Failure
    	end
    
    	if self.curRunTask == nil then
    		--选择(or)节点肯定是去找子节点
    		self.curRunTask = self:GetNextChild()
    				--如下不该发生
    		if self.curRunTask == nil then
    			--如果没有子节点
    			logError('错误的节点配置!:没有子节点或已越界!!'..self.name..'子节点长度:'..self:GetChildCount()..'   尝试访问:'..self:GetCurChildIndex()+1)
    			return BehTree.TaskStatus.Failure
    		end
    	end
    	return self:RunChildByAnd()
    end
    --and:遇到一个false就中断执行
    --序列组合节点:AND逻辑,所有子节点Success才返回Success
    function this:RunChildByAnd()
    
    	while self.curRunTask ~= nil do
    		self.curReturnStatus = self.curRunTask:OnUpdate() 
    		self.curRunTask:ResetTaskStatus()
    		--找到false或者running直接返回,就中断执行,这一帧到此结束
    		if self.curReturnStatus == BehTree.TaskStatus.Failure then
    			--返回Failure说明这次Sequence走完了,重置等下一轮
    			self:Reset()
    			return BehTree.TaskStatus.Failure
    		 elseif self.curReturnStatus == BehTree.TaskStatus.Running then
    			return BehTree.TaskStatus.Running
    		else
    			--没找到false就一直执行下去
    			self.curRunTask = self:GetNextChild()
    		end
    	end
    
    	--找完了所有节点没有false,那么success
    	--说明这次Sequence走完了,重置等下一轮
    	self.curReturnStatus = BehTree.TaskStatus.Success
    	self:Reset()
    	return BehTree.TaskStatus.Success
    end
    --重置
    function this:Reset()
    	self:ResetChildren()
    end
    

    IComposite.lua

    --[[
    常用于Sequence的第一个节点判断
    ]]
    BehTree.IComposite = BehTree.IParent:New()
    local this = BehTree.IComposite
    this.taskType = BehTree.TaskType.Composite
    
    

    IParent.lua

    --[[
    父任务 Parent Tasks
    behavior tree 行为树中的父任务 task 
    包括:composite(复合),decorator(修饰符)!
    虽然 Monobehaviour 没有类似的 API,但是并不难去理解这些功能:
    ]]
    
    BehTree.IParent = BehTree.ITask:New({})
    local this = BehTree.IParent
    --此时this把ITask设为元表的表
    --提供共有函数
    function this:New(o)
    	o = o or {}
    	o.curChilIndex = 0
    	o.curRunTask = nil
    	o.childTasks={}
    	--o把BehTree.IParentTask设为元表,
    	--而BehTree.IParentTask把ITask设为元表
    	--从而保持类的属性独立,不共用
    	setmetatable(o, self)
    	self.__index = self
    	return o
    end
    --重置当前访问的子节点位置为第一个
    function this:ResetChildren()
    	self.curRunTask = nil
    	self.curChilIndex = 0
    end
    
    function this:GetCurChildIndex()
    	return self.curChilIndex
    end
    
    --对于ReaterTask等只能有一个子节点的
    function this:GetOnlyOneChild()
    	if self:GetChildCount() ~= 1 then
    		logError('---------'..self.name..'应该有且只有一个子节点!but:childCount:'..self:GetChildCount())
    		return nil
    	end
    	return self.childTasks[1]
    end
    --添加子节点有顺序要求
    function this:AddChild(task)
    	log('------------------'..self.name..'  添加子节点 : '..task.name)
    	if task == nil then
    		logError('---------------------add task is nil !!')
    		return
    	end
    	local index = #self.childTasks+1
    	task.index = index
    	task.layer = self.layer + 1
    	task.parent = self
    	task.root = self.root
    	self.childTasks[index] = task
    	self.root:AddGlobalTask(task.tag, task)
    	return self
    end
    function this:ClearChildTasks()
    	self.curIndex = 0
    	self.childTasks = nil
    	self.childTasks = {}
    end
    function this:HasChildren()
    	if #self.childTasks <= 0 then
    		return false
    	else
    		return true
    	end
    end
    function this:GetChildCount()
    	return #self.childTasks
    end
    function this:GetNextChild()
    	if #self.childTasks >= (self.curChilIndex+1) then
    		--指向當前正執行的
    		self.curChilIndex = self.curChilIndex + 1
    		local nextChild = self.childTasks[self.curChilIndex]
    		return nextChild
    	else
    		return nil 
    	end
    end
    --获取前一个子节点,不移动指针
    function this:GetCurPrivousTask()
    	if self.curChilIndex <=1 then
    		logError(self.name..' GetCurPrivousTask : 已经是最前的Task或childtask为空')
    		return nil
    	else
    		return self.childTasks[self.curChilIndex-1]
    	end
    end
    --获取下一个子节点,不移动指针
    function this:GetCurNextTask()
    	if self.curChilIndex >= #self.childTasks then
    		--logError(self.name..' GetCurNextTask : 已经是最后的Task或childtask为空')
    		return nil
    	else
    		return self.childTasks[self.curChilIndex+1]
    	end
    end
    
    

    ITask.lua

    --[[
    所有task基础
    ]]
    BehTree.ITask={
    	--不需要主动设置参数
    	--由树结构的机制驱动的参数,
    	taskStatus = BehTree.TaskStatus.Running,
    	curReturnStatus = BehTree.TaskStatus.Inactive,
    	taskType = BehTree.TaskType.UnKnow,
    	root = nil,
    	index = 1,
    	parent = nil,
    	layer = 1,
    
    	--主动设置参数
    	name = '暂未设置名称',
    	tag = 'UnTag',--用于搜索
    	desc = '暂无描述'
    }
    local this = BehTree.ITask
    function this:New(o)
    	o = o or {}
    	setmetatable(o, self)
    	self.__index = self
    	return o
    end
    
    function this:ResetTaskStatus()
    end
    --获取同一层layer的上一个节点
    function this:GetPriviousTask()
    	if self.parent == nil then
    		logError(self.name..' 找不到父节点 try call GetPriviousTask')
    		return nil
    	end
    	if self.layer <= 1 then
    		logError(self.name..' GetPriviousTask已经是最顶层,单独Task')
    		return nil
    	end
    	local priviousTask = self.parent:GetCurPrivousTask()
    	return priviousTask
    end
    --获取同一层layer下一个task
    function this:GetNextTask()
    	if self.parent == nil then
    		logError(self.name..' 找不到父节点 try call GetNextTask')
    		return nil
    	end
    	if self.layer <= 1 then
    		logError(self.name..' GetNextTask已经是最顶层,单独Task')
    		return nil
    	end
    	local nextTask = self.parent:GetCurNextTask()
    	return nextTask
    end
    
    function this:ToString()
    	local name = '名称 : '..self.name..'\n'
    	local layer = '所处层次 :'..self.layer..'\n'
    	local parent = '父节点 : '..self.parent.name..'\n'
    	local index = '作为子节点顺序 : '..self.index..'\n'
    	local desc = '描述 : '..self.desc..'\n'
    	local status = 'UnKnow'
    	if self.curReturnStatus == 1 then
    		status = 'Inactive'
    	elseif self.curReturnStatus == 2 then
    		status = 'Failure'
    	elseif self.curReturnStatus == 3 then
    		status = 'Success'
    	elseif self.curReturnStatus == 4 then
    		status = 'Running'
    	end
    	local curReturnStatus = '运行返回结果:'..status..'\n'
    	return name..desc..layer..parent..index..curReturnStatus
    end
    
    

    关于Sequence部分差不多这样,其他代码略多我就不贴完了,我传上去,可以下载来看看,
    但也只有LuaFramework中的LuaFramework\Lua\BehaviorTree部分代码,而不是整个ulua工程,
    记住:调用时记得在Game.lua等游戏启动入口写上这3行来启动行为树。

    require 'BehaviorTree/BehaviorTreeManager'
    local tree = require 'BehaviorTree/Test/Test'
    BehTree.BehaviorTreeManager.RunTree(tree)
    

    下载地址:
    https://github.com/HengyuanLee/LuaBehaviorTree

    
    
    展开全文
  • Unity3D行为树系统编程实现

    千次阅读 2015-03-06 12:32:19
    Unity3D行为树系统编程实现行为树在AI领域使用十分广泛,...关于行为树的介绍和行为树概念请看《使用行为树behavior-tree实现游戏ai》这是业界一个前辈写的文章我只是代为转载。 很久没有写文章了,如果文笔
  • cark.behavior-tree:功能行为树实现
  • 依靠行为树建立的AI系统,控制实体动作。如何添加“人在回路”功能?也就是说,人的控制指令如何干预行为树,同时又满足行为树的执行逻辑?(也就是说,不出现“用户给已死亡实体下指令,死亡实体竟然也执行了指令”...
  • 基于Unity行为树设计与实现的尝试

    万次阅读 热门讨论 2013-04-02 15:32:37
    查阅了一些行为树资料,目前最主要是参考了这篇文章,看完后感觉行为树实乃强大,绝对是替代状态机的不二之选。但从理论看起来很简单的行为树,真正着手起来却发现很多细节无从下手。 总结起来,就是: 1、...
  • 行为树的原理及实现

    万次阅读 2016-08-04 11:09:56
    查阅了一些行为树资料,目前最主要是参考了这篇文章,看完后感觉行为树实乃强大,绝对是替代状态机的不二之选。但从理论看起来很简单的行为树,真正着手起来却发现很多细节无从下手。 总结起来,就是: ...
  • 红黑的理解与代码实现

    千次阅读 2019-02-11 23:01:20
    红黑  我们知道对于二叉搜索而言,无法保证的平衡性,从而使得进行操作的时候时间复杂度在O(logn)与O(n)之间。这样是不稳定的。...而红黑就是为了实现自平衡这个功能而对2-3进行了扩...
  • 行为树的设计与实现

    万次阅读 2014-01-16 10:02:44
    查阅了一些行为树资料,目前最主要是参考了这篇文章,看完后感觉行为树实乃强大,绝对是替代状态机的不二之选。但从理论看起来很简单的行为树,真正着手起来却发现很多细节无从下手。 总结起来,就是: ...
  • Unity Behavior Tree Editor 行为树编辑器初级实现 编辑效果如下 编辑器脚本最好和行为树脚本分离。 需要存储的信息有:节点类型、子节点数组、(条件节点、行为节点需要特殊编辑) 数据存储在 ...
  • 二、行为树的原理和实现

    千次阅读 2014-10-03 18:18:59
    使用BTEditor可以生成行为树的Lua代码,这里对生成的代码进行解析。 (BTEditor的项目主页:https://github.com/bartoleo/BTEditor) 要注意: 1、行为树每个节点都需要向其父节点返回一个值,以允许父节点根据子...
  • 行为树

    千次阅读 2017-09-14 10:04:46
    如果了解过状态机,会知道在行为树之前,在实现AI用得比较多的技术是状态机,状态机理解起来是比较简单的,即一个状态过渡到另一个状态,通过判断将角色的状态改变即可,如果学习过Unity的Mecanim动画系统,会更加...
  • AI逻辑实现-用行为树取代状态机

    万次阅读 2014-12-28 14:13:45
    同时在ppt(附上下载地址)中述说了行为树的诸多优点,这里就不在赘述了。更多得是想总结一下自己玩了一阵子行为树后的一些实践体会。 个人体会: 状态机来实现AI更符合我们思维的朴素表达,我想任何一个有经验的...
  • AI决策算法 行为树实现(一)

    千次阅读 2017-03-30 15:32:20
    本系列文章部分内容参考自 ...感谢原作者的知识分享 行为树包括 : 基础节点:BaseNode 前置条件节点:PreconditionNode 顺序节点:SequenceNode 并行节点:ParallelNode ( 当一个节点不通过就不执行 ) 并
  • Java游戏服务器开发之行为树

    万次阅读 2018-05-25 16:39:07
    Java游戏服务器开发之行为树 之前有看到过状态机、行为树这一块的内容,但是没有认真细看,现在终于静下心来认真看了看,就看了别人的实现方式(网上代码没有Java实现的), 然后使用Java语言实现了一下 运行之后,在...
  • Javascript 和 C# 中行为树的一个非常简单的实现。 我们试图让事情变得尽可能简单。 有一个简单地在您的浏览器中运行的示例。 这是一个工作浏览器演示: : ,这里是关于此代码的完整博客文章: : 关于 C# 版本的...
  • AI逻辑实现-取舍行为树还是状态机

    万次阅读 2016-08-30 14:57:20
    AI逻辑实现-选择行为树还是状态机? 关注AI的朋友可能会看过赖勇浩翻译的《有限状态机时代终结的10大理由》 ,里面谈到了状态机的诸多弊端。同时在ppt(附上下载地址)中述说了行为树的诸多优点,这里就不在赘述了。更...
  • unity 行为树使用

    千次阅读 2016-05-13 21:11:28
    行为树是各种经典的控制节点+行为节点进行组合,从而实现一些复杂的行为状态的控制,这里使用的行为树插件是 Behavior Designer,它包含四种节点。分别是action节点、组合节点、条件节点和修饰节点。 (1) ...
  • Unity 行为树 Behavior Designer

    万次阅读 2015-12-02 19:29:33
    目前在Unity3D游戏中一般复杂的AI都可以看到行为树的身影,简单的AI使用状态机来实现就可以了,所以这里我也是简单的学习下,为以后用到做准备。 行为树的概念出现已经很多年了,总的来说,就是使用各种经典的...
  • 行为树入门

    千次阅读 2016-12-21 11:22:35
    Behavior Designer 行为树入门  本篇教程将带领大家熟悉 行为树插件之Behavior Designer 。本篇教程不会出现过多的代码,故也适合策划学习。 你将学习到一种新的动画与AI的解决方案使即使用Behavior Designer来...
  • 行为树简介 与有限状态机不同,行为树是一个分层节点树,它控制决策流和“任务”的执行,或者我们将进一步称之为“动作”。 树的叶子是实际命令,即我们的协调组件与系统其余部分交互的位置。 例如,在面向服务的...
  • unity行为树

    千次阅读 2017-09-08 13:35:01
    浅谈游戏AI 谈到游戏AI,很明显智能体拥有的知识条目越多,便显得更智能,但维护庞大数量的知识条目是个噩梦:使用有限状态机(FSM),分层有限状态机(HFSM),决策树(Decision Tree)来...试试Next-Gen AI的行为树(Behav
  • 行为树基础概念

    千次阅读 2017-12-29 10:14:25
    行为树基本原理

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 125,585
精华内容 50,234
关键字:

行为树代码实现