zoukankan      html  css  js  c++  java
  • Unity BehaviorDesigner行为树基础总结

    BehaviorDesigner——行为树,用于控制和实现AI逻辑,类似于这样:

    上面这个行为树实现了这样的逻辑:

    当Player有Input时按照Input值来移动,无Input时查找最近的可攻击目标,如果能找到就执行攻击;当既没有Input也没有找到攻击目标时,那就一直处于Idle状态。

    下面总结BehaviorDesigner最常见的基础知识:

    首先要明确一个行为树必须有一个依赋对象,它诠释的是该对象的一系列行为模式。

    这些行为模式由Task节点构成,图中的每一个可执行的方框就是一个Task节点,将这些节点按照设计的逻辑进行连接,就组成了该对象的行为树。

    行为树从根节点开始,从上至下,从左至右依次执行其下每一Task节点,任何被执行的Task将返回一种状态,当根节点Task返回成功(或失败)状态时,意味着该行为树单次执行结束。

    Task有以下状态:

    正在执行,执行成功,执行失败,未激活。

    任何Task都处于这几种状态之一。

    Task分为不同类型,每种类型的Task作用各不相同,首先必须弄清楚每类Task的大致作用:

    Composites(复合类):主要用于控制行为树的走向,也是用的最多最重要的一类,任何一个相对复杂的行为树都包含这类Task节点,但它本身不做任何具体行为,所以它们一般位于父节点或根节点。

    Decorators(装饰类):多用于对其下的子Task节点执行额外操作,例如反转结果,重复执行等。

    Actions(行为类):数量最多,为具体执行行为的Task,一般位于行为树的叶子节点右侧,该类Task可能并非单帧就能完成。没必要每个Action都搞清楚,因为可以很容易的自己扩展Action。后面会具体介绍如何扩展。

    Conditionals(条件类):一般放在Action节点左侧进行约束,只有当条件满足(或不满足)时才继续往下执行,单帧内完成一次判断。更多时候配合复合节点进行打断或任务跳转,这一点后面会详细说明。

    最基础最常用的复合类Task是下面两个:

    Selector(选择):相当于Or操作,下面的子Task节点只要有一个返回成功了它就返回成功,只有当所有的都返回失败了才返回失败。

    Sequence(序列):相当于And操作,下面的子Task节点只要有一个返回失败了它就返回失败,只有当所有的都返回成功了才返回成功。

    其余的只是在这两个的基础上变形,例如子节点同时执行或随机顺序执行等,具体可看官方文档说明。

    复合类Task的优先级和打断:

    这一点非常重要,是复合类Task的唯一特殊属性:分为四种——不打断,可打断自身,可打断低于该Task优先级的其他Task,既可以打断自身也可以打断低于其优先级的。

    需要注意的是,该复合节点的打断条件是其下子节点必须有条件节点,此时该条件节点的判断一直处于运行状态,一旦该条件节点在某一刻发生改变,此时行为树将重新跳转到该复合节点位置继续运行,从而打断其他正在运行的低优先级节点。

    例如最上面的行为树中,Player通过判断是否接入Input移动指令可以打断比它优先级低的攻击节点和Idle节点的运行,而攻击节点可以打断Idle节点。

    这意味着,当Player处于Idle状态时一旦成功找到敌人,那么就变为攻击状态,攻击或Idle状态时一旦接受到Input指令又跳转到移动;只有当既没有Input指令输入又没有查找到敌人时,才Idle。

    一旦某一时刻比它优先级高的节点满足了前置条件,就会跳转执行优先级高的。

    所以在设计行为树时,一般会把优先级高的Task节点置于行为树的左侧,将优先级低的置于右侧,因为复合节点并不能打断比该它优先级高的Task节点。

    开启了Abort Type后Task方框的左上角会出现向右或向下的箭头作为标志提示。

    自定义Task任务:

    一般复合类和装饰类的Task是够用的,甚至有些根本用不到,而具体的行为类Task和条件类Task从来都不能满足我们的需求,而且自己写这类Task可以很大程度的简化整个行为树结构。

    自己写Task的步骤如下:

    1.引入命名空间:

    using BehaviorDesigner.Runtime;
    using BehaviorDesigner.Runtime.Tasks;
    

    2.明确继承的Task类型:

    public class MyInputMove : Action
    public class MyIsInput : Conditional
    

    3.知晓Task内部函数的执行流程:(可以看官方文档,写的很清楚,这里把最重要的一张图贴上来)

    观察上图就会发现和Unity中编写脚本大同小异,不一样的地方就是这里的Update有返回值,要返回该任务的执行状态,只有在Running状态时才每帧调用。

    using UnityEngine;
    using BehaviorDesigner.Runtime;
    using BehaviorDesigner.Runtime.Tasks;
    
    public class MyInputMove : Action
    {
        public SharedFloat speed = 5f;
        public override TaskStatus OnUpdate()
        {
            float inputX = Input.GetAxis("Horizontal");
            float inputZ = Input.GetAxis("Vertical");
            if (inputX != 0 || inputZ != 0)
            {
                Vector3 movement = new Vector3(inputX, 0, inputZ);
                transform.Translate(movement*Time.deltaTime*speed.Value);
                return TaskStatus.Running;
            }
            return TaskStatus.Success;
        }
    }

    还有一点不同,就是Task中封装了一层Share的类型的属性,它和非Share类型有何区别呢?

    下面是对比:

    using UnityEngine;
    using BehaviorDesigner.Runtime.Tasks;
    using BehaviorDesigner.Runtime;
    
    public class MyLog : Action
    {
        public string staticLog;
        public SharedString shareLog;
    
        public override TaskStatus OnUpdate()
        {
            Debug.Log(staticLog + shareLog.Value);
            return TaskStatus.Success;
        }
    }

    可以看到,这里的Share的类型就是一个方便在行为树中传递和修改的变量,因为Task之间是不方便直接修改其他Task变量的,那怎么办呢,于是就增加一种Share的类型变量在行为树的各个Task之间进行交流。

    比如这里,每次找到的最近的敌人是不一样的,要根据上一个Task返回的值去执行下一个Task的攻击或打印结果,这时固定的属性就无法满足要求,但直接调用别的Task又增加了耦合性,于是就单独用Share变量来传递值。这样也方便在面板中统一查看管理。

    另外Share变量也可以增加自定义类型,全局的和本地变量的区别就是一个在所有的行为树中有,一个只有这棵树中有。

    上面就是将查找到的最近的敌人和名字返回,其他Task例如攻击和打印时直接就可以取到这里返回的值。在取Share变量值时需要.Value。

    using UnityEngine;
    using BehaviorDesigner.Runtime;
    using BehaviorDesigner.Runtime.Tasks;
    
    public class MyCanFindLatestTarget : Conditional
    {
        public SharedGameObjectList origins;
        public bool bUseTag = false;
        public SharedString tag;
        public SharedGameObject result;
        public SharedString resultName;
    
        public override void OnStart()
        {
            if (bUseTag)
            {
                origins.Value.Clear();
                origins.Value.AddRange(GameObject.FindGameObjectsWithTag(tag.Value));
            }
        }
        public override TaskStatus OnUpdate()
        {
            if (origins.Value.Count > 0)
            {
                float minDistance=Mathf.Infinity;
                GameObject minDistanceObj=null;
                foreach(var item in origins.Value)
                {
                    if (item == null)
                        continue;
                    float sqrDistance = (item.transform.position - transform.position).sqrMagnitude;
                    if (sqrDistance < minDistance)
                    {
                        minDistance = sqrDistance;
                        minDistanceObj = item;
                    }
                }
                result.Value = minDistanceObj;
                resultName.Value = minDistanceObj.name;
                return TaskStatus.Success;
            }
            return TaskStatus.Failure;
        }
    }
    using UnityEngine;
    using BehaviorDesigner.Runtime.Tasks;
    using BehaviorDesigner.Runtime;
    
    public class MyAttack : Action
    {
        public SharedGameObject target;
    
        public SharedFloat damage=10f;
        public SharedFloat attackSpeed = 10f;
    
        private Enemy enemy;
        private float timer;
    
        public override void OnStart()
        {
            if (target.Value != null)
            {
                enemy = target.Value.GetComponent<Enemy>();
            }
            timer = 0f;
        }
        public override TaskStatus OnUpdate()
        {
            if (enemy == null)
                return TaskStatus.Failure;
            timer += Time.deltaTime;
            if (timer > (10f /attackSpeed.Value))
            {
                timer = 0f;
                enemy.Hit(damage.Value);
                Debug.Log(target.Value.name + "剩余HP:" + enemy.hp);
                if (enemy.hp <= 0)
                {
                    enemy.Dead();
                    return TaskStatus.Success;
                }
            }
            return TaskStatus.Running;
        }
    }
  • 相关阅读:
    洛谷 1850 NOIP2016提高组 换教室
    2018牛客多校第三场 C.Shuffle Cards
    2018牛客多校第一场 B.Symmetric Matrix
    2018牛客多校第一场 A.Monotonic Matrix
    2018牛客多校第一场 D.Two Graphs
    2018宁夏邀请赛L Continuous Intervals
    2018宁夏邀请赛K Vertex Covers
    BZOJ
    HDU
    ACM International Collegiate Programming Contest, Egyptian Collegiate Programming Contest (ECPC 2015)
  • 原文地址:https://www.cnblogs.com/koshio0219/p/11734423.html
Copyright © 2011-2022 走看看