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

2023-05-16

行为树介绍

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

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

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

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

一棵行为树表示一个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 = SUCCEED


func _task():
	while task_idx < get_child_count():
		result = get_child(task_idx)._task()
		# 执行成功继续执行下一个,直到失败或束
		if result == SUCCEED:
			task_idx += 1
		else:
			break
	
	if task_idx >= get_child_count() || result == FAILED:
		task_idx = 0
		if result == FAILED:
			return FAILED
	
	# 如果都没有执行失败的,则回 SUCCEED
	return SUCCEED
Selector 节点

脚本名:Composite_Selector.gd

## Selector 执行失败则继续执行,执行一次成功则返回成功
extends "BT_Node.gd"


var result = FAILED


func _task():
	while task_idx < get_child_count():
		result = get_child(task_idx)._task()
		# 执行失败继续执行下一个,直到成功败或结束
		if result == FAILED:
			task_idx += 1
		else:
			break
	
	if task_idx >= get_child_count() || result == SUCCEED:
		task_idx = 0
		if result == SUCCEED:
			return SUCCEED
	
	# 如果都没有成功执行的,则回 FAILED
	return FAILED
Parallel 节点

脚本名:Composite_Parallel.gd

## Paraller 并行节点,全部节点都执行一遍
extends "BT_Node.gd"


var result = SUCCEED


func _task():
	var is_running = false
	
	# 运行全部子节点,有一个为失败,则返回 FAILED
	for task_idx in get_child_count():
		var node = get_child(task_idx)
		result = get_child(task_idx)._task()
		if result == FAILED:
			return FAILED
		elif result == RUNNING:
			is_running = true
	
	# 如果有运行的节点则返回 RUNNING
	if is_running:
		return RUNNING
	
	# 如果全部都是成功状态,则返回 SUCCEE
	return 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 SUCCEED
	
	else:
		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 = 0
	while count < max_count:
		# 计数
		count += 1
		if get_child(0)._task() == FAILED:
			return FAILED
	return SUCCEED


func _run_task():
	var result = get_child(0)._task()
	count += 1
	
	if result == FAILED:
		count = 0
		return FAILED
	
	if count < max_count:
		return RUNNING
	else:
		count = 0
		return SUCCEED

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

Blackboard

脚本名:Blackboard.gd

## Blackboard 黑板,存储数据
extends Reference

var 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 = self
	actor = get_parent()
	blackboard = Blackboard.new()


