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 != || inputZ != )
{
Vector3 movement = new Vector3(inputX, , 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 > )
{
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 <= )
{
enemy.Dead();
return TaskStatus.Success;
}
}
return TaskStatus.Running;
}
}

Unity BehaviorDesigner行为树基础总结的更多相关文章

  1. Poj 3246 Balanced Lineup(线段树基础)

    依旧是线段树基础题 询问区间的最大值和最小值之差,只有询问,没有插入删除.继续理解基础线段树 #include <iostream> #include <algorithm> ...

  2. 《Unity 3D游戏客户端基础框架》概述

    框架概述: 做了那么久的业务开发,也做了一年多的核心战斗开发,最近想着自己倒腾一套游戏框架,当然暂不涉及核心玩法类型和战斗框架,核心战斗的设计要根据具体的游戏类型而定制,这里只是一些通用的基础系统的框 ...

  3. P1198 [JSOI2008]最大数(线段树基础)

    P1198 [JSOI2008]最大数 题目描述 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值. 限制: ...

  4. Unity 行为树-基础

    .前言 Unity里面的行为树又名BehaviorTree,最常用在NPC的敌人逻辑中. 二.基础说明(转载) 1.行为树的调用时间为每帧: 2.每个节点的状态只能下面3个中的其一:成功Success ...

  5. Unity进阶:行为树 01

    版权申明: 本文原创首发于以下网站: 博客园『优梦创客』的空间:https://www.cnblogs.com/raymondking123 优梦创客的官方博客:https://91make.top ...

  6. HDU 1754 I Hate It(线段树基础应用)

    基础线段树 #include<iostream> #include<cstdio> #include<cstring> using namespace std; # ...

  7. 关于Unity中Shader的基础认识

    Shader也叫着色器,是Unity里面比较难的一个点,网上有很多别人写好的shader,我们可以下载下来用或者修改学习. Shader可以做出很多非常不错的效果,因为它是插在渲染管道里面的程序,一来 ...

  8. 【Unity Shader】Shader基础

    目录 Chapter3 Unity Shader 基础 Chapter3 Unity Shader 基础 概述 在Unity需要材质(Material)与Unity Shader配合使用来达到满意的效 ...

  9. 《Unity 3D游戏客户端基础框架》消息系统

    功能分析: 首先,我们必须先明确一个消息系统的核心功能: 一个通用的事件监听器 管理各个业务监听的事件类型(注册和解绑事件监听器) 全局广播事件 广播事件所传参数数量和数据类型都是可变的(数量可以是 ...

随机推荐

  1. MySQL数据库~~~~ 完整性约束

    1. not null 与 default not null : 不可空 default : 默认值 例: create table t1(id int not null default 2); 2. ...

  2. [MySQL] 使用force index强制使用索引

    1.在测试一个按照时间的范围查询时,尽管增加了索引,发现使用不到索引,可以使用这个来强制使用索引 测试过程为,创建下面的表,以及创建了联合索引 create table delay_delete_us ...

  3. Nginx 简介与安装、常用的命令和配置文件

    1.nginx 简介(1)介绍 nginx 的应用场景和具体可以做什么事情 (2)介绍什么是反向代理 (3)介绍什么是负载均衡 (4)介绍什么是动静分离 2.nginx 安装(1)介绍 nginx 在 ...

  4. Unity《ATD》塔防RPG类3D游戏架构设计(一)

    目录 <ATD> 游戏简介 <ATD> 整体结构 <ATD> 游戏机制 Buff机制 Skill机制(技能机制) 仇恨机制 <ATD> 游戏模型 策划案 ...

  5. nginx(4)

    目录 一.安装配置 1.安装 2.配置文件 3.测试和启动 二.功能 1.虚拟主机 1.1 基于IP 1.2 基于域名 1.3 基于端口 2.访问控制 3.用户认证 4.文件共享 5.文件别名 6.状 ...

  6. <挑战程序设计竞赛> poj 3320 Jessica's Reading Problem 双指针

    地址 http://poj.org/problem?id=3320 解答 使用双指针 在指针范围内是否达到要求 若不足要求则从右进行拓展  若满足要求则从左缩减区域 代码如下  正确性调整了几次 然后 ...

  7. (day67)组件、组件化、组件传参、JS补充(命名转换、for in 、数据转换)、css取消选中和模拟小手

    目录 一.初识组件 (一)概念 (二)特点 二.组件的分类 (一)根组件 (二)局部组件 (三)全局组件 二.数据组件化 三.组件的传参 (一)父传子 (二)子传父 四.JS补充 (一)与html命名 ...

  8. 几行代码轻松实现PHP文件打包下载zip

    <?php //获取文件列表 function list_dir($dir){ $result = array(); if (is_dir($dir)){ $file_dir = scandir ...

  9. docker cp命令出错问题

    docker cp 4e6:/etc/nginx/nginx.conf /home/nginx/conf 使用docker在复制官方nginx容器的conf文件时,发生了错误. Error respo ...

  10. 立 Flag

    行动目标 立Flag时间 计划开始时间 开始时间 行动寄语 通关目标 打卡1 打卡2 打卡3 打卡4 打卡5 打卡6 C# 7.0 核心技术指南 2019-11-1 1号 1号 打好C#基础 看完.实 ...