行为树介绍

行为树是个节点树,父节点通过不断遍历子节点,根据不同类型的节点执行不同的分支。最终调用叶节点执行功能。行为树也不难理解,他就像代码逻辑一样,只是用节点的方式展现出来,而且比代码更直观。如果行为树中写有各种行为功能的节点的话,即便没有写过代码的,稍微学习一下,只用行为树也可以做出具有一定的智能行为的角色。

行为树从上到下,从左到右执行。

行为树采用节点描述行为逻辑。

主要有:选择节点、顺序节点、并行节点、修饰节点、随机节点、条件节点、行为节点。

一棵行为树表示一个AI逻辑。要执行这个 AI 逻辑,需要从根节点开始遍历整棵树,遍历执行的过程中,父节点根据自身的类型,确定需要如何执行、执行哪些子节点并继续执行,子节点执行完毕后,会将执行结果反馈给父节点。

  • 行为树 Behavior Tree 原理 一

如图,可大致看出角色的行为逻辑。而且添加更多行为时,只用在节点中再添加即可,可扩展性非常高。

执行结果

节点执行后有三种结果:

  • SUCCEED(执行成功)
  • FAILED(执行失败)
  • RUNNING(正在执行)

节点类别

  • Composite(组合节点)
    组合节点用来控制树的遍历方式,每种组合节点的遍历方式都不相同。一般有以下几个节点。
    如果结果为 RUNNING,则下一帧仍从这个节点开始运行。

    • Sequence(顺序节点):按照节点顺序执行,如果有一个结果为 FAILED,则中断执行,返回 FAILED,类似于“逻辑与(And)”。
    • Selector(选择节点):按照节点顺序执行,如果有一个结果为 SUCCEED,则中断执行,返回 SUCCEED,类似于“逻辑或(Or)”。
    • Parallel(并行节点):子节点中有一个结果为 FAILED,则中断执行返回 FAILED
      如果有一个结果为 RUNNING,则执行完返回 RUNNING
      全部结果都为 SUCCEED,结果返回 SUCCEED
  • Decorator(修饰节点):修饰节点(Decorator)修饰节点不能独立存在,其作用为对子节点进行修饰,以得到我们所希望的结果.
    修饰节点有很多种,其中有一些是用于决定是否允许子节点运行的,也叫过滤器,例如 Until Success, Until Fail 等,首先确定需要的结果,循环执行子节点,直到节点返回的结果和需要的结果相同时向父节点返回需要的结果,否则返回 RUNNING

    • Inverter(反转):任务执行结果如果为 SUCCEED,则结果转为 FAILED;任务执行结果如果为 FAILED,则结果转为 SUCCEED;结果为 RUNNING 则不变。
    • UntilSuccess(直到成功):一直执行,返回 RUNNING,直到结果为 SUCCEED
    • UntilFail(直到失败):一直执行,返回 RUNNING,直到结果为 FAILED
    • Counter(计数):重复执行子节点多次。
    • …(可以自行设计其他更多符合自己需求的Decorator节点)
  • Leaf(叶节点):对叶节点进行重写,以进行逻辑判断和功能的执行。

    • Condition(条件节点):判断条件是否成立,只返回 SUCCEEDFAILED 这两种状态。
    • Action(行为节点):控制节点,执行各种功能。

其他类

  • Blackboard(黑板):存储节点树中的全局数据。
  • Root(行为树根节点):用来运行整个行为树。

节点脚本

开始进行节点的设计,我们先在 文件系统 中创建一个 src 文件夹,我们之后创建的脚本都放在这个文件夹里

通过上面的信息,我们可以添加任务结果枚举,如下的 enum{} 内容,添加 _task() 方法执行任务,并返回执行结果。剩下添加 rootactor 用于可能会操作用到的变量。

行为树节点的基类

脚本名:BT_Node.gd

