当前位置: 首页 > news >正文

行为树详解(5)——事件驱动

【分析】

如果行为树的节点很多,那么会存在要经过很多节点才会走到动作节点的情况。显然,性能上不如状态机。

每帧都需要重新遍历一系列节点才会走到动作节点,而实际上很多条件节点在数帧内不会有变化,这是造成性能问题的重要原因。

行为树每帧的Update其实就是在轮询这些条件,为避免轮询,我们自然而然的希望在一帧直接执行动作节点。

为此,需要将在Running状态的动作节点缓存,直接执行直到成功。

这种方式引起的问题是动作节点不一定总是会执行成功,可能在某一帧,某些条件改变了,动作需要被打断。

为了解决该问题,就需要给动作节点引入一些事件,当特定事件发生时,在动作节点内接收该事件,直接结束动作节点的运行,返回失败。

随后重新开始整个行为树的轮询,找到下一个动作节点。

这就是事件和轮询混合驱动的行为树。

事件本质上就是直接调用,当经过动作节点的路径较长,也即影响该动作节点的条件过多时,不可避免要通过代码调用发多种消息,这就失去了配置的意义。

需要将打断动作节点及节点的逻辑做成单独的Conditional节点,这些节点接收消息或监听变化,以决定是否打断其子节点。

通过轮询这些Conditional节点以简化发多种消息的调用。

当打断发生时,需要重新运行被打断节点的父节点确定新的分支走向。为此,需要将路径上所有节点做缓存。

这里要对控制节点做特殊处理,控制节点会控制自身的子节点运行,其状态受子节点影响,当打断存在时,子节点可能不需要再次运行,因此控制节点在控制子节点运行前需要知道当前是否被打断。