## 初始化节点
func _init_node(node: Node):
	node.actor = self.actor
	node.root = self.root
	if 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)– 基本概念
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Godot】行为树(一)了解与设计行为树代码 的相关文章

  • 【Godot】对 Godot 节点设计的思考

    对 Godot 中节点设计的思考 单个节点的功能设计的想法 xff0c 体会 Godot 的设计思想 低耦合 设计单个节点可复用的节点时 xff0c 调用方法尽量只对当前节点可获取到的变量或方法进行使用 xff0c 比如我写一个可以控制 K
  • 【Godot】行为树(一)了解与设计行为树代码

    行为树介绍 行为树是个节点树 xff0c 父节点通过不断遍历子节点 xff0c 根据不同类型的节点执行不同的分支 最终调用叶节点执行功能 行为树也不难理解 xff0c 他就像代码逻辑一样 xff0c 只是用节点的方式展现出来 xff0c 而
  • Godot Engine:GDScript 4.X中语法的变化(2020年8月4日 更新)

    文章目录 4 X版 GDScript范例支持注解属性 xff08 Properties xff09 的定义格式await关键字代替yield加入super关键字去除了多级调用问题小结 4 X版 GDScript范例 支持注解 从4 x开始
  • Godot 4 应用 - 图形绘制

    花了两天时间 做了一个初步的图形软件效果 先占个坑 以后再叙
  • 关于在windows使用msys2 + mingw + gcc/g++ 编译godot的笔记

    关于在windows使用msys2 mingw gcc g 编译godot的笔记 编译参数 1 target release debug release debug 2 多线程参数 j数字 3 profile是自定义构建参数 可以启用或者禁
  • 如何创建没有 setter 函数的 getter 函数?

    我的脚本中有多个导出的变量 每当更改一个变量时 我想调用一个通用的 getter 并让值自动设置 tool export float var sample1 setget smthn changed export float var sam
  • 合并多个精灵节点?

    例如 假设我有 2 个精灵节点 但也可以超过 2 个 如下所示 每个人都有自己独立的图像我想要的是将它们组合起来并用单个图像创建一个新的精灵节点 在工具模式下 like this 也许可以通过使用Image 毫无疑问涉及计算 或者也许使用一
  • 如何在 GDScript 中实现结构?

    GDScript 中是否有相当于 C 结构 类的东西 例如 struct Player string Name int Level 戈多3 1 1gdscript不支持structs 但使用可以实现类似的结果classes dict or
  • Godot 监听来自同一场景的多个实例的信号

    我有以下场景 玩家 敌人 攻击 当攻击与敌人发生碰撞时 敌人会发出 onHit 信号 播放器监听该信号并反弹 这一切都运行良好 但现在如果我复制敌人 因此有多个敌人场景 我如何收听所有敌人的信号 有没有办法获取场景的所有实例并连接到它们的所
  • 在 godot 游戏引擎中使用不同的编程语言?

    我想要将不同的编程语言绑定到 Godot 游戏引擎 有关于这个主题的指导文件或视频吗 例如 这个项目是如何完成的 戈多锈 https github com godot rust godot rust 如果我能学习基础知识 我就能成功地用不同
  • 安卓:应用程序未安装

    这里是新手 我用Godot游戏引擎制作了我的第一个游戏并成功导出到android 复制到我的手机上 它安装并运行良好 几个小时后 我做了一些更改并再次导出 再次复制了 apk 但现在当我尝试安装它时 它没有完成安装 我多次尝试更改导出设置
  • 如何获取另一个场景godot中的节点?

    我正在制作一个具有多个场景的游戏 需要有 get node 来自另一个场景的节点 变量 并且我不知道如何从另一个场景获取节点 如果我理解正确的话 您想连接到来自另一个场景中的节点的信号 直接方法会起作用 const bullet prelo
  • 如何在 Godot 中将字符串转换为枚举?

    使用 Godot 3 4 我的枚举设置为 enum STRENGTH DEXTERITY CONSTITUTION INTELLIGENCE WISDOM CHARISMA 我希望能够使字符串 STRENGTH 返回枚举值 0 我希望下面的
  • 如何在戈多中使刚体跳跃而不赋予其飞行能力

    我本来会使用运动体 但我想将现实生活中的物理添加到我的 2d 对象中 但似乎我可以通过多次按向上键来飞行 extends RigidBody2D var velocity Vector2 ZERO const GRAVITY 35 cons
  • Godot:调用外部方法

    经过大量谷歌搜索 我仍然不明白什么可能是一个简单的解决方案 场景 主要 包含一个 TileMap Grid 并附有一个脚本 Grid gd 场景 玩家 包含一个 KinematicBody2D Player 及其附加脚本 Player gd
  • 如何克服 Godot 将按钮字体更改为默认颜色的问题?

    我正在使用 Godot 4 我在容器中手动创建了很多按钮 我在主场景中设置了一个颜色变量 tempcol 当我单击带有 tempcol 设置的按钮时 比如 Color Red 按钮的字体颜色更改为白色 似乎是默认字体颜色 但是当我单击另一个
  • 传递Physics2DShapeQueryParameters 层进行检查

    我目前正在为我的 2D 自上而下游戏开发一个构建系统 最后一步是检查是否有任何物体 例如树或玩家 阻碍了物品的放置 经过一些研究后 我发现使用Physics2DShapeQueryParameters 是正确的方法 我唯一的问题是我不知道如
  • 关闭 Godot 中的游戏

    我正在使用 Godot 创建网页游戏 为了关闭游戏 我尝试使用 get tree quit 如果我在 IDE 上使用它 它就可以工作 当我在我的服务器上尝试它时 导出项目后 它不起作用 我确信导出设置没问题 我怎样才能关闭游戏 并且 如何添
  • 简单模式7公式/例子?

    我最近发现了利用 SNES 模式 7 的伪 3D 效果 并想尝试在 Godot 引擎中复制它 我尝试在网上查找 但所有内容要么以我无法理解的方式解释 要么以我不知道的编程语言解释 我还需要学习如何旋转该区域 并将精灵作为角色或敌人放入 但我
  • Godot:检测 Area2D 内部的“鼠标按下”和 Area2D 外部的“鼠标向上”

    我想检测 Area2D 内部的鼠标单击 并按住 然后检测 Area2D 内部或外部的鼠标释放 这是我到目前为止所拥有的 extends Area2D PickArea func input event viewport event shap