## BTNode 行为树的基类节点
extends Node## 任务执行结果
enum {SUCCEED,      # 执行成功FAILED,           # 执行败RUNNING,       # 正在执行
}var root           # 节点的根节点
var actor           # 控制的节点
var task_idx = 0   # 当前执行的 task 的 index(执行的第几个节点)## 节点的任务,返回执行结果
func _task() -> int:return SUCCEED

Composite 节点

组合节点,控制树的遍历方式。

Sequence 节点

脚本名:Composite_Sequence.gd

## Sequence 执行成功则继续执行,执行一次失败则返回失败
extends "BT_Node.gd"var result = SUCCEEDfunc _task():while task_idx < get_child_count():result = get_child(task_idx)._task()# 执行成功继续执行下一个,直到失败或束if result == SUCCEED:task_idx += 1else:breakif task_idx >= get_child_count() || result == FAILED:task_idx = 0if result == FAILED:return FAILED# 如果都没有执行失败的,则回 SUCCEEDreturn SUCCEED
Selector 节点

脚本名:Composite_Selector.gd

## Selector 执行失败则继续执行,执行一次成功则返回成功
extends "BT_Node.gd"var result = FAILEDfunc _task():while task_idx < get_child_count():result = get_child(task_idx)._task()# 执行失败继续执行下一个,直到成功败或结束if result == FAILED:task_idx += 1else:breakif task_idx >= get_child_count() || result == SUCCEED:task_idx = 0if result == SUCCEED:return SUCCEED# 如果都没有成功执行的,则回 FAILEDreturn FAILED
Parallel 节点

脚本名:Composite_Parallel.gd

## Paraller 并行节点,全部节点都执行一遍
extends "BT_Node.gd"var result = SUCCEEDfunc _task():var is_running = false# 运行全部子节点,有一个为失败,则返回 FAILEDfor task_idx in get_child_count():var node = get_child(task_idx)result = get_child(task_idx)._task()if result == FAILED:return FAILEDelif result == RUNNING:is_running = true# 如果有运行的节点则返回 RUNNINGif is_running:return RUNNING# 如果全部都是成功状态,则返回 SUCCEEreturn SUCCEED

Decorator 节点

改变子节点任务执行的结果。以下做两个可能会用到的两个节点。

Inverter 节点

脚本名:Decorator_Inverter.gd

## Inverter 取反
extends "BT_Node.gd"var result func _task():result = get_child(0)._task()# 如果 成功,则返回 失败if result == SUCCEED:return FAILED# 如果 失败,则返回 成功elif result == FAILED:return SUCCEEDelse:return RUNNING
Counter 节点

脚本名:Decorator_Counter.gd

在这里,这个脚本中其实可以只写 run_task() 方法中的代码,可以少写一半代码。我是额外写了一个 _run_loop() 方法,供更多不同情况的需求。

## Counter 计数器,运行指定次数
extends "BT_Node.gd"## 执行类型
enum RunType {TaskCount,        # 任务执行次数(多帧的时间执行完最大次数)LoopCount,      # 循环次数(一帧时间执行完最大次数)
}export (RunType) var run_type = RunType.TaskCount
export var max_count = 3   # 执行最大次数var run_func : FuncRef
var count = 0  # 执行节点的次数func _ready():if run_type == RunType.LoopCount:run_func = funcref(self, "_run_loop")elif run_type == RunType.TaskCount:run_func = funcref(self, "_run_task")func _task():return run_func.call_func()func _run_loop():count = 0while count < max_count:# 计数count += 1if get_child(0)._task() == FAILED:return FAILEDreturn SUCCEEDfunc _run_task():var result = get_child(0)._task()count += 1if result == FAILED:count = 0return FAILEDif count < max_count:return RUNNINGelse:count = 0return SUCCEED

在设计 Leaf 节点之前,Leaf 需要用到 Blackboard 进行存储数据,所以我们设计一下 Blackboard

Blackboard

脚本名:Blackboard.gd

## Blackboard 黑板,存储数据
extends Referencevar data = {} # 存放数据

Leaf 节点