【代码实现】

    public class BehaviorTree{public string btName;public int btId;public UpdateType updateType;public DrivenType drivenType;public float updateTime;private float curTime;public GameObject owner;private Node rootNode;private List<Node> allNodes = new List<Node>();private Dictionary<int,Node> id2Node = new Dictionary<int,Node>();private Dictionary<int,NodeInfo> id2NodeInfo = new Dictionary<int,NodeInfo>();private Dictionary<Type, Dictionary<string, Delegate>> eventDic = new Dictionary<Type, Dictionary<string, Delegate>>();private Node curActionNode;private Stack<Node> activeNodes = new Stack<Node>();private List<ConditionalNode> conditionalNodes = new List<ConditionalNode>();  public static Func<string, BehaviourTreeData> loadFunc = null;public void Init(GameObject owner, string path, Func<string, BehaviourTreeData> loadFunc){this.owner = owner;//这里省略部分边界情况检查的逻辑,if (!string.IsNullOrEmpty(path)){var data = loadFunc?.Invoke(path);btId = owner.GetInstanceID();updateType = data.updateType; updateTime = data.updateTime;drivenType = data.drivenType;//通常初始化有两种写法,一种是在管理者中完成管理者自身以及被管理者的数据初始化;另一种是管理者完成自身的初始化后将被数据传递给被管理者,被管理者自身完成初始化//第二种方式更加灵活可变,且不需要关注行为树结构是怎么样的,只需每个节点做好自身的初始化//在第二种方式下涉及如何将数据递归传递的问题,为统一获取,将数据记录在管理者上,被管理者根据Id从管理者中获取,初始化后可选择将数据释放foreach (var item in data.nodes){id2NodeInfo[item.nodeId] = item;}var rootData = data.nodes[0];//默认第一个是rootNode数据rootNode = new RootNode();rootNode.Init(rootData, this);id2NodeInfo.Clear();//也可以保留}}public void Update(float time){if(drivenType == DrivenType.Polling){PollingUpdate(time);}else if(drivenType == DrivenType.PollingAndEvent){if (curActionNode != null){var actionStatus = curActionNode.Update();if(actionStatus != NodeStatus.Running){curActionNode = null; PollingUpdate(time);}else{if (updateType == UpdateType.FixedTime){curTime += time;}}}else{PollingUpdate(time);}}else if(drivenType == DrivenType.EventDriven){EventUpdate(time);}}private void PollingUpdate(float time){if (updateType == UpdateType.EveryFrame){Update();}else if (updateType == UpdateType.FixedTime){curTime += time;if (curTime > updateTime){curTime = 0;Update();}}}private void EventUpdate(float time){int index = -1;for (int i = 0; i < conditionalNodes.Count; i++){if (conditionalNodes[i].ConditionChanged()) //当条件节点条件改变,其后所有节点都需要重新计算,动作节点需要被打断{index = i;break;}}if (index != -1){              while(activeNodes.Count> 0 && activeNodes.Peek().nodeId != conditionalNodes[index].nodeId)//移除其后所有Active节点{PopNode(activeNodes.Peek());}conditionalNodes[index].Update();//重新运行该节点if (conditionalNodes[index].StatusChanged(out var curStatus)){                    var curNode = (Node)conditionalNodes[index];var parent = conditionalNodes[index].parent;while (parent is ControlNode)//如果父节点是控制节点,其状态会与子节点相关,这里简化处理,直接重新运行{curNode = parent;parent = parent.parent;}if(curNode != conditionalNodes[index]){while (activeNodes.Count > 0 && activeNodes.Peek().nodeId != curNode.nodeId){PopNode(activeNodes.Peek());}curNode.Update();}}}else{var state = activeNodes.Peek().Update();//没有打断,再次运行末尾的动作节点if(state != NodeStatus.Running)//运行结束,重新从根节点开始运行,也可以选择回退到上一个节点开始运行{activeNodes.Clear();conditionalNodes.Clear();rootNode.Update();}}}public void PushNode(Node node){if (drivenType != DrivenType.EventDriven)return;activeNodes.Push(node);if(node is ConditionalNode conditionalNode){conditionalNodes.Add(conditionalNode);}}private void PopNode(Node node){if (drivenType != DrivenType.EventDriven)return;node.End();activeNodes.Pop();if(node is ConditionalNode conditionalNode){conditionalNodes.Remove(conditionalNode);}}public NodeStatus Update(){var status = rootNode.Update();if(status != NodeStatus.Running){rootNode.End();}return status;}public void Destroy(){foreach (var item in allNodes){item.Destroy();}allNodes.Clear();id2Node.Clear();id2NodeInfo.Clear();}public NodeInfo GetNodeInfo(int nodeId){return id2NodeInfo[nodeId];}public void AddNode(Node node){allNodes.Add(node);id2Node[node.nodeId] = node;}public void SetActionNode(Node node){curActionNode = node;}private void RegisterEvent(string eventName, Delegate handler){if (drivenType == DrivenType.Polling)return;if (!eventDic.TryGetValue(handler.GetType(), out var dic)){dic = new Dictionary<string, Delegate>();eventDic[handler.GetType()] = dic;}if (!dic.TryGetValue(eventName, out var value)){dic[eventName] = handler;}else{dic[eventName] = Delegate.Combine(value, handler);}}public void RegisterEvent(string eventNmae,Action action){RegisterEvent(eventNmae, (Delegate)action);}public void RegisterEvent<T>(string eventNmae, Action<T> action){RegisterEvent(eventNmae, (Delegate)action);}public void RegisterEvent<T1,T2>(string eventNmae, Action<T1,T2> action){RegisterEvent(eventNmae, (Delegate)action);}public void RegisterEvent<T1, T2,T3>(string eventNmae, Action<T1, T2,T3> action){RegisterEvent(eventNmae, (Delegate)action);}private Delegate GetDelegate(string eventName,Type type){if (drivenType == DrivenType.Polling)return null;if (eventDic.TryGetValue(type, out var dic)){if (dic.TryGetValue(eventName, out var handler)){return handler;}}return null;}public void SendEvent(string eventName){            if(GetDelegate(eventName, typeof(Action)) is Action action){action();}}public void SendEvent<T>(string eventName, T t){if (GetDelegate(eventName, typeof(Action<T>)) is Action<T> action){action(t);}}public void SendEvent<T1,T2>(string eventName, T1 t1,T2 t2){if (GetDelegate(eventName, typeof(Action<T1,T2>)) is Action<T1,T2> action){action(t1,t2);}}public void SendEvent<T1, T2,T3>(string eventName, T1 t1, T2 t2,T3 t3){if (GetDelegate(eventName, typeof(Action<T1, T2,T3>)) is Action<T1, T2,T3> action){action(t1, t2,t3);}}}public enum DrivenType{Polling,PollingAndEvent,EventDriven}
    public class Node{public string nodeName;public int nodeId;public NodeStatus status;public BehaviorTree owner;public Node parent;public void Init(NodeInfo nodeInfo, BehaviorTree owner){this.owner = owner;this.nodeName = nodeInfo.nodeName;this.nodeId = nodeInfo.nodeId;OnInit(nodeInfo);owner.AddNode(this);//对于字段的配置可以通过字段名反射获取字段然后设置值,但这里允许的值是有限的,我们直接在每个节点写对应的处理}public NodeStatus Update(){owner.PushNode(this);return OnUpdate();}public void End()//方法名字叫Exit也是一样的{OnEnd();}protected virtual void OnInit(NodeInfo nodeInfo){}protected virtual NodeStatus OnUpdate(){return NodeStatus.Success;}protected virtual void OnEnd(){}public virtual void Destroy(){}}public class ConditionalNode:Node{public Node subNode;protected override void OnInit(NodeInfo nodeInfo){if (nodeInfo.subNodes.Count > 0){var subNodeInfo = owner.GetNodeInfo(nodeInfo.subNodes[0]);Type type = Type.GetType(subNodeInfo.nodeType);subNode = (Node)Activator.CreateInstance(type);subNode.Init(subNodeInfo, owner);subNode.parent = this;}}protected override NodeStatus OnUpdate(){return subNode.Update();}public virtual bool ConditionChanged(){return false;}public virtual bool StatusChanged(out NodeStatus status){status = NodeStatus.Success;return false;}public class ActionNode:Node{private bool start;private bool stop;protected override NodeStatus OnUpdate(){if (!start){Start();start = true;}var res = base.OnUpdate();if(stop){res = NodeStatus.Failure;}if (res == NodeStatus.Running){owner.SetActionNode(this);}return res;}private void Start(){OnStart();}protected virtual void OnStart(){//各动作节点自定义注册事件}protected override void OnEnd(){start = false;stop = false;}}

相关文章:

行为树详解(5)——事件驱动

【分析】 如果行为树的节点很多&#xff0c;那么会存在要经过很多节点才会走到动作节点的情况。显然&#xff0c;性能上不如状态机。 每帧都需要重新遍历一系列节点才会走到动作节点&#xff0c;而实际上很多条件节点在数帧内不会有变化&#xff0c;这是造成性能问题的重要原…...

3.若依前端项目拉取、部署、访问

因为默认RuoYi-Vue是使用的Vue2,所以需要另外去下载vue3来部署。 拉取代码 git clone https://gitee.com/ys-gitee/RuoYi-Vue3.git 安装node才能执行npm相关的命令 执行命令npm install 如果npm install比较慢的话&#xff0c;需要添加上国内镜像 npm install --registrhttp…...

Debian操作系统相对于Ubuntu有什么优势吗?

更高的稳定性&#xff1a;Debian 以其出色的稳定性闻名&#xff0c;得益于严格的软件包测试和发布流程。其稳定版经过长时间测试与验证&#xff0c;确保了系统的高度稳定&#xff0c;更适合对稳定性要求极高的长期运行服务器环境。而 Ubuntu 虽有稳定版本&#xff0c;但更新周期…...

【漏洞复现】CVE-2015-3337 Arbitrary File Reading

漏洞信息 NVD - CVE-2015-3337 Directory traversal vulnerability in Elasticsearch before 1.4.5 and 1.5.x before 1.5.2, when a site plugin is enabled, allows remote attackers to read arbitrary files via unspecified vectors. 在安装了具有“site”功能的插件以…...

win10、win11-鼠标右键还原、暂停更新

系统优化 win 10jihuo win 11jihuo鼠标右键还原暂停更新 update 2024.12.28win 10 jihuo winx&#xff0c;打开powershell管理员&#xff0c;输入以下命令,选择1并等待 irm https://get.activated.win | iex参考&#xff1a;https://www.bilibili.com/video/BV1TN411M72J/?sp…...

FFmpeg来从HTTP拉取流并实时推流到RTMP服务器

当使用FFmpeg来从HTTP拉取流并实时推流到RTMP服务器时&#xff0c;你可以使用以下命令&#xff1a; ffmpeg -i http://输入流地址 -c:v copy -c:a copy -f flv rtmp://RTMP服务器地址/应用名称/流名称 这是一个基本的命令示例&#xff0c;其中&#xff1a; - -i http://输入流地…...

Quo Vadis, Anomaly Detection? LLMs and VLMs in the Spotlight 论文阅读

文章信息&#xff1a; 原文链接&#xff1a;https://arxiv.org/abs/2412.18298 Abstract 视频异常检测&#xff08;VAD&#xff09;通过整合大语言模型&#xff08;LLMs&#xff09;和视觉语言模型&#xff08;VLMs&#xff09;取得了显著进展&#xff0c;解决了动态开放世界…...

Rust : tokio中select!

关于tokio的select宏&#xff0c;有不少的用途。包括超时和竞态选择等。 关于select宏需要关注&#xff0c;相关的异步条件&#xff0c;会同时执行&#xff0c;只是当有一个最早完成时&#xff0c;会执行“抛弃”和“对应”策略。 说明&#xff1a;对本文以下素材的来源表示感…...

【hackmyvm】hacked靶机wp

tags: HMVrootkitDiamorphine Type: wp 1. 基本信息^toc 文章目录 1. 基本信息^toc2. 信息收集2.1. 端口扫描2.2. 目录扫描2.3. 获取参数 3. 提权 靶机链接 https://hackmyvm.eu/machines/machine.php?vmHacked 作者 sml 难度 ⭐️⭐️⭐️⭐️️ 2. 信息收集 2.1. 端口扫描…...

MaixBit k210学习记录

开发背景&#xff1a;Window系统主机&#xff0c;在主机上安装了虚拟机&#xff08;VirtualBoxUbuntu23.04&#xff09; 目标实现&#xff1a;在虚拟机&#xff08;Ubuntu&#xff09;中&#xff0c;实现对Maix bit&#xff08;k210&#xff09;开发板的开发 虚拟机的安装参考…...

Wordperss漏洞 DeDeCMS漏洞

Wordperss漏洞 环境搭建 #执⾏命令 cd /vulhub/wordpress/pwnscriptum docker-compose up -d #靶场地址 http://8.155.7.173:8080/wp-admin/ 注册账号 登录 漏洞一&#xff1a;后台修改模板拿WebShell 步骤一&#xff1a;思路是修改其WP的模板写入⼀句话木马后门并访问其文件…...

如何构建有效的AI Agents:从复杂到简约——深度解读Claude实践总结《Building effective agents》(上)

在人工智能技术日新月异的今天&#xff0c;大语言模型(LLM)已经成为技术创新的热点。 然而&#xff0c;在追逐技术前沿的热潮中&#xff0c;我们是否忽视了工程设计的本质&#xff1f; 作为全球人工智能领域的领军企业之一&#xff0c;Anthropic以其在AI安全和伦理方面的深入…...

git status 耗时

某个git库每次status一下就是半小时起步&#xff0c;gc后还是没有效果&#xff0c;后来排查记录发现某笔记录提交几百G的冗余文件&#xff0c;虽然revert了&#xff0c;但是还是存在库中&#xff0c;遂如下清理&#xff1a; # 查找大文件 git verify-pack -v .git/objects/pac…...

C++进阶重点知识(一)|智能指针|右值|lambda|STL|正则表达式

目录 1智能指针1.shared_ptr1.1 shared_ptr的基本用法使用shared_ptr要注意的问题运用 2.unique_ptr独占的智能指针示例&#xff1a;管理动态内存 3.weak_ptr弱引用的智能指针weak_ptr的基本用法lock 的作用&#xff1a;weak_ptr返回this指针weak_ptr解决循环引用问题weak_ptr使…...

OSCP打靶大冒险之Solidstate:多端口获取信息,shell逃逸,计划任务提权

声明&#xff01; 学习资源来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…...

八股(One Day one)

最近老是看到一些面试的视频&#xff0c;对于视频内部面试所提到的八股文&#xff0c;感觉是知道是什么&#xff0c;但是要说的话&#xff0c;却又不知道该怎么说&#xff08;要不咋称之为八股文呢&#xff09;&#xff0c;所以就想到写一篇八股文总结的博客&#xff0c;以便进…...

如何快速又安全的实现端口转发【Windows MAC linux通用】

背景 有很多程序是在虚拟机上运行的&#xff0c;返回的url 又是127.0.0.1。在个人电脑上调试需要解决这个问题。端口转发是一个不错的方法 可能的解决办法&#xff1a; 1.修改程序&#xff0c;返回虚拟机的ip &#xff08;要改代码&#xff0c;换虚拟机还要再改代码&#xf…...

LongLLMLingua: 长上下文场景的智能提示压缩框架

LongLLMLingua: 长上下文场景的智能提示压缩框架 一、框架概述 核心目标 解决长上下文场景下的计算成本问题降低处理延迟提升模型性能表现 创新特点 问题感知的粗到细压缩策略文档重排序机制动态压缩比例控制压缩后的子序列恢复策略 二、技术方法详解 粗粒度压缩 (a) 文…...

Django serializers:把ValidationError处理的更优雅

开发中必不可少的会对参数做各种校验&#xff0c;必填、唯一等等&#xff0c;这个时候我们需要在校验失败时抛出友好、清晰的提示。 直接使用serializers做校验&#xff0c;虽然也能抛出自定义的错误提示&#xff0c;但是其信息格式是嵌套&#xff0c;并不适合直接反映在前端页…...

CASA(Carnegie-Ames-Stanford Approach) 模型原理及实践技术

植被作为陆地生态系统的重要组成部分对于生态环境功能的维持具有关键作用。植被净初级生产力&#xff08;Net Primary Productivity, NPP&#xff09;是指单位面积上绿色植被在单位时间内由光合作用生产的有机质总量扣除自养呼吸的剩余部分。 专题&#xff08;一&#xff09;C…...

页面输入数据的表格字段(如 Web 表单或表格控件)与后台数据库进行交互时常用的两种方式

“从页面输入数据的表格字段(如 Web 表单或表格控件)在与后台数据库进行交互时,常用的有两种方式:” 🎯 两种方式(操作调用数据库、绑定数据) 🚀 方式1:前端代码提交数据到后端,再由后端调用数据库 💡 原理和逻辑: 用户在页面上(比如输入表单、表格)输入数据…...

对 `llamafactory-cli api -h` 输出的详细解读

llamafactory-cli 是 LlamaFactory 项目提供的命令行接口工具&#xff0c;它允许用户通过命令行参数来配置和运行大型语言模型的各种任务&#xff0c;如预训练&#xff08;PT&#xff09;、有监督微调&#xff08;SFT&#xff09;、奖励模型训练&#xff08;RM&#xff09;、基…...

使用 Python 制作 GIF 动图,并打包为 EXE 可执行程序

文章目录 成品百度网盘下载&#x1f3ac; 使用 Python 制作 GIF 动图&#xff0c;并打包为 EXE 可执行程序&#xff08;含图形界面&#xff09;&#x1f9f0; 环境准备&#x1f4bb; 功能预览&#x1f9d1;‍&#x1f4bb; 完整代码&#xff08;图形界面 功能&#xff09;如何…...

嵌入式硬件篇---龙芯2k1000串口

针对串口错误 “device reports readiness to read but returned no data (Device disconnected or multiple access on port?)” 的排查和解决方法 硬件方面 检查连接 确认串口设备&#xff08;如串口线、连接的模块等&#xff09;与龙芯设备之间的物理连接是否牢固&#xf…...

华为盘古 Ultra MoE 模型:国产 AI 的技术突破与行业影响

2025 年 5 月 30日&#xff0c;华为正式发布参数规模达 7180 亿的盘古 Ultra MoE 模型&#xff0c;全程基于昇腾 AI 计算平台完成训练。这一进展标志着中国在超大规模人工智能模型领域的自主研发能力达到新高度&#xff0c;同时也为全球 AI 技术发展提供了新的技术路径。 盘古 …...

阿里云百炼全解析:一站式大模型开发平台的架构与行业实践

目录 大模型开发范式的革新平台核心架构与技术解析全生命周期开发工作流企业级安全与合规体系行业应用场景与最佳实践未来演进与技术展望1. 大模型开发范式的革新 1.1 从碎片化到平台化的演进 传统大模型开发面临三大核心挑战:算力管理复杂、工具链割裂、安全合规风险高。阿…...

Linux 权限管理入门:从基础到实践

文章目录 引言一、Linux 权限管理概述二、文件权限值的表示方法三、文件访问权限的设置&#xff08;chmod&#xff09;四、file指令&#xff1a;快速识别文件类型五、目录的权限六、普通文件的权限七、权限总结八、粘滞位 引言 在 Linux 系统中&#xff0c;权限管理是确保多用…...

Spring AI Alibaba + Nacos 动态 MCP Server 代理方案

作者&#xff1a;刘宏宇&#xff0c;Spring AI Alibaba Contributor 文章概览 Spring AI Alibaba MCP 可基于 Nacos 提供的 MCP server registry 信息&#xff0c;建立一个中间代理层 Java 应用&#xff0c;将 Nacos 中注册的服务信息转换成 MCP 协议的服务器信息&#xff0c…...

malloc 内存分配机制:brk 与 mmap

一、malloc的两种内存分配策略 malloc 并非直接的系统调用&#xff0c;而是C标准库封装的内存管理函数。它根据应用程序请求的内存大小&#xff0c;智能地选择两种不同的底层机制向操作系统申请内存&#xff1a; 小块内存分配 (< 128KB)&#xff1a;brk() / sbrk() 系统调用…...

[蓝桥杯]春晚魔术【算法赛】

目录 输入格式 输出格式 样例输入 样例输出 运行限制 解决思路 代码说明 复杂度分析 问题描述 在蓝桥卫视春晚的直播现场&#xff0c;魔术师小蓝表演了一个红包魔术。只见他拿出了三个红包&#xff0c;里边分别装有 A、B 和 C 个金币。而后&#xff0c;他挥动魔术棒&a…...