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

    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时,比如游戏里面的角色、npc、怪物等一些预设的AI逻辑,最简单的时候用if...else...,但是当游戏逻辑有点复杂时就显得有点力不从心,单单看这一大堆的if...else都恶心到吐。目前比较流行的ai模型有状态机和行为树(Behavior tree).

    状态机的实现我这里就不多加讨论了

    当游戏中的角色,npc,怪物等的决策不太复杂时状态机很有效,然而随着决策的复杂,状态机的缺点也慢慢的体现出来了

    罗列状态机比较突出的几个缺点:

    1、每一个状态的逻辑会随着新的状态的增加而越来越复杂。

    2、状态机状态的复用性很差,一旦一些因素变化导致环境发生变化,你只能新增一个状态,并给这个新状态添加连接及其跳转逻辑。

    3、没办法并行处理多个状态。

    行为树

    1、高度模块化状态,去掉状态中的逻辑跳转,使得状态编程一个"行为"。

    2、行为和行为之间的跳转是通过父节点的类型来决定的。并且可以通过并行节点来并行处理多个状态。

    3、通过增加控制节点的类型,可以达到复用行为的目的。


    关于行为树网上有不少相关文章,大部分都是理论方面的东西,对于行为树的实现,不少朋友不知从何下手。最近相对空闲之余,写了一个简单的行为树库。

    如果没有这方面基础的同学请先网上找下这方面的资料,先了解下行为树的一些基本的知识点。

    行为树的一些基本的控制节点。我们先实现几个最基本的控制节点。可以根据项目的需要再加一些其他控制节点。

    1、选择节点

    从头到尾按顺序选择执行条件为真的节点

    2、带记忆的选择节点

    从上一次执行的子节点开始,按顺序选择执行条件为true的节点

    3、序列节点

    从头到尾按顺序执行每个子节点,遇到false为止

    4、带记忆的序列节点

    从上一次执行的子节点开始,按顺序执行每个子节点,遇到false为止


    实现部分:

    定义一个节点的基类:

    #ifndef __BevNode_H__
    #define __BevNode_H__
    
    #include <vector>
    //#include "BevComm.h"
    using namespace std;
    namespace BT
    {
    	enum eBevState
    	{
    		E_BevState_Success,//成功
    		E_BevState_Fail,//失败
    		E_BevState_Running,//该节点正在运行
    	};
    	class BevNode
    	{
    	public:
    		BevNode()
    			: m_pParent(nullptr)
    		{
    		}
    		~BevNode()
    		{
    			for (auto pNode : m_VecChildren)
    			{
    				delete pNode;
    				pNode = nullptr;
    			}
    			m_VecChildren.clear();
    		}
    		void addChild(BevNode* pBevNode);
    		void setParent(BevNode* pParent){ m_pParent = pParent; }
    		BevNode* getParent(){ return m_pParent; }
    		virtual eBevState execute(float fDelta)
    		{
    			return E_BevState_Fail;
    		}
    	protected:
    		BevNode* m_pParent;
    		vector<BevNode*> m_VecChildren;
    	};
    }
    
    #endif
    选择节点
    #include "BevComm.h"</span>
    namespace BT
    {
    	class Selector : public BevNode
    	{
    	public:
    		Selector(){}
    		virtual ~Selector(){}
    
    		virtual eBevState execute(float fDelta);
    	};
    }
    #include "Selector.h"
    
    using namespace BT;
    eBevState Selector::execute(float fDelta)
    {
    	eBevState result = eBevState::E_BevState_Fail;
    	for (auto pNode : m_VecChildren)
    	{
    		eBevState status = pNode->execute(fDelta);
    		if (status != eBevState::E_BevState_Fail)
    		{
    			result = status;
    			break;
    		}
    	}
    	return result;
    }
    

    带记忆的选择节点


    
    
    #include "memorySelector.h"
    
    using namespace BT;
    eBevState memorySelector::execute(float fDelta)
    {
    	for (int i = m_nLastNode; i < m_VecChildren.size(); ++i)
    	{
    		BevNode* pNode = m_VecChildren[i];
    		eBevState status = pNode->execute(fDelta);
    		if (status != eBevState::E_BevState_Fail)
    		{
    			if (status == eBevState::E_BevState_Running)
    			{
    				m_nLastNode = i;
    				return status;
    			}
    		}
    	}
    	return eBevState::E_BevState_Fail;
    }

    序列节点
    
    
    #include "SequenceNode.h"
    
    using namespace BT;
    eBevState SequenceNode::execute(float fDelta)
    {
    	for (auto pNode : m_VecChildren)
    	{
    		eBevState status = pNode->execute(fDelta);
    		if (status != eBevState::E_BevState_Success)
    		{
    			return status;
    		}
    	}
    	return eBevState::E_BevState_Success;
    }
    

    带记忆的序列节点
    
    
    #include "memorySequence.h"
    
    using namespace BT;
    
    eBevState memorySequence::execute(float fDelta)
    {
    	for (int i = m_nLastIndex; i < m_VecChildren.size(); ++i)
    	{
    		BevNode* pNode = m_VecChildren[i];
    		eBevState status = pNode->execute(fDelta);
    		if (status != eBevState::E_BevState_Success)
    		{
    			if (status == eBevState::E_BevState_Running)
    			{
    				m_nLastIndex = i;
    				return status;
    			}
    		}
    	}
    	m_nLastIndex = 0;
    	return eBevState::E_BevState_Fail;
    }
    


    
    叶子节点(LeafNode)
    

    叶子节点也就是真正跟我们逻辑相关的节点了。

    首先叶子节点需要   进入时的逻辑(即该节点的初始化逻辑),运行逻辑,退出该节点时的逻辑。因为叶子节点直接跟业务逻辑挂钩,一开始实现时我是把处理具体逻辑的类继承于叶子节点。这样做的弊端是随着业务逻辑的复杂,基本上每个业务逻辑都要写一个业务逻辑的节点。而且这些业务逻辑节点不好共用,跟具体逻辑的耦合性太高了。后来想了想,干脆所有具体业务逻辑的节点都使用叶子节点,那么不一样的业务逻辑怎么处理呢?每个业务逻辑类不一样的无非就是进入时的逻辑(即该节点的初始化逻辑),运行逻辑,退出该节点时的逻辑,那么好办了,我们可以通过函数指针的形式,把不一样的逻辑传到叶子节点里面,这样所有的业务逻辑都可以使用叶子节点类LeafNode了。

    下来开始上代码

    #ifndef __LeafNode_H__
    #define __LeafNode_H__
    
    #include <functional>
    #include "BevNode.h"
    namespace BT
    {
    //外部强制中断
    	enum eInterruptState
    	{
    		E_IS_NONE,
    		E_IS_FAIL,
    		E_IS_SUCCESS,
    	};
    	class LeafNode;
    //刚进入时的初始化操作,外部可能需要跟该节点交互,所以把该节点的指针传出去,下面两个函数同理
    typedef std::function<void(LeafNode*)> enterFunc;

    //退出时执行的逻辑

    
    typedef std::function<void(LeafNode*)> exitFunc
    

    typedef std::function<eBevState(LeafNode*, float)> executeFunc;;//运行逻辑

    class LeafNode : public BevNode

    {

    public:

    LeafNode();

    virtual ~LeafNode();

    virtual eBevState execute(float fDelta);

    void interruptState(eInterruptState nInterruptState);

    void setEnterFunc(const enterFunc& enterFun);

    void setExecuteFunc(const executeFunc& executeFun);

    void setExitFunc(const exitFunc& exitFun);

    private:

    
    
    <span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre">	</span>//控制该节点的初始化,执行和退出</span>
    <span style="white-space:pre">	</span>enum
    	{
    		E_LS_ENTER,
    		E_LS_RUNNING,
    		E_LS_EXIT,
    	};
    private:
    	int m_nInterrupt;
    	int m_nLeafStatus;
    	enterFunc m_enterFunc;
    	executeFunc m_executeFunc;
    	exitFunc m_exitFunc;
    <span style="white-space:pre">	</span>};
    }
    
    #endif
    //LeafNode.cpp

    #include "LeafNode.h"
    
    using namespace BT;
    LeafNode::LeafNode()
    	: m_nInterrupt(E_IS_NONE)
    	, m_nLeafStatus(E_LS_ENTER)
    	, m_enterFunc(nullptr)
    	, m_executeFunc(nullptr)
    	, m_exitFunc(nullptr)
    {
    }
    
    LeafNode::~LeafNode()
    {
    }
    
    eBevState LeafNode::execute(float fDelta)
    {
    	eBevState status = E_BevState_Success;
    <span style="white-space:pre">	</span>//进入时
    	if (m_nLeafStatus == E_LS_ENTER)
    	{
    		if (m_enterFunc)
    		{
    			m_enterFunc(this);
    		}
    		m_nLeafStatus = E_LS_RUNNING;
    	}
    <span style="white-space:pre">	</span>//执行该节点
    	if (m_nLeafStatus == E_LS_RUNNING)
    	{
    		if (E_IS_NONE == m_nInterrupt)
    		{
    			if (m_executeFunc)
    			{
    				status = m_executeFunc(this, fDelta);
    				if (status != eBevState::E_BevState_Running)
    				{
    					m_nLeafStatus = E_LS_EXIT;
    				}
    			}
    			else
    			{
    				//m_nLeafStatus = E_LS_EXIT;
    				status = E_BevState_Running;
    			}
    		}
    		else
    		{
    			//被打断
    			if (E_IS_FAIL == m_nInterrupt)
    			{
    				status = E_BevState_Fail; 
    			}
    			else if (E_IS_SUCCESS == m_nInterrupt)
    			{
    				status = E_BevState_Success;
    			}
    			m_nLeafStatus = E_LS_EXIT;
    		}
    	}
    <span style="white-space:pre">	</span>//退出该节点
    	if (m_nLeafStatus == E_LS_EXIT)
    	{
    		if (m_exitFunc)
    		{
    			m_exitFunc(this);
    		}
    		m_nLeafStatus = E_LS_ENTER;
    		m_nInterrupt = E_IS_NONE;
    	}
    	return status;
    }
    
    void BT::LeafNode::setEnterFunc(const enterFunc& enterFun)
    {
    	m_enterFunc = enterFun;
    }
    
    void BT::LeafNode::setExecuteFunc(const executeFunc& executeFun)
    {
    	m_executeFunc = executeFun;
    }
    
    void BT::LeafNode::setExitFunc(const enterFunc& exitFun)
    {
    	m_exitFunc = exitFun;
    }
    
    void BT::LeafNode::interruptState(eInterruptState nInterruptState)
    {
    	m_nInterrupt = nInterruptState;
    }
    
    条件节点:跟具体逻辑节点一样,可以使用LeafNode节点来把条件逻辑传进来执行。

    到此一个简单的行为树框架已经完成,当然目前的控制节点太少了,需要补充更丰富的控制节点来满足我们的逻辑需要。


    后续有时间我会写一些demo来说明下如何使用该框架。




    展开全文
  • 行为树方式实现AI

    2015-03-27 12:15:43
      ...后来想用行为树的方式去实现AI 找了几个行为树编辑器最后找到了这个:http://behavior3js.guineashots.com 还可以在线编辑,很符合我的需求   添加各种节点后导出json 然后我来解析....

     

    最近苦于思考怎么去加强AI

    之前都是在代码里面根据各种情况去写代码

    简单的逻辑还好说,复杂的情况实在是让人受不了

    一大堆的这种业务逻辑代码自己都看晕了

     

    后来想用行为树的方式去实现AI

    找了几个行为树编辑器最后找到了这个:http://behavior3js.guineashots.com

    还可以在线编辑,很符合我的需求

     

    添加各种节点后导出json 然后我来解析.需要做的是实现你添加节点的方法

     

    用一个Blackboard 传递属性给下一个节点,记录公共变量

     

    --------------

    具体点的用法说明:

    这里面分为复合节点(顺序节点,选择节点), 条件节点,装饰节点,行为节点

     

    我用起来的话这几个就够了

    条件节点,装饰节点,行为节点 这几个是需要你去添加并且实现自己业务逻辑的

    复合节点不需要变动了.

     

    复合节点没什么逻辑.就是顺序调用子节点的返回结果(成功或者失败)

     

    条件节点 这个也很简单.和行为节点有点相识,都是没有子节点的.然后用来做一些基础判断 返回(成功或者失败)

     

    行为节点 也很简单,就是最终需要执行的操作.这里直接调用你原本的业务代码就行

     

    装饰节点 的作用就是计算一些数值,然后这个数值存储到Blackboard  传递到下一个节点去

    下一个节点可以利用上一个节点传递的值.

     

    装饰节点不做判断逻辑. 这个交给条件节点.多个装饰节点串联起来最终进行简单的true false 或者数字比较判断

     

    我在里面实现了个循环节点,有些操作是需要循环判断的,这里有点不同的是 一般用到循环节点,上个节点会传入 list. 这里会在循环的时候 把里面的元素传入到子节点里面去

     

     

    解析代码:

     

    @PostConstruct
    	public void init() throws IOException, InstantiationException,
    			IllegalAccessException {
    
    		FileSystemResource f = new FileSystemResource("config/ai/ddz_ai.txt");
    
    		File file = f.getFile();
    
    		String str = FileUtils.readFileToString(file, "utf-8");
    
    		BehaviorTree tree = JSON.parseObject(str, BehaviorTree.class);
    
    		JSONObject jsonObject = JSON.parseObject(str);
    
    		initClass();
    
    		JSONObject array = jsonObject.getJSONObject("nodes");
    
    		for (Object j : array.values()) {
    
    			JSONObject json = (JSONObject) j;
    
    			String id = json.getString("id");
    
    			map.put(id, json);
    		}
    
    
    		rootNode = parse(jsonObject.getString("root"));
    
    	}
    
    	BaseNode rootNode;
    
    	BaseNode parse(String root) throws InstantiationException,
    			IllegalAccessException {
    		JSONObject obj = map.get(root);
    
    		String name = obj.getString("name");
    		Class clazz = clazzs.get(name);
    		if (clazz == null) {
    			logger.error("不存在节点:{}", name);
    		}
    		BaseNode node = JSON.toJavaObject(obj, clazz);
    
    		if (node.getChild() != null && node instanceof Decorator) {
    			Decorator decorator = (Decorator) node;
    			decorator.setChildNode(parse(node.getChild()));
    		}
    
    		if (node.getChildren() != null && node instanceof Composite) {
    
    			Composite composite = (Composite) node;
    			List<BaseNode> list = new ArrayList<BaseNode>();
    
    			for (String nodeId : node.getChildren()) {
    				list.add(parse(nodeId));
    			}
    
    			// 按照y值 升序排序保证执行顺序
    			Collections.sort(list, new Comparator<BaseNode>() {
    				@Override
    				public int compare(BaseNode o1, BaseNode o2) {
    
    					if (o1.getDisplay().getY() > o2.getDisplay().getY()) {
    						return 1;
    					} else if (o1.getDisplay().getY() < o2.getDisplay().getY()) {
    						return -1;
    					}
    					return 0;
    				}
    
    			});
    
    			composite.setChildrenNodes(list);
    
    		}
    		node.setChild(null);
    		node.setChildren(null);
    		return node;
    	}
    
    	public void initClass() {
    
    		add(new Priority());
    		add(new Sequence());
    		//
    		add(new CallAction());
    		add(new NotCallAction());
    		add(new RobAction());
    		add(new NotRobAction());
    		add(new PlayCardAction());
    		add(new PassAction());
    		add(new FilterAction());
    
    		add(new SuccessAction());
    		//
    		add(new EqualCondition());
    		add(new FalseCondition());
    		add(new GreaterCondition());
    		add(new LessCondition());
    		add(new TrueCondition());
    
    		add(new CompareCondition());
    
    		//
    
    		add(new IsLordDecorator());
    
    		add(new BaseChooseCardDecorator());
    
    		add(new CanPlayCardDecorator());
    		add(new GetCardTypeDecorator());
    		add(new GetCardTypeNameDecorator());
    
    		add(new GetHandCardKindDecorator());
    		add(new GetCardValueDecorator());
    		add(new GetGameStatusDecorator());
    		add(new GetLordCardNumDecorator());
    		add(new GetNextHandCardNumDecorator());
    		add(new GetStartCardDecorator());
    
    		add(new GetStartCardDecorator());
    		add(new IfLessLordCardDecorator());
    		add(new IfMustPlayDecorator());
    
    		add(new IfSuppressedDecorator());
    		add(new LoopDecorator());
    		add(new SelfHandCardNumDecorator());
    
    	}
    
    	public void add(BaseNode baseNode) {
    		clazzs.put(baseNode.getName(), baseNode.getClass());
    	}

     

     

     

     

     

     

    上传了 代码和脚本.可以看看怎么去编辑逻辑 让逻辑跑通

     

    展开全文
  • 行为树JavaScript实现。 它们对于实现AI很有用。 如果您需要有关行为树的更多信息,请查看 , 不错的。 产品特点 需要的:序列,选择器,任务 扩展:装饰器 安装 如果使用npm: npm install behaviortree 或使用...
  • lua行为树设计与实现

    2019-10-22 12:11:47
    项目需要,之前行为树用的是behaviorDesigner,要改成纯lua的 ...用栈模拟递归好处在于效率高,并且容易控制,用非递归实现后自定义一个行为树节点,那么该节点不用知道父亲的方法,只要做好自己的事情就OK了 完...

    项目需要,之前行为树用的是behaviorDesigner,要改成纯lua的

    我先做了一版用递归实现,代码可读性高但是中断机制实现起来比较复杂,而且创建自定义action重写方法时需要调用父类的方法, 如果忘了调用就会出现问题, 所以改成了用栈模拟递归。

    用栈模拟递归好处在于效率高,并且容易控制,用非递归实现后自定义一个行为树节点,那么该节点不用知道父亲的方法,只要做好自己的事情就OK了

    完整测试工程已上传到了github:https://github.com/MCxYY/LuaBT

    行为树整体结构

     (和BehaviorDesigner很像,因为就是参考BehaviorDesigner设计的,要做的是c#移植成lua,移植成本尽可能低)

    如上,base文件夹中是行为树核心逻辑,最好不要修改,其他几个文件夹是自定义节点,如果是Action节点那么继承Base中的Action.lua;如果是Composite节点则继承Composite.lua等

    行为树类结构大致如下:(出于篇幅限制,有些方法没写出来)

    其中BTManager存储着所有行为树BTree,unity每帧调用BTManager的Update,而BTManager调用所有运行中的BTree的Update,BTree管理着自身的节点Task,根据逻辑执行调用Task的OnAwake()、OnStart等

    Shared是节点共享数据,在后文中讲述

    Task的OnAwake是整颗行为树激活时运行一次

    OnStart是进入该Task时运行一次

    OnUpdate是该Task执行中时每帧运行一次

    OnPause(bPause)是整棵行为树暂停或者从暂停中苏醒时运行,bPause为true则暂停

    OnEnd()是该Task退出时运行一次

    运行逻辑 

    行为树(BTree)启动的时候调用BTree.Init()方法先序遍历该树,获得一系列节点数据,比如赋值儿子index,每个节点的儿子index是什么,每个节点的父亲index等,代码如下:

    www.wityx.comwww.wityx.com
     1 --先序遍历,root的index为1、父亲index为0
     2 function BT.BTree:Init(task, parentIndex, compositeIndex)
     3     task:Init(self)
     4     task:OnAwake()
     5     local curIndex = #self.tTaskList + 1
     6     table.insert(self.tTaskList,task) --赋值task的index
     7     table.insert(self.tParentIndex,parentIndex) --可以找到其父亲的index
     8     table.insert(self.tParentCompositeIndex,compositeIndex) --可以找到是Composite类型且离自己最近的祖先的index,用于中断评估
     9 
    10     if task:CheckType(BT.ParentTask) then
    11         if task:CheckChildCount() == false then
    12             LogMgr.Error(BT.ErrorRet.ChildCountMin.." index = "..curIndex.." count = "..#task.tChildTaskList)
    13             return
    14         end
    15         self.tChildrenIndex[curIndex] = {}
    16         self.tChildConditionalIndex[curIndex] = {}
    17         for i = 1, #task.tChildTaskList do
    18             table.insert(self.tRelativeChildIndex,i) --该task在其父亲中处于第几个
    19             table.insert(self.tChildrenIndex[curIndex],#self.tTaskList + 1) --可以找到其所有儿子的index
    20             if task:CheckType(BT.Composite) then
    21                 self:Init(task.tChildTaskList[i], curIndex, curIndex)
    22             else
    23                 self:Init(task.tChildTaskList[i], curIndex, compositeIndex)
    24             end
    25         end
    26     else
    27         if task:CheckType(BT.Conditional) then
    28             --可以找到是Conditional类型且离自己最近的子孙的index,用于中断评估
    29             table.insert(self.tChildConditionalIndex[self.tParentCompositeIndex[curIndex]],curIndex) 
    30         end
    31     end
    32 end
    View Code

    行为树(BTree)中存储着一个list<stack<taskindex>>,这个是运行栈,行为树启动时创建一个运行栈,塞进去树根;每当有并行分支,则创建一个运行栈,塞进去分支第一个运行的节点。

    节点(Task)的状态有四种:

    1、ETaskStatus.Inactive //未激活

    2、ETaskStatus.Failure //失败

    3、ETaskStatus.Success //成功

    4、ETaskStatus.Running //运行中

    运行栈中放的节点都是处于Running状态,update时遍历运行栈,取出栈顶节点执行,如果节点执行完毕后状态不等于running,说明该节点不需要再次运行,那么就出栈,代码如下:

    www.wityx.comwww.wityx.com
     1 function BT.BTree:Update()
     2     --进入评估阶段,中断修改运行栈
     3     self:ConditionalReevaluate()
     4     local status
     5     if #self.tRunStack == 0 then
     6         return BT.ETaskStatus.Inactive
     7     end
     8     --遍历执行所有栈
     9     for i = #self.tRunStack,1,-1 do
    10         repeat
    11             if self.tRunStack[i] == Const.Empty then
    12                 table.remove(self.tRunStack,i)
    13                 break
    14             end
    15             status = BT.ETaskStatus.Inactive
    16             while status ~= BT.ETaskStatus.Running do
    17                 if self.tRunStack[i] ~= Const.Empty then
    18                     status = self:RunTask(self.tRunStack[i]:Peek(),i)
    19                 else
    20                     break
    21                 end
    22             end
    23         until(true)
    24     end
    25     return BT.ETaskStatus.Running
    26 end
    View Code

     节点运行的时候

    如果该节点是ParentTask类型则需要运行儿子,其状态由儿子执行完毕后的状态来决定

    如果该节点是Task类型没有儿子,那么其状态就是其Update的状态

    递归实现那么代码大致如下:

    status task.runParent(){

      for task.childList {

        if typeOf(task.childItem) == ParentTask {

          status = task.childItem.runParent()

        }

        else{

          status = task.childItem.OnUpdate()

        }

        if task.CanExcute(status) == false{

          return task.status

        }

      }

      return task.status

    }

    栈实现虽然麻烦点,但思路还是一样的,多了出栈入栈和其他一些操作

    RunTask()

    www.wityx.comwww.wityx.com
     1 function BT.BTree:RunTask(taskIndex, stackIndex)
     2     local task = self.tTaskList[taskIndex]
     3     self:PushTask(taskIndex,stackIndex)
     4 
     5     local status = BT.ETaskStatus.Inactive
     6 
     7     if task:CheckType(BT.ParentTask) then
     8         status = self:RunParentTask(taskIndex,stackIndex)
     9     else
    10         status = task:OnUpdate()
    11     end
    12 
    13     if status ~= BT.ETaskStatus.Running then
    14         self:PopTask(stackIndex, status)
    15     end
    16     return status
    17 end
    View Code

    RunParent()

    www.wityx.comwww.wityx.com
     1 function BT.BTree:RunParentTask(taskIndex, stackIndex)
     2     local task = self.tTaskList[taskIndex]
     3     local curRelChildIndex = -1
     4     local preRelChildIndex = -1
     5     while task:CanExcute() do
     6         curRelChildIndex = task:GetCurChildIndex()
     7         if curRelChildIndex == preRelChildIndex then
     8             return BT.ETaskStatus.Running
     9         end
    10         local childIndex = self.tChildrenIndex[taskIndex][curRelChildIndex]
    11         if childIndex == nil then
    12             break
    13         end
    14         --这个主要是为并行节点服务的
    15         --其他类型的节点都是儿子执行完毕主动通知父亲然后curChildIndex指向下个儿子
    16         --但是并行节点是所有儿子一开始都同时执行
    17         task:OnChildStart(curRelChildIndex)
    18         if task:CanExcuteParallel() then
    19             --并行节点创建新的分支
    20             local newStack = Stack:New()
    21             table.insert(self.tRunStack, newStack)
    22             newStack:Push(childIndex)
    23             self:RunTask(childIndex, #self.tRunStack)
    24         else
    25             self:RunTask(childIndex, stackIndex)
    26         end
    27         preRelChildIndex = curRelChildIndex
    28     end
    29     return task:GetStatus()
    30 end
    View Code

    节点共享数据

    节点共享数据分为三种:一,树之间任意节点全局共享的数据  二,树内任意节点共享的数据 三,节点内不共享数据

    节点内数据那就不用说了,在节点内声明的数据都是节点内数据

    BehaviorDesigner的共享数据是通过编辑器保存读取的

    由于时间不允许,没有做编辑器,所以我就做了个存储的类简单的实现了下

    Shared.lua就是存储的类,其实里面就是一个table,对外只提供一个GetData(name)的方法,如果没有name的变量就创建个值为空的table保存起来,返回这个table

    结构大致是data = {

    name1 = {name = name1, val = val1},

    ...

    name2 = {name = name2, val = val2},

    }

    之所以用table存,是因为table在lua中属于引用类型

    shared.lua代码如下:

    www.wityx.comwww.wityx.com
     1 BT.Shared = {}
     2 BT.Shared.__index = BT.Shared
     3 
     4 function BT.Shared:New()
     5     local o = {}
     6     setmetatable(o,BT.Shared)
     7     o.data = {} -- val is [name] = {name = name,val = val}
     8     return o
     9 end
    10 
    11 function BT.Shared:GetData(name)
    12     if self.data[name] == nil then
    13         self.data[name] = {name = name,val = nil}
    14     end
    15     return self.data[name]
    16 end
    View Code

    那么全局共享数据放在BTManager中,使得树都可以访问

    树内共享数据放在树中

    在树执行Init时将树传给task

    代码如下:

    www.wityx.comwww.wityx.com
     1 BT.Mgr = {
     2     ...
     3     globalSharedData = BT.Shared:New()
     4 }
     5 
     6 function BT.BTree:New(gameObject, name)
     7     local o = {}
     8     setmetatable(o,this)
     9     ...
    10     o.sharedData = BT.Shared:New()
    11     o.globalSharedData = BT.Mgr.globalSharedData
    12     ...
    13 }
    14 
    15 function BT.BTree:Init(task, parentIndex, compositeIndex)
    16     task:Init(self)
    17     ... 
    18 }
    View Code

    中断的实现

    中断的实现应该是行为树中比较复杂的功能了,涉及到树上的一些算法及运行栈的操作,牵涉到的判断也多,这里会重点讲述

    中断必要的原因是可能存在以下情况(不限于此情况):
    比如怪物正在向目标点移动的时候遇到玩家需要攻击,此时移动的节点状态是running,没有中断的时候只能走到目标点的时候返回success停止移动才开始进入其他节点,这时候就错过了攻击玩家,中断的作用就体现出来了,就算是在running状态也能打断运行栈进入攻击节点

    BehaviorDesigner打断的方法是将打断类型分为这么几类:

    EAbortType = {

      None = 0, //不打断

      Self = 1, //打断自身

      LowerPriority = 2, //打断低优先级

      Both = 3, //同时包括Self和LowerPriority两种效果

    }

    其中只有Composite类型的节点可以拥有打断操作

    Self打断类型:指的是Composite节点下面的直系子孙(这个名词是我临时取得。。意思是Composite与Conditional中间可以插入Decorate,可以插入Composite但插入得Composite类型必须是Self或Both)Conditional类型的节点的状态发生变化时,那么打断正在运行且是Composite子孙的分支,重新进入变化的Conditional节点所处的分支中。打断的结构大概如下图所示:

    (绿色的指正在运行中的节点)

    那么Conditional变化时可以打断Task2,当然如果Task1处于运行时也可以打断,因为Composite2的打断类型为Self且Composite3的打断类型也是Self,即AbortType是可以递归的

    运行栈回退到Composite和运行节点的最近公共祖先节点,在此图中回退到Composite3节点处。

    假设Composite2的打断类型是None或者LowerPriority且正在运行的是Task1,那么就不会打断

    LowerPriority打断类型:指的是当Composite直系子孙(意思同上,只是插入的Composite必须是LowerPriority或Both)Conditional类型的节点的状态变化时,那么打断运行节点所处分支是Composite兄弟节点分支,打断结构如下图所示:

     假设正在运行的是Task1,那么不可以打断

    通过以上的例子,我们可以知道,从Composite到Conditional是一条链也就是打断链,将打断链存储下来,每次树update的时候先判断运行分支是否和打断链上的节点处于同一分支,那么就可以打断

    Self打断链和LowerPriority的打断链如图所示:(当运行分支处于红色节点分支中,则可以打断)

     当然还有一种情况例外,比如并行节点Parallel,也属于Composite,但是两个子分支之间是不能打断的,如下图所示,不能打断Task,因为Conditional和Task是两个运行栈

    通过上面所说,我们只需要算出打断链算出来并存储下来进行计算即可。

    只有Conditional进行过一次才可以进行中断评估

    那么在节点出栈的时候进行计算,将打断链接上自己的父亲,或者删除打断链,代码如下所示:

    www.wityx.comwww.wityx.com
     1 function BT.BTree:PopTask(stackIndex, status)
     2     ...
     3     ...
     4     --reevaluate
     5     local parentCompositeIndex = self.tParentCompositeIndex[taskIndex]
     6     local parentComposite = self.tTaskList[parentCompositeIndex]
     7 
     8     --如果节点是Conditional类型且父亲Composite有中断类型,那么创建中断链保存起来
     9     if task:CheckType(BT.Conditional) then
    10         if  parentComposite ~= nil and parentComposite.abortType ~= BT.EAbortType.None then
    11             if self.tConditionalReevaluateDic[taskIndex]  == nil then
    12                 local reevaluate = BT.Reevaluate:New(taskIndex, status, stackIndex, parentComposite.abortType == BT.EAbortType.LowerPriority and 0 or parentCompositeIndex, parentComposite.abortType)
    13                 table.insert(self.tConditionalReevaluate,reevaluate)
    14                 self.tConditionalReevaluateDic[taskIndex] = reevaluate
    15             end
    16         end
    17     elseif task:CheckType(BT.Composite) then
    18 
    19         repeat
    20             --LowerPriority延迟指向
    21             if task.abortType == BT.EAbortType.LowerPriority then
    22                 for i = 1, #self.tChildConditionalIndex[taskIndex] do
    23                     local reevalute = self.tConditionalReevaluateDic[self.tChildConditionalIndex[taskIndex][i]]
    24                     if reevalute ~= nil then
    25                         reevalute.compositeIndex = taskIndex
    26                     end
    27                 end
    28             end
    29 
    30             --指向自己的reevalute重新指向自己的父亲
    31             local lam_BothOrOther = function(tab,abortType)
    32                 if tab.abortType == abortType or tab.abortType == BT.EAbortType.Both then
    33                     return true
    34                 end
    35                 return false
    36             end
    37 
    38             for i = 1, #self.tConditionalReevaluate do
    39                 local reevalute = self.tConditionalReevaluate[i]
    40                 if reevalute.compositeIndex == taskIndex then
    41                     if lam_BothOrOther(task,BT.EAbortType.Self) and lam_BothOrOther(parentComposite,BT.EAbortType.Self) and lam_BothOrOther(reevalute,BT.EAbortType.Self) or
    42                             lam_BothOrOther(task,BT.EAbortType.LowerPriority) and lam_BothOrOther(reevalute,BT.EAbortType.LowerPriority)
    43                     then
    44                         reevalute.compositeIndex = parentCompositeIndex
    45                         if reevalute.abortType == BT.EAbortType.Both then
    46                             if task.abortType == BT.EAbortType.Self or parentComposite.abortType == BT.EAbortType.Self then
    47                                 reevalute.abortType = BT.EAbortType.Self
    48                             elseif task.abortType == BT.EAbortType.LowerPriority or parentComposite.abortType == BT.EAbortType.LowerPriority then
    49                                 reevalute.abortType = BT.EAbortType.LowerPriority
    50                             end
    51                         end
    52                     end
    53                 end
    54             end
    55 
    56             --自己已经出栈,删除目前还指向自己的中断链
    57             for i = #self.tConditionalReevaluate,1,-1  do
    58                 local reevalute = self.tConditionalReevaluate[i]
    59                 if reevalute.compositeIndex == taskIndex then
    60                     self.tConditionalReevaluateDic[reevalute.index] = nil
    61                     table.remove(self.tConditionalReevaluate,i)
    62                 end
    63             end
    64         until(true)
    65 
    66     end
    67     if stack:Empty() then
    68         self.tRunStack[stackIndex] = Const.Empty
    69     end
    70     task:OnEnd()
    71 end
    View Code

     经过上一步就计算并保存了中断链,接下来就是打断操作,代码如下所示:

    www.wityx.comwww.wityx.com
     1 --遍历所有的中断链
     2 function BT.BTree:ConditionalReevaluate()
     3     for i = 1, #self.tConditionalReevaluate do
     4         repeat
     5             local reevalute = self.tConditionalReevaluate[i]
     6             if reevalute == nil or reevalute.compositeIndex == 0 then
     7                 break
     8             end
     9             local status = self.tTaskList[reevalute.index]:OnUpdate()
    10             if status == reevalute.status then
    11                 break
    12             end
    13             --打断
    14             local bBreak = false
    15             for stackIndex = 1, #self.tRunStack do
    16                 repeat
    17                     if self.tRunStack[stackIndex] == Const.Empty then
    18                         break
    19                     end
    20                     local runIndex = self.tRunStack[stackIndex]:Peek()
    21                     local lcaIndex = self:LCA(reevalute.compositeIndex,runIndex)
    22                     --只有在reevaluate打断链上的运行节点才能被打断
    23                     if not self:IsParent(reevalute.compositeIndex,lcaIndex) then
    24                         break
    25                     end
    26                     --如果运行节点和reevaluate的conditional处于同一个并行节点的不同分支上,不能被打断
    27                     if stackIndex ~= reevalute.stackIndex and self.tTaskList[self:LCA(reevalute.index,runIndex)]:CanExcuteParallel() then
    28                         break
    29                     end
    30 
    31                     if reevalute.abortType == BT.EAbortType.LowerPriority and self.tParentCompositeIndex[reevalute.index] == self.tParentCompositeIndex[runIndex] then
    32                         break
    33                     end
    34 
    35                     --更改运行栈
    36                     while true do
    37                         if self.tRunStack[stackIndex] == Const.Empty or self.tRunStack[stackIndex]:Empty() then
    38                             break
    39                         end
    40                         runIndex = self.tRunStack[stackIndex]:Peek()
    41                         if runIndex == lcaIndex then
    42                             self.tTaskList[lcaIndex]:OnConditionalAbort()
    43                             break
    44                         end
    45                         self:PopTask(stackIndex,BT.ETaskStatus.Inactive)
    46                     end
    47                     bBreak = true
    48                 until(true)
    49             end
    50 
    51             if not bBreak then
    52                 break
    53             end
    54             --删除同一个中断链且优先级较低的reevalute
    55             for j = #self.tConditionalReevaluate, i,-1 do
    56                 local nextReevalute = self.tConditionalReevaluate[j]
    57                 if self:IsParent(reevalute.compositeIndex,nextReevalute.index) then
    58                     self.tConditionalReevaluateDic[nextReevalute.index] = nil
    59                     table.remove(self.tConditionalReevaluate,j)
    60                 end
    61             end
    62         until(true)
    63     end
    64 end
    View Code

    至此,行为树讲述完毕

    展开全文
  • #概述BehaviorTree组件编辑器是Egret Pro为用户提供的一个直观、可视化AI行为树编辑器,可以创建复杂的AI行为并且无需编写代码。使用行为树编辑器,您不需要知道基础行为树实现机制,只需了解例如动作,复合,条件...
  • 二、行为树的原理和实现

    千次阅读 2014-10-03 18:18:59
    使用BTEditor可以生成行为树的Lua代码,这里对生成的代码进行解析。 (BTEditor的项目主页:https://github.com/bartoleo/BTEditor) 要注意: 1、行为树每个节点都需要向其父节点返回一个值,以允许父节点根据子...
  • ### 简单版行为树(BehaviorTree)实现 2018/11/25更新:这篇博客只是对行为树的简单说明,不能按照下面说的这样来写代码,简直太蠢了. 1.代码是火箭式,写的时候要盯对括号,不然容易写歪 2.不易调试,加了打印信息代码又会...
  • 做敌人AI总是伴随着复杂的逻辑顺序,用代码控制改来改去头都裂了,学习一下行为树插件.插件:behavior manager+Movement 学习完基本操作后,发现行为树用起来还是很爽的. 基本操作不多说,主要记录一下一些使用上的...
  • 春节前,收到一个网友的邮件,说看了行为树的一些东西,但还是不知道如何去入手实现,我就乘着春节假期,动手写了一个简单的行为树的库,和大家一起边分析代码,边说说行为树的具体实现方法。这个库很简单,一共也就...
  • Java游戏服务器开发之行为树

    万次阅读 2018-05-25 16:39:07
    Java游戏服务器开发之行为树 之前有看到过状态机、行为树这一块的内容,但是没有认真细看,现在终于静下心来认真看了看,就看了别人的实现方式(网上代码没有Java实现的), 然后使用Java语言实现了一下 运行之后,在...
  • 应用,需要给行为树的需求分类,掌握不同类型的需要最适合的行为树实现 通用的行为树工具,能力范围,扩展方式 特定游戏类型的行为树工具,能力范围,扩展方式 实现,各类行为树实现方式 1.通用的代码组织...
  • 行为树 Behavior Tree 实例 五

    千次阅读 2018-01-18 19:11:46
    行为树 Behavior Tree 实例 前篇完成了 Behavior Tree 的核心代码 下面通过实际例子使用Unity来测试核心代码 下面是行为树结构图 本篇实现 选择节点 1 的所有内容 执行流程如下 遍历根节点 ...
  • 行为树从本质上来说,是一颗逻辑树,它把所有的行为逻辑用树形结构串联起来,仔细观察的话,可以发现行为树的核心思想有三个方面: 逻辑分离 逻辑关联 逻辑抽象 听上去很玄乎,其实是很简单的东西,可以先想想我们...
  • 一个简单的游戏框架:行为树设计

    千次阅读 2018-07-15 21:03:53
    代码:https://github.com/HushengStudent/myGameFramework这里主要是介绍一下自己写的一个简单的行为树。1、行为树?①行为树是常见的游戏ai解决方案;常见做法就是程序实现好工具,由策划配置实现ai功能;这里就...
  • 解释器设计模式对于指定如何使用类来表示语句并构建语法来评估语言表达式非常有用。 解释器设计模式也使用复合设计模式。解释器的一些常见用途是语言解析,协议等。 示例类图创建语言和语法是一项复杂的任务,在...
  • 游戏AI之行为树(上)

    千次阅读 2016-04-01 14:40:16
    我看的很多手册都偏重于具体代码实现,或者简单地基于通用的节点的工作流,都没有实际的实现案例,就像下面这张图: 因为那些教程对于我理解行为树的核心规则没有用处,我发现我尽管知道行为树是如何操作...
  • 我看的很多手册都很严重地依赖于具体代码实现,或者简单地基于通用的节点的工作流,都没有实际的实现案例,就像下面这张图: 因为那些教程对于我理解行为树的核心规则没有用处,我发现我尽管知道行为树是如何...
  • unity简单实现融合动画

    千次阅读 2018-12-15 14:30:36
    本篇博客将简单介绍用状态机制作行为树动画,我将用这个行为树去简单的控制人物移动,待机和后退,尽量用最少的代码实现丰富的功能。 行为树:又称为混合树,一个流行的AI动画技术,涵盖了层次状态机,事件调度,...
  • 树实现客户端红点系统红点系统总览demo 设计代码设``` 引用:https://zhuanlan.zhihu.com/p/85978429 红点系统总览 如上图所示,规划红点系统的时候,我们将整个系统分为独立的三个部分:结构层、驱动层和表现层。 ...
  • cocos creator中使用行为树(BehaviorTree) 六 本节代码在我的github上:https://github.com/kirikayakazuto/BehaviorTreeGame 继续完善我们的游戏, 这一节我们实现 控制玩家攻击, 防守 敌人自动防御 先把控制玩家...
  • 由于机器人AI代码可以快速增长,因此以有限状态机方式编写此代码可以帮助使代码库易于管理并提高机器人行为树的质量。 它是什么? Mineflayer-StateMachine是Mineflayer的插件。 它旨在在Mineflayer之上添加一个...
  • Shader Forge是一款基于节点的着色器编辑器,开发者只需在编辑器中拖拽自己需要的效果进行组合,无需编写代码即可实现炫酷的着色器特效,并且支持实时预览着色器效果。 Shader Forge使用起来也很方便,首先依次...
  • 机器学习是一门多领域交叉学科,涉及...专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。 它是人工智能的核心,是使计算机具有智能的根本途径。

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 267
精华内容 106
关键字:

行为树代码实现