脚本名:BT_Leaf.gd

叶节点,用户重写相关的方法,实现各种功能。

## BT_Leaf 行为树叶节点,用于重写行为树
extends "BT_Node.gd"var blackboard = null    # 黑板(记录设置数据,用于 Action 节点中)#==================================================
# Set/Get
#==================================================
## 设置黑板
func set_blackboard(value):blackboard = value## 设置数据
func set_data(property: String, value):blackboard.data[property] = value## 获取数据
func get_data(property: String):return blackboard.data[property]#==================================================
# 自定义方法
#==================================================
func _task():return SUCCEED
Condition 节点

脚本名:Leaf_Condition.gd

## Condition 条件节点
extends "BT_Leaf.gd"func _task():return SUCCEED if condition() else FAILED# 重写这个方法
func condition() -> bool:return true
Action 节点

脚本名:Leaf_Action.gd

## Action 控制节点,执行功能
extends "BT_Leaf.gd"func _task():return action(get_viewport().get_physics_process_delta_time())# 重写这个方法
func action(delta: float) -> int:return SUCCEED

最后设计用于运行整个节点数的根节点。

Root 节点

脚本名:BT_Root.gd

用来执行驱动整个行为树节点

## Root 根节点
extends "BT_Node.gd"const Blackboard = preload("Blackboard.gd")var blackboard      # 全局行为树黑板##==================================================
#   内置方法
##==================================================
func _ready():_init_data()_init_node(self)func _physics_process(delta):get_child(0)._task()##==================================================
#   自定义方法
##==================================================
## 初始化当前数据
func _init_data():root = selfactor = get_parent()blackboard = Blackboard.new()## 初始化节点
func _init_node(node: Node):node.actor = self.actornode.root = self.rootif node.has_method("set_blackboard"):node.blackboard = self.blackboard# 不断向子节点迭代,对节点树中的所有节点进行初始化设置for child in node.get_children():_init_node(child)

至此,行为树设计结束。


相关学习链接:

  • 行为树 Behavior Tree 原理 一
  • Godot Behavior Tree
  • 行为树(Behavior Tree)实践(1)– 基本概念

