行为树行为树行为树

行为树由一个个节点组成

  • 结构:树状结构
  • 运行流程:从根节点开始自顶向下往下遍历,每经过一个节点就执行节点对应的功能。

我们规定,每个节点都提供自己的excute函数,返还执行失败/成功结果。
然后根据不同节点的执行结果,遍历的路径随之改变,而这个过程中遍历到什么节点就执行excute函数。

主流的行为树实现,将节点主要分为四种类型。
下面列举四种节点类型及其对应excute函数的行为:

  • 控制节点(非叶节点),用于控制如何执行子节点(控制遍历路径的走向)。
  • 条件节点(叶节点),行为是提供条件的判断结果。
  • 行为节点(叶节点):

    行为节点是代表智能体行为的叶节点,其执行函数一般位该节点代表的行为。

    行为节点的类型是比较多的,毕竟一个智能体的行为是多种多样的,而且都得根据自己的智能体模型定制行为节点类型。
    这里列举一些行为:站立,射击,移动,跟随,远离,保持距离....

  • 装饰节点 :行为是修饰(辅助)其他三类节点。例如执行结果取反/并/或,重复执行若干次等辅助修饰节点的作用,均可做成装饰节点。

持续行为

一些行为是可以瞬间执行完的(例如转身?),
而另外一些动作则是执行持续一段时间才能完成的(例如攻击从启动攻击行为到攻击结算要1秒左右的时间)。

因此,这些持续行为节点的excute函数里,应先启动智能体的持续行为,然后挂起该行为树(更通俗地说是暂停行为树),等到持续时间结束才允许退出excute函数并继续遍历该行为树。
为了支持挂起行为树而不影响其他CPU代码执行,我们往往需要利用协程等待该其行为完成而不产生CPU阻塞,而且开销远低于真正的线程。
此外,一般是一个行为树对应维护一个协程。

条件节点:

执行节点不会总是一帆风顺的,有成功也总会有失败的结果。
这就是引入前提条件的作用——
满足前提条件,才能成功执行行为,返还执行成功结果。否则不能执行行为,返还执行失败结果。

但是每个节点的前提总会不同,或有些没有前提(换句话说总是能满足前提)。

一个可行的做法是:让行为节点含有bool函数对象(或函数接口)。这样对于不同的逻辑条件,就可以写成不同的bool函数,绑定给相应的行为节点。

现在比较成熟的做法是把前提条件抽象分离成新的节点类型,称之为条件节点
将其作为叶节点混入行为树,提供条件的判断结果,交给控制节点决策。

它相当模块化,更加方便适用。

这里的Sequence节点是上面控制节点的一种:能够让其所有子节点依次运行,若运行到其中一个子节点失败则不继续往下运行。
这样可以实现出不满足条件则失败的效果。

相比较传统的有限状态机:

  • 易脚本化/可视化的决策逻辑
  • 逻辑和实现的低耦合,可复用的节点
  • 可以迅速而便捷的组织较复杂的行为决策

这里并不是说有限状态机一无所用:

  • 状态机可以搭配行为树:状态机负责智能体的身体状态,行为树则负责智能体的智能决策。这样在行为树做决策前,得考虑状态机的状态。
  • 状态机适用于简单的AI:对于区区需两三个状态的智能,状态机解决绰绰有余。
  • 状态机运行效率略高于行为树:因为状态机的运行总是在当前状态开始,而行为树的运行总在根开始,这样就额外多了一些要遍历的节点(也就多了一些运行开销)。

在《杀手:赦免》的人群系统里,人群的状态机AI只有简单的3种状态,由于人群的智能体数量较多,若采取行为树AI,则会大大影响性能。

代码示例:

