Unity BehaviorDesigner行为树基础总结

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

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

当物体有Input时按照Input来移动,无Input时查找最近的可攻击目标,将找到的目标打印出来;如果既没有Input也没有找到攻击目标,那就一直处于Idle状态。

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

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

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

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

Task有以下状态:

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

每一个Task都必须处于这几种状态之一。

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

Composites(复合类):这类Task主要控制行为树的走向,也是用的最多最重要的一类,任何一个相对复杂的行为树都包含这类Task,但它本身不做任何具体行为,所以它们的下面一般需要有子节点来执行具体操作。

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

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

Conditionals(条件类):一般放在Action类Task前进行约束,只有当条件满足(或不满足)时才继续往下执行,只在单帧内完成一次判断。

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

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

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

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

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

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

可以把它理解为Task跳转或重新开始。例如最上面的图中,对象通过Input移动可以打断比它优先级低的Task查找敌人和Idle状态,查找敌人可以打断Idle状态,这意味着,当该对象处于Idle状态时若成功找到敌人,那么就变为攻击,当正在攻击敌人时接受到输入指令时又跳转到移动,只有当既没有Input指令输入又没有查找到敌人时,才Idle,一旦某一时刻比它优先级高的Task满足了前置条件,就会跳转执行优先级高的。

所以在设计行为树时,一般把优先级高的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;
        return TaskStatus.Failure;
    }
}

还有一点不同,就是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之间是不方便直接调用和修改变量的,那怎么办呢,于是就增加一种Share的类型变量在行为树的各个Task之间进行交流。

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

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

上面就是将查找到的最近的敌人和名字返回,其他Task例如打印时直接就可以取到这里返回的值。

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

public class MyFindLatestTarget : Action
{
    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;
    }
}

在取Share变量的值时需要.Value。

猜你喜欢

转载自www.cnblogs.com/koshio0219/p/11734423.html