随机推荐

  • VMware 17 Pro安装(升级)

    文章目录 前言一 下载安装1 下载地址2 安装流程 前言 本文演示如何在windowns 10下 安装 xff08 升级 xff09 VMware 17 Pro 一 下载安装 1 下载地址 VMware 17 Pro 2 安装流程 如下图所
  • VMware 安装CentOS7

    文章目录 前言一 CentOS 7镜像下载二 VMware 创建CentOS 71 CentOS 7创建2 CentOS 7配置3 总结 前言 前文已完成对VMware 的升级 xff0c 我们需要在VMware Workstation创建
  • npm install npm ERR! Error: EPERM: operation not permitted 解决方法记录

    问题描述 执行npm install安装依赖时报错如下 xff1a 解决方案 xff1a 删除C Users 账户 下的 npmrc文件 npmrc文件即可 注意 xff1a 不是nodejs安装目录npm模块下的那个npmrc文件
  • java swing 常用的三种布局方式:边界布局、流布局、网格布局管理器

    作者 xff1a firstmiki 链接 xff1a http www cnblogs com firstmiki p 6340001 html 来源 xff1a firstmiki的博客 著作权归作者所有 xff0c 转载请联系作者获得
  • Linux(CentOS 7)配置静态ip及ping 不通外网问题

    前言 日常学习中 xff0c 如果Linux中安装了MySQL nacos redis等中间件 我们可能会通过navicat dataGrip连接MySQL 会通过ip port nacos访问nacos 会通过ip port连接redis
  • docker run启动镜像容器时忘记添加开机自启动解决方法

    问题描述 在使用以下命令启动mysql容器时 xff0c 忘记添加了 restart 61 always 开启开机自启动 xff0c 导致每次开机 重启后 xff0c 需要重新通过一系列命令手动重启对应的容器 xff0c 极其不方便 doc
  • docker 安装RabbitMQ

    系列文章目录 第一章 RabbitMQ安装 提示 xff1a 写完文章后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一 RabbitMQ安装1 dockerHub 选择镜像2 Cent
  • 记 Content type ‘application/octet-stream‘ not supported

    项目场景 xff1a 实现一个入参方式为 64 RequestPart 43 64 RequestParam files 的接口 xff0c 即该接口要包含文件上传和其它 实体类 入参 示例代码 xff1a 64 PostMapping v
  • Go语言环境搭建

    一 下载开发工具 1 1 Go语言官网 1 2 进入Go官网 xff0c 点击Download xff0c 进入开发工具下载界面 xff0c 根据个人系统选择对应的安装包进行下载 windows 对应下载链接 二 安装开发工具 2 1 双击
  • Go入门教程--Hello,World.

    零 文本编辑器实现Hello World 官方给出的教程主要针对Linux 或Mac系统 但也没关系 xff0c 在windows 中实现也很简单 0 1 创建文件夹 xff0c 名称任意 xff0c 如 xff1a hello 0 2 创
  • 在AWS上开通EC2服务器并部署tomcat

    1 登录aws 2 点击服务 计算 EC2 3 点击启动实例 4 选择linux镜像 5 选择一个实例类型 6 配置实例详细信息 xff0c 保持默认 xff0c 点击下一步 7 添加存储 xff08 选择linux根目录硬盘大小和类型 x
  • android studio gradle 使用阿里源 (修改 settings.gradle)

    默认的地址下载速度极慢 依赖项几个小时也下载不完 改为 阿里源 1分钟就下载ok了 代码 修改根目录中 的 settings gradle 文件 内容 pluginManagement span class token punctuatio
  • 实用!Windows 远程控制 Ubuntu 系统

    点击上方 xff0c 选择 设为星标 优质文章 xff0c 及时送达 上一篇 xff1a 来源 xff1a 头条 互联网上的小蜘蛛 有时需要在实际的电脑上安装Ubuntu的操作系统来搭建免费的网站平台 这就需要使用远程的客户端Windows
  • 并查集——洛谷P3367

    题目描述 如题 xff0c 现在有一个并查集 xff0c 你需要完成合并和查询操作 输入输出格式 输入格式 xff1a 第一行包含两个整数N M xff0c 表示共有N个元素和M个操作 接下来M行 xff0c 每行包含三个整数Zi Xi Y
  • Web项目通过webservice编写一个接口,部署在远程服务器上

    在我的上一片文章中 xff0c 我在本地新建了一个普通的类来编写WebService xff0c 使用终端类 Endpoint 发布这个WebService xff0c 以此来实现让其他类调用这个接口 xff0c 实现接口中定义的功能 通过
  • ubuntu与win10共享LE蓝牙鼠标

    类似的教程网上有很多 xff0c 大部分是找到蓝牙设备目录下info文件中的 linkKey 中的key值复制到win10下注册表中 xff0c 但是对于蓝牙5 0或LE设备来说 xff0c 是没有linKey的 xff0c 这里我也参考了
  • FileZilla搭建FTP服务器图解教程,并允许外网访问NAT内网

    FTP是用来在两台计算机之间传输文件 xff0c 是Internet中应用非常广泛的服务之一 FTP服务是网络中经常采用的资源共享方式之一 FTP协议有PORT和PASV两种工作模式 xff0c 即主动模式和被动模式 今天我分享一个最近我自
  • 十进制转换八进制(C语言基础)

    题目描述编程 xff0c 输入一个 xff11 xff10 进制正整数 xff0c 然后输出它所对应的八进制数 输入无输出无样例输入10样例输出12 include lt stdio h gt int main int num m 61 0
  • 【Godot】对 Godot 节点设计的思考

    对 Godot 中节点设计的思考 单个节点的功能设计的想法 xff0c 体会 Godot 的设计思想 低耦合 设计单个节点可复用的节点时 xff0c 调用方法尽量只对当前节点可获取到的变量或方法进行使用 xff0c 比如我写一个可以控制 K
  • 【Godot】行为树(一)了解与设计行为树代码

    行为树介绍 行为树是个节点树 xff0c 父节点通过不断遍历子节点 xff0c 根据不同类型的节点执行不同的分支 最终调用叶节点执行功能 行为树也不难理解 xff0c 他就像代码逻辑一样 xff0c 只是用节点的方式展现出来 xff0c 而