enum Status {SUCCESS,FAILURE,RUNNING
}// 基础节点类
abstract class Node {abstract execute(): Status;
}// 非叶节点类,用于容纳子节点
abstract class CompositeNode extends Node {protected children: Node[] = [];addChild(child: Node): void {this.children.push(child);}
}// 选择器节点:依次执行子节点,直到其中一个成功为止
class SelectorNode extends CompositeNode {execute(): Status {for (const child of this.children) {const status = child.execute();if (status === Status.SUCCESS) {return Status.SUCCESS;}}return Status.FAILURE;}
}// 序列节点:依次执行子节点,直到其中一个失败为止
class SequenceNode extends CompositeNode {execute(): Status {for (const child of this.children) {const status = child.execute();if (status === Status.FAILURE) {return Status.FAILURE;}}return Status.SUCCESS;}
}// 条件节点:判断某个条件是否满足,并返回结果
abstract class ConditionNode extends Node {abstract check(): boolean;execute(): Status {return this.check() ? Status.SUCCESS : Status.FAILURE;}
}// 行为节点:执行具体的动作,并返回结果
abstract class ActionNode extends Node {abstract performAction(): boolean;execute(): Status {return this.performAction() ? Status.SUCCESS : Status.FAILURE;}
}// 装饰节点:用于修改其他节点的行为,通常只有一个子节点
abstract class DecoratorNode extends Node {protected child: Node;constructor(child: Node) {super();this.child = child;}
}// 反转节点:将子节点的结果反转
class InverterNode extends DecoratorNode {execute(): Status {const status = this.child.execute();if (status === Status.SUCCESS) {return Status.FAILURE;} else if (status === Status.FAILURE) {return Status.SUCCESS;} else {return status;}}
}// 重复执行装饰节点:重复执行子节点指定的次数,或直到满足某个条件
class RepeaterNode extends DecoratorNode {private maxRepeats: number;private stopOnSuccess: boolean;private stopOnFailure: boolean;constructor(child: Node, maxRepeats: number, stopOnSuccess: boolean = false, stopOnFailure: boolean = false) {super(child);this.maxRepeats = maxRepeats;this.stopOnSuccess = stopOnSuccess;this.stopOnFailure = stopOnFailure;}execute(): Status {let count = 0;while (this.maxRepeats === -1 || count < this.maxRepeats) {const status = this.child.execute();if (this.stopOnSuccess && status === Status.SUCCESS) {return Status.SUCCESS;}if (this.stopOnFailure && status === Status.FAILURE) {return Status.FAILURE;}count++;}return Status.SUCCESS;}
}// 判断敌人是否在范围内的条件节点
class IsEnemyInRange extends ConditionNode {check(): boolean {// 判断敌人是否在范围内return Math.random() < 0.5; // 50% 概率敌人在范围内}
}// 追击敌人的行为节点
class ChaseEnemy extends ActionNode {performAction(): boolean {console.log("Chasing the enemy!");return true; // 假设追击成功}
}// 攻击敌人的行为节点
class AttackEnemy extends ActionNode {performAction(): boolean {console.log("Attacking the enemy!");return true; // 假设攻击成功}
}// 创建行为树
const root = new SelectorNode();const sequence = new SequenceNode();
root.addChild(sequence);const condition = new IsEnemyInRange();
sequence.addChild(condition);const chase = new ChaseEnemy();
const repeater = new RepeaterNode(chase, 3); // 重复执行ChaseEnemy 3次
sequence.addChild(repeater);const attack = new AttackEnemy();
sequence.addChild(attack);// 执行行为树
const status = root.execute();
console.log(`Behavior tree execution status: ${Status[status]}`);


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/30472.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国产数据库中读写分离实现机制

在数据库高可用架构下会存在1主多备的部署&#xff0c;备节点可以根据业务场景分发一部分流量以充分利用资源&#xff0c;并减轻主库的压力&#xff0c;因此在数据库的功能上需要读写分离来实现。 充分利用备节点的资源&#xff0c;提升业务的吞吐量&#xff1b;防止运维等非业…

《算法设计与分析》第五六章:回溯法与分支限界法

文章目录 回溯法分支限界法一、本章作业1.活动安排问题2.旅行商问题3.单源最短路径4.任务分配问题 二、算法积累1.回溯法求解01背包问题2.回溯法求解最大团问题3.回溯法求解n皇后问题4.回溯法求解地图着色5.回溯法求解哈密尔顿图6.回溯法求活动安排7.分支限界法求01背包问题8.分…

Flutter第十三弹 路由和导航

目标&#xff1a; 1.Flutter怎么创建路由&#xff1f; 2.怎么实现路由跳转&#xff1f;页面返回&#xff1f; 一、路由 1.1 什么是路由&#xff1f; 路由(Route)在移动开发中通常指页面&#xff08;Page&#xff09;&#xff0c;在Android中通常指一个Activity。所谓路由管…

小功率电机驱动方案中如何选择驱动IC

小功率电机驱动方案及驱动IC的选择 电机驱动作为工业4.0中工厂自动化整个闭环中的执行器环节&#xff0c;其性能好坏直接影响到整个闭环的性能。因此&#xff0c;工业4.0对电机驱动提出了更高的性能和功能要求&#xff0c;例如更快的响应速度、更高的带宽、更高精度的位置和速…

前端框架中的路由(Routing)和前端导航(Front-End Navigation)

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介前端框架中的路由&#xff08;Routing&#xff09;和前端导航&#xff08;Front-End Navigation&#xff09;1. 路由&#xff08;Routing&#xff09;1.1 定义1.2 路由的核心概念1.2.1 路由表&#xff08;Route Table&#xff09;1…

多模态大模型:基础架构

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则…

gitee添加别人的仓库后,在该仓库里添加文件夹/文件

一、在指定分支里添加文件夹&#xff08;如果库主没有创建分支&#xff0c;自己还要先创建分支&#xff09; eg:以在一个项目里添加视图文件为例&#xff0c;用Echarts分支在usr/views目录下添加Echarts文件夹&#xff0c;usr/views/Echarts目录下添加index.vue 1.切换为本地仓…

Linux系统安装Ruby语言

Ruby是一种面向对象的脚本语言&#xff0c;由日本的计算机科学家松本行弘设计并开发&#xff0c;Ruby的设计哲学强调程序员的幸福感&#xff0c;致力于简化编程的复杂性&#xff0c;并提供一种既强大又易于使用的工具。其语法简洁优雅&#xff0c;易于阅读和书写&#xff0c;使…

重学java 66.IO流 转换流

且敬我疯狂&#xff0c;生命中不败的篇章 —— 24.6.11 一、字符编码 计算机中储存的信息都是用二进制数表示的&#xff0c;而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。[按照某种规则&#xff0c;将字符存储到计算机中&#xff0c;称为编…

打造一个属于你的桌面天气 超级有个性的天气桌面

打造一个属于你的桌面天气 超级有个性的天气桌面。大家好&#xff0c;今天我们带来一个非常有趣的桌面天气工具&#xff0c;喜欢桌面diy的你&#xff0c;快点用上它吧&#xff01; 桌面上的美化&#xff0c;是许多爱美用户的心血和热爱。每个地方的美化&#xff0c;都是自己亲…

机器学习课程复习——隐马尔可夫

不考计算题 Q:概率图有几种结构? 条件独立性的公式? 顺序结构发散结构汇总结构Q:隐马尔可夫模型理解? 概念 集合:状态集合、观测集合 序列:状态序列、观测序列

linux环境编程基础学习

Shell编程&#xff1a; 相对的chmod -x xx.sh可以移除权限 想获取变量的值要掏点dollar&#xff08;&#xff04;&#xff09; 多位的话要加个花括号 运算&#xff1a;expr 运算时左右两边必须要加空格 *号多个含义必须加转义符 双引号可以加反单&#xff0c;但是发过来就不行 …

【android】安卓入门学习

文档介绍&#xff1a;http://8.136.122.222/book/primary/kotlin/kotlin-intro.html 文档补充说明&#xff1a;https://blog.csdn.net/qq_42059717/category_12047508.html 一、搭建环境及工具安装 见文档 二、工具界面及项目文件介绍 ├── app //工程主模块名称 │ …

【Linux安装Conda环境的详细教程】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

PTP简介及Linux phy ptp驱动实现

1、PTP简介 PTP(precision time protocol)精确时间协议&#xff0c;是一种时间同步的协议&#xff0c;对应 IEEE 1588 标准&#xff0c;是基于网络数据包的一种时间同步协议&#xff0c;1588v2的同步精度可以达到ns级&#xff0c;但1588协议对硬件有依赖。 2、PTP原理 时间同…

DFS(一)

问题一(指数级选择) 从1~n这n个整数中任意选取多个&#xff0c;输出所有可能的选择方案。 首先想一下&#xff0c;在现实世界中&#xff0c;我们要如何解决这个问题。 应该是一个一个枚举&#xff0c;即每个数都可以有两个选择(选/不选)。共有种结果。 想一下&#xff0c;如…

PHP转Go系列 | 字符串的使用姿势

大家好&#xff0c;我是码农先森。 输出 在 PHP 语言中的输出比较简单&#xff0c;直接使用 echo 就可以。此外&#xff0c;在 PHP 中还有一个格式化输出函数 sprintf 可以用占位符替换字符串。 <?phpecho 码农先森; echo sprintf(码农:%s, 先森);在 Go 语言中调用它的输…

创建进程的常用方式

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中有多个模块可以创建进程&#xff0c;比较常用的有os.fork()函数、multiprocessing模块和Pool进程池。由于os.fork()函数只适用于Unix/Linu…

0617,递归问题(详细——好好好一入递归深似海)

目录 第七章&#xff08;函数&#xff09;思维导图总结&#xff1a;递归三问 01&#xff0c;电影院问题 理解递归的执行过程 02&#xff0c;FIBNACCI数列 不是说具有递归结构的问题&#xff0c;就可以用递归求解——存在大量的重复计算 法一&#xff1a;自顶向下求解 BUG…

ruoyi框架第二天,自定义接口,在若依框架显示数据

书接上文&#xff0c;搭建好若依&#xff0c;并且创建自己想要的模块。 让ruoyi框架显示自己想要的模块。 今天&#xff0c;我们就要自定义接口&#xff0c;模仿ruoyi框架收发数据模式&#xff0c;来创建自己的模块。 我们创建好自己想要的接口&#xff0c;我这个是无参的查…