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 != || 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行为树基础总结的更多相关文章
- Poj 3246 Balanced Lineup(线段树基础)
依旧是线段树基础题 询问区间的最大值和最小值之差,只有询问,没有插入删除.继续理解基础线段树 #include <iostream> #include <algorithm> ...
- 《Unity 3D游戏客户端基础框架》概述
框架概述: 做了那么久的业务开发,也做了一年多的核心战斗开发,最近想着自己倒腾一套游戏框架,当然暂不涉及核心玩法类型和战斗框架,核心战斗的设计要根据具体的游戏类型而定制,这里只是一些通用的基础系统的框 ...
- P1198 [JSOI2008]最大数(线段树基础)
P1198 [JSOI2008]最大数 题目描述 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值. 限制: ...
- Unity 行为树-基础
.前言 Unity里面的行为树又名BehaviorTree,最常用在NPC的敌人逻辑中. 二.基础说明(转载) 1.行为树的调用时间为每帧: 2.每个节点的状态只能下面3个中的其一:成功Success ...
- Unity进阶:行为树 01
版权申明: 本文原创首发于以下网站: 博客园『优梦创客』的空间:https://www.cnblogs.com/raymondking123 优梦创客的官方博客:https://91make.top ...
- HDU 1754 I Hate It(线段树基础应用)
基础线段树 #include<iostream> #include<cstdio> #include<cstring> using namespace std; # ...
- 关于Unity中Shader的基础认识
Shader也叫着色器,是Unity里面比较难的一个点,网上有很多别人写好的shader,我们可以下载下来用或者修改学习. Shader可以做出很多非常不错的效果,因为它是插在渲染管道里面的程序,一来 ...
- 【Unity Shader】Shader基础
目录 Chapter3 Unity Shader 基础 Chapter3 Unity Shader 基础 概述 在Unity需要材质(Material)与Unity Shader配合使用来达到满意的效 ...
- 《Unity 3D游戏客户端基础框架》消息系统
功能分析: 首先,我们必须先明确一个消息系统的核心功能: 一个通用的事件监听器 管理各个业务监听的事件类型(注册和解绑事件监听器) 全局广播事件 广播事件所传参数数量和数据类型都是可变的(数量可以是 ...
随机推荐
- Centos8尝鲜
Centos 8阿里云下载地址https://mirrors.aliyun.com/centos/8.0.1905/isos/x86_64/ Centos8的一些变化 网络服务: 在/etc/sysc ...
- django重点url,视图函数,模板语言
django重点url,视图函数,模板语言url 1.django重点url无命名分组:re_path() 2.url第一个参:url未命别名分组就不需要views中参数一定,若命别名(?P<y ...
- Vue 小练习01
有红, 黄, 蓝三个按钮, 以及一个200X200px的矩形box, 点击不同的按钮, box的颜色会被切换为指定的颜色 <!DOCTYPE html> <html lang=&qu ...
- ETCD:gRPC代理
原文地址:gRPC proxy gRPC代理是在gRPC层(L7)运行的无状态etcd反向代理.代理旨在减少核心etcd群集上的总处理负载.对于水平可伸缩性,它合并了监视和租约API请求. 为了保护集 ...
- HTML中的三元表达式,灵活的使用or逻辑判断
08.27自我总结 HTML中的三元表达式 判断内容 ? 满足返回的值 : 不满足返回的值 灵活使用or逻辑判断 比如我们某个变量为空的时候返回他另外个值 var a = msg || '没有消息'
- Oracle数据库之第一篇
1 : Oracle 简介 : 是美国ORACLE公司(甲骨文)提供的以分布式数据库为核心的一组软件产品,是目前最流行的客户/服务器IP,端口,用户名.密码,点击:连接 (CLIENT/SERVER) ...
- JS中的call,apply和bind及记忆方式
总结 call().apply()和bind()都是用来改变函数执行时的上下文,可借助它们实现继承:call()和apply()唯一区别是参数不一样,call()是apply()的语法糖:bind() ...
- layui table 分页 序号始终从”1“开始解决方法
在用Layui table 分页显示数据,用 type:"numbers" 进行显示序号有以下的问题 1.表格自带的分页,page:true 这种分页,在切换页面的时候序号可以正常 ...
- Java反射04 : 通过Array动态创建和访问Java数组
java.lang.reflect.Array类提供了通过静态方法来动态创建和访问Java数组的操作. 本文转载自:https://blog.csdn.net/hanchao5272/article/ ...
- Andorid Studio 新建模拟器无法联网问题
1.查看自己本机的dns cmd -> ipconfing /all 2.修改模拟器的dns 跟PC本机一致. 开启模拟器 -> cmd -> adb root (需要root ...