行为树详解(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)——事件驱动
【分析】 如果行为树的节点很多,那么会存在要经过很多节点才会走到动作节点的情况。显然,性能上不如状态机。 每帧都需要重新遍历一系列节点才会走到动作节点,而实际上很多条件节点在数帧内不会有变化,这是造成性能问题的重要原…...
3.若依前端项目拉取、部署、访问
因为默认RuoYi-Vue是使用的Vue2,所以需要另外去下载vue3来部署。 拉取代码 git clone https://gitee.com/ys-gitee/RuoYi-Vue3.git 安装node才能执行npm相关的命令 执行命令npm install 如果npm install比较慢的话,需要添加上国内镜像 npm install --registrhttp…...
Debian操作系统相对于Ubuntu有什么优势吗?
更高的稳定性:Debian 以其出色的稳定性闻名,得益于严格的软件包测试和发布流程。其稳定版经过长时间测试与验证,确保了系统的高度稳定,更适合对稳定性要求极高的长期运行服务器环境。而 Ubuntu 虽有稳定版本,但更新周期…...
【漏洞复现】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,打开powershell管理员,输入以下命令,选择1并等待 irm https://get.activated.win | iex参考:https://www.bilibili.com/video/BV1TN411M72J/?sp…...
FFmpeg来从HTTP拉取流并实时推流到RTMP服务器
当使用FFmpeg来从HTTP拉取流并实时推流到RTMP服务器时,你可以使用以下命令: ffmpeg -i http://输入流地址 -c:v copy -c:a copy -f flv rtmp://RTMP服务器地址/应用名称/流名称 这是一个基本的命令示例,其中: - -i http://输入流地…...
Quo Vadis, Anomaly Detection? LLMs and VLMs in the Spotlight 论文阅读
文章信息: 原文链接:https://arxiv.org/abs/2412.18298 Abstract 视频异常检测(VAD)通过整合大语言模型(LLMs)和视觉语言模型(VLMs)取得了显著进展,解决了动态开放世界…...
Rust : tokio中select!
关于tokio的select宏,有不少的用途。包括超时和竞态选择等。 关于select宏需要关注,相关的异步条件,会同时执行,只是当有一个最早完成时,会执行“抛弃”和“对应”策略。 说明:对本文以下素材的来源表示感…...
【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学习记录
开发背景:Window系统主机,在主机上安装了虚拟机(VirtualBoxUbuntu23.04) 目标实现:在虚拟机(Ubuntu)中,实现对Maix bit(k210)开发板的开发 虚拟机的安装参考…...
Wordperss漏洞 DeDeCMS漏洞
Wordperss漏洞 环境搭建 #执⾏命令 cd /vulhub/wordpress/pwnscriptum docker-compose up -d #靶场地址 http://8.155.7.173:8080/wp-admin/ 注册账号 登录 漏洞一:后台修改模板拿WebShell 步骤一:思路是修改其WP的模板写入⼀句话木马后门并访问其文件…...
如何构建有效的AI Agents:从复杂到简约——深度解读Claude实践总结《Building effective agents》(上)
在人工智能技术日新月异的今天,大语言模型(LLM)已经成为技术创新的热点。 然而,在追逐技术前沿的热潮中,我们是否忽视了工程设计的本质? 作为全球人工智能领域的领军企业之一,Anthropic以其在AI安全和伦理方面的深入…...
git status 耗时
某个git库每次status一下就是半小时起步,gc后还是没有效果,后来排查记录发现某笔记录提交几百G的冗余文件,虽然revert了,但是还是存在库中,遂如下清理: # 查找大文件 git verify-pack -v .git/objects/pac…...
C++进阶重点知识(一)|智能指针|右值|lambda|STL|正则表达式
目录 1智能指针1.shared_ptr1.1 shared_ptr的基本用法使用shared_ptr要注意的问题运用 2.unique_ptr独占的智能指针示例:管理动态内存 3.weak_ptr弱引用的智能指针weak_ptr的基本用法lock 的作用:weak_ptr返回this指针weak_ptr解决循环引用问题weak_ptr使…...
OSCP打靶大冒险之Solidstate:多端口获取信息,shell逃逸,计划任务提权
声明! 学习资源来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&a…...
八股(One Day one)
最近老是看到一些面试的视频,对于视频内部面试所提到的八股文,感觉是知道是什么,但是要说的话,却又不知道该怎么说(要不咋称之为八股文呢),所以就想到写一篇八股文总结的博客,以便进…...
如何快速又安全的实现端口转发【Windows MAC linux通用】
背景 有很多程序是在虚拟机上运行的,返回的url 又是127.0.0.1。在个人电脑上调试需要解决这个问题。端口转发是一个不错的方法 可能的解决办法: 1.修改程序,返回虚拟机的ip (要改代码,换虚拟机还要再改代码…...
LongLLMLingua: 长上下文场景的智能提示压缩框架
LongLLMLingua: 长上下文场景的智能提示压缩框架 一、框架概述 核心目标 解决长上下文场景下的计算成本问题降低处理延迟提升模型性能表现 创新特点 问题感知的粗到细压缩策略文档重排序机制动态压缩比例控制压缩后的子序列恢复策略 二、技术方法详解 粗粒度压缩 (a) 文…...
Django serializers:把ValidationError处理的更优雅
开发中必不可少的会对参数做各种校验,必填、唯一等等,这个时候我们需要在校验失败时抛出友好、清晰的提示。 直接使用serializers做校验,虽然也能抛出自定义的错误提示,但是其信息格式是嵌套,并不适合直接反映在前端页…...
CASA(Carnegie-Ames-Stanford Approach) 模型原理及实践技术
植被作为陆地生态系统的重要组成部分对于生态环境功能的维持具有关键作用。植被净初级生产力(Net Primary Productivity, NPP)是指单位面积上绿色植被在单位时间内由光合作用生产的有机质总量扣除自养呼吸的剩余部分。 专题(一)C…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...