【Godot】行为树(一)了解与设计行为树代码相关推荐

  1. MySQL数据库搜题_智慧树知到_MySQL数据库设计与应用_搜题公众号

    智慧树知到_MySQL数据库设计与应用_搜题公众号 更多相关问题 阅读理解. This is the twin's(双胞胎的) room. It's a nice room. The two beds ...

  2. LSM树——Log-Structured Merge-Tree数据结构、LSM树设计思想、LSM的数据写入操作、LSM的数据查询操作

    LSM树数据结构 简介 传统关系型数据库,一般都选择使用B+树作为索引结构,而在大数据场景下,HBase.Kudu这些存储引擎选择的是LSM树.LSM树,即日志结构合并树(Log-Structured ...

  3. html5小游戏猴子爬树源码,关于猴子爬树的大班活动设计

    关于猴子爬树的大班活动设计 大班计算活动设计――猴子爬树 活动目的: 1.学习6的组成,能按次序和规律进行分解和组合,懂得运用交换位置的方法来分解6. 2.发展幼儿逻辑思维能力和推理能力. 活动准备: ...

  4. 超富有创意的微型纸袋树(paper bag tree)设计

    日期:2012-11-20  来源:GBin1.com 今天我们推荐来自日本设计师Yuken Teruya的一组超棒的微型纸袋树设计,如果对于设计和创意有兴趣的朋友一定要看看他的网站中的各种创意设计, ...

  5. springboot工科树洞网站的设计与实现 计算机毕设源码48510

    摘  要 随着互联网趋势的到来,各行各业都在考虑利用互联网将自己推广出去,最好方式就是建立自己的互联网系统,并对其进行维护和管理.在现实运用中,应用软件的工作规则和开发步骤,采用Java技术建设工科树 ...

  6. Bw树:新硬件平台的B树(内存数据库中的b树索引)

    Bw树:新硬件平台的B树 Bw树:新硬件平台的B树... 1 1. 概述... 2 1.1 原子记录存储(Atomic Record Stores)... 2 1.2 新的环境... 2 1.3 实现 ...

  7. 二叉树第i层中的所有结点_讲透学烂二叉树(二):图中树的定义amp;各类型树的特征分析...

    日常中我们见到的二叉树应用有,Java集合中的TreeSet和TreeMap,C++ STL中的set.map,以及Linux虚拟内存的管理,以及B-Tree,B+-Tree在文件系统,都是通过红黑树 ...

  8. mysql mongodb b树_为何Mongodb索引用B树,而Mysql用B+树?

    引言 很久没写文章了,今天回来重操旧业. 今天讲的这个主题,是<面试官:谈谈你对mysql索引的认识>,里头提到的一个坑.mysql 也就是说,若是面试官问的是,为何Mysql中Innod ...

  9. 浅谈二叉查找树、AVL树、红黑树、B树、B+树的原理及应用

    一.二叉查找树 1.简介 二叉查找树也称为有序二叉查找树,满足二叉查找树的一般性质,是指一棵空树具有如下性质: 任意节点左子树不为空,则左子树的值均小于根节点的值. 任意节点右子树不为空,则右子树的值 ...

  10. 数据结构(八):排序 | 插入排序 | 希尔排序 | 冒泡排序 | 快速排序 | 简单选择排序 | 堆排序 | 归并排序 | 基数排序 | 外部排序 | 败者树 | 置换-选择排序 | 最佳归并树

    文章目录 第八章 排序 一.排序的基本概念 (一)什么是排序 (二)排序的应用 (三)排序算法的评价指标 (四)排序算法的分类 (五)总结 二.插入排序 (一)算法思想 (二)算法实现 (三)算法效率 ...

最新文章

  1. 在纸上写好一个c语言程序后,上机运行的基本步骤为,c基本概念(选择题).docx
  2. 【怎样写代码】工厂三兄弟之工厂方法模式(一):问题案例
  3. shell 字符串操作(长度,查找,替换)详解
  4. 【C++ 语言】文件操作 ( fopen | fprintf | fscanf | fgets | fputc | fgetc | ofstream | ifstream )
  5. python里的append怎么用_python中append实例用法总结
  6. Android之单复选框及Spinner实现二级联动
  7. 六招教你快速提升网站交互体验,降低跳出率
  8. 计算机网络(三)——TCP/IP协议
  9. 微信公布朋友圈9月十大谣言 包括新型手机病毒出现等
  10. React学习(8)—— 高阶应用:不使用ES6、JSX实现React
  11. 必须掌握的Java基础知识(一)
  12. VirtualBox安装RedHat7
  13. Flutter仿写一个iOS风格的通讯录
  14. C++进程和Python进程通信
  15. 网络空间地图测绘的战略意义(下)
  16. 除了美团点评合并,国庆长假O2O还发生了什么?
  17. 服务器私有信息,私有服务器
  18. Java简单日记本项目
  19. App渗透中常见的加密与解密
  20. HashMap源码分析(深入理解HashMap)

热门文章

  1. 学生计算机重启删除文件,Delete.On.Reboot(重启时删除无用文件工具)
  2. 2021-10-09《星科快报》第一期 何为元宇宙
  3. 用计算机计算教学反思,《用计算器计算》的教学反思
  4. DosBox 的 DOSBOX.CONF 的详细配置说
  5. 【稀饭】react native 系列教程之已有项目接入React Native
  6. 程 | 深度学习 + OpenCV,Python 实现实时视频目标检测 机器之心 09-21
  7. 举例一种计算机病毒,电脑病毒介绍及举例
  8. 利用智能ABC漏洞关闭网吧驱动防火墙
  9. c语言编写温度监测界面,基于51单片机的温度检测系统_单片机C语言课题设计报告.doc...
  10. [CATARC_2017] 第一周