行为树详解(4)——节点参数配置化
【分析】
行为树是否足够灵活强大依赖于足够丰富的各类条件节点和动作节点,在实现这些节点时,不可避免的,节点本身需要有一些参数供配置。
这些参数可以分为静态的固定值的参数以及动态读取设置的参数。
静态参数直接设置为Public即可,动态参数的难点在于可能需要获取不同模块的数据。(这里认为数据是这些模块的某个属性或字段)
节点是通用的,不可能在获取模块A的数据时在Update中调用A方法,在获取模块B的数据时调用B方法,这里需要一个统一的调用方式。
获取属性值的关键在于获取实例类和属性名,在代码上就可以通过实例类.属性名获取到属性值
因此,动态参数的配置内容为类名和字段名,需要通过代码自动生成[实例类.属性名]的调用,这必须要依赖反射。
理论上我们可以通过类A.类B.类C这样生成嵌套的调用。但这样通过程序去实现是成本较高,出现Bug容易导致错乱。
需要通过流程做规范,最多通过某个类就可以获取到值。
例如,在Unity中,脚本挂在GameObject上,可以通过统一的GetComponet获取;有个属性系统,可以通过一个的单例获取等等,这于具体的项目相关。这里我们用GetComponet
为了实现动态的设置和获取值,我们需要将这些动态值做封装,设置Getter和Setter
【行为树数据初始化】
参数配置是整个行为树配置的一部分,在此之前,我们需要补充实现前文缺省的初始化数据部分
我们需要有一个结构来描述行为树的配置数据,这个结构是用于运行时初始化数据的,可以将编辑数据和运行时数据结构分隔开来。
数据显而易见的可以分为三个层次:行为树的数据、节点的数据、参数的数据
比较难解决的是最下层的参数数据,这在很多配置数据中是通用的,其主要问题是参数类型、数量、值等各不相同,如何做一个统一的描述?
方式一是采用Json描述不同节点的参数,每个参数各自解析,便于任意扩展,可以用Unity提供的JsonUtility.ToJson和JsonUtility.FromJson做编码和解析。更进一步的,可以看到这和网络协议的编码和解码类似,在追求性能和效率时可以考虑用ProtoBuffer
方式二是使用固定的结构来存储所有类型数据,每个参数各自解析。
行为树节点基本参数是可以做明确的,这里采用方式二
【生命周期】
行为树需要初始化读取配置数据,节点也需要有初始化
行为树在Update中执行,轮询所有节点,节点需要有Update方法。行为树本身也不一定需要每帧都执行,可以根据实际情况定期执行等
大多数节点只在Update中执行即可,有些节点自身包含参数数据,需要重置。
有些特殊的节点在首次执行时,有特殊的逻辑处理,需要有特殊的Start方法
行为树销毁时,节点数据也需要销毁
【代码实现】
配置数据结构
#region BTConfigDatapublic class BehaviourTreeData:ScriptableObject{public string btName;public UpdateType updateType;public float updateTime;public List<NodeInfo> nodes = new List<NodeInfo>();}[Serializable]public class NodeInfo{public int nodeId;public string nodeName;public string nodeType;public List<NodeStaticParams> staticParams = new List<NodeStaticParams>();public List<NodeDynamicParams> dynamicParams = new List<NodeDynamicParams>();public List<int> subNodes = new List<int>();}[Serializable]public class NodeStaticParams{public string paramType;public string paramName;public List<int> intParams;public List<string> strParams;//节点类型是有限的,直接对每个类型做编码解码public void Encode(string name,bool value){paramType = "bool";paramName = name;intParams = new List<int>();intParams.Add(value ? 1 : 0);}public void Encode(string name,int value){paramType = "int";paramName = name;intParams = new List<int>();intParams.Add(value);}public void Encode(string name, float value){paramType = "float";paramName = name;intParams = new List<int>();intParams.Add((int)value*1000);}public void Encode(string name, string value){paramType = "string";paramName = name;strParams = new List<string>();strParams.Add(value);}public void Encode(string name, Comparison value){paramType = "comparison";paramName = name;intParams = new List<int>();intParams.Add((int)value);}public void Encode(string name, List<int> value){paramType = "listint";paramName = name;intParams = new List<int>();intParams.AddRange(value);}public void Encode(string name, List<float> value){paramType = "listfloat";paramName = name;intParams = value.Select(x=>(int)(x*1000)).ToList();}public void Encode(string name, List<string> value){paramType = "liststring";paramName = name;strParams = new List<string>();strParams.AddRange(value);}public object Decode(){object value = null;switch (paramType){case "bool": value = intParams[0] > 1;break;case "float": value = ((float)intParams[0]) / 1000; break;case "string": value = strParams[0]; break;case "int": value = intParams[0]; break;case "listint": value = new List<int>(intParams); break;case "listfloat":value = intParams.Select(x=>((float)x)/1000).ToList(); break;case "liststring":value = new List<string>(strParams); break;case "comparison":value = (Comparison)intParams[0];break;default:break;}return value;}}[Serializable]public class NodeDynamicParams{public string paramType;public string paramName;public string classType;public string fieldName;public bool isProperty;//最好字段都是属性}public class DynamicParams{public string paramName;public virtual void Init(BehaviorTree bt, NodeDynamicParams param){}}public class DynamicParams<T>: DynamicParams{protected Func<T> getter;protected Action<T> setter;public T Value {get {if(getter != null){return getter();}return default(T);}set {if(setter != null){setter.Invoke(value);}}}public override void Init(BehaviorTree bt, NodeDynamicParams param){var classType = Type.GetType(param.classType);var classIntance = bt.owner.GetComponent(classType);if(param.isProperty){var property = classIntance.GetType().GetProperty(param.fieldName);var getMethod = property.GetGetMethod(true);var getter = (Func<T>)Delegate.CreateDelegate(typeof(Func<T>), classIntance, getMethod);this.getter = getter;var setMethod = property.GetSetMethod(true);var setter = (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), classIntance, setMethod);this.setter = setter;}else{var fieldInfo = classIntance.GetType().GetField(param.fieldName);Func<T> getter = () =>{return (T)fieldInfo.GetValue(classIntance);};this.getter = getter;Action<T> setter = (value) =>{fieldInfo.SetValue(classIntance, value);};this.setter = setter;}}}#endregion
行为树
public class BehaviorTree{public string btName;public int btId;public UpdateType updateType;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>();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;//通常初始化有两种写法,一种是在管理者中完成管理者自身以及被管理者的数据初始化;另一种是管理者完成自身的初始化后将被数据传递给被管理者,被管理者自身完成初始化//第二种方式更加灵活可变,且不需要关注行为树结构是怎么样的,只需每个节点做好自身的初始化//在第二种方式下涉及如何将数据递归传递的问题,为统一获取,将数据记录在管理者上,被管理者根据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(updateType == UpdateType.EveryFrame){Update();}else if(updateType == UpdateType.FixedTime){curTime += time;if(curTime>updateTime){curTime = 0;Update();}}}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 class RootNode: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);}}protected override NodeStatus OnUpdate(){return subNode.Update();}}public class ControlNode:Node { public List<Node> subNodes;public int curSubIndex;protected override void OnInit(NodeInfo nodeInfo){foreach (var item in nodeInfo.subNodes){var subNodeInfo = owner.GetNodeInfo(item);Type type = Type.GetType(subNodeInfo.nodeType);//根据完整类名,例如反射创建类及其实例var node = (Node)Activator.CreateInstance(type);subNodes.Add(node);node.Init(subNodeInfo, owner);}foreach (var item in nodeInfo.staticParams){var field = this.GetType().GetField(item.paramName);//这里类型有限,直接用对每个类型做判断实现类型转换field.SetValue(this, item.Decode());}foreach (var item in nodeInfo.dynamicParams){var paramtype = Type.GetType(item.paramType);var paramInstance = (DynamicParams)Activator.CreateInstance(paramtype);paramInstance.Init(owner, item);var fieldInfo = GetType().GetField(item.paramName);fieldInfo.SetValue(this, paramInstance);}}public class BoolCompareSelectorNode:ControlNode{public bool targetValue;public DynamicParams<bool> curValue;public bool targetValues { get; set; }public bool GetBool(bool a){return a;}protected override NodeStatus OnUpdate(){curSubIndex = targetValue == curValue.Value ? 0 : 1;var status = subNodes[curSubIndex].Update();return status;}}public class IntCompareSelectorNode : ControlNode{public int targetValue;public Comparison comparison;public DynamicParams<int> curValue;protected override NodeStatus OnUpdate(){bool res = true;switch(comparison){case Comparison.SmallerThan: res = curValue.Value<targetValue; break;case Comparison.GreaterThan: res = curValue.Value >targetValue; break;case Comparison.Equal: res = curValue.Value == targetValue;break;case Comparison.SmallerThanOrEqual: res = curValue.Value <= targetValue; break;case Comparison.GreaterThanOrEqual: res = curValue.Value >= targetValue; break;}curSubIndex = res?0:1;var status = subNodes[curSubIndex].Update();return status;}}public class FloatCompareSelectorNode : ControlNode{public float targetValue;public Comparison comparison;public DynamicParams<float> curValue;protected override NodeStatus OnUpdate(){bool res = true;switch (comparison){case Comparison.SmallerThan: res = curValue.Value < targetValue; break;case Comparison.GreaterThan: res = curValue.Value > targetValue; break;case Comparison.Equal: res = curValue.Value == targetValue; break;case Comparison.SmallerThanOrEqual: res = curValue.Value <= targetValue; break;case Comparison.GreaterThanOrEqual: res = curValue.Value >= targetValue; break;}curSubIndex = res ? 0 : 1;var status = subNodes[curSubIndex].Update();return status;}}public class IntRangeSelectorNode:ControlNode{public List<int> intRange;public DynamicParams<int> curValue;protected override NodeStatus OnUpdate(){for (int i = 0;i<intRange.Count;i++){if (curValue.Value < intRange[i]){curSubIndex = i;break;}}var status = subNodes[curSubIndex].Update();return status;}}public class FloatRangeSelectorNode : ControlNode{public List<float> intRange;public DynamicParams<float> curValue;protected override NodeStatus OnUpdate(){for (int i = 0; i < intRange.Count; i++){if (curValue.Value < intRange[i]){curSubIndex = i;break;}}var status = subNodes[curSubIndex].Update();return status;}}public class ActionNode:Node{private bool start;protected override NodeStatus OnUpdate(){if(!start){Start();start = true; }return base.OnUpdate();}private void Start(){OnStart();}protected virtual void OnStart(){}protected override void OnEnd(){start = false;}}
相关文章:
行为树详解(4)——节点参数配置化
【分析】 行为树是否足够灵活强大依赖于足够丰富的各类条件节点和动作节点,在实现这些节点时,不可避免的,节点本身需要有一些参数供配置。 这些参数可以分为静态的固定值的参数以及动态读取设置的参数。 静态参数直接设置为Public即可&…...
计算机网络中的三大交换技术详解与实现
目录 计算机网络中的三大交换技术详解与实现1. 计算机网络中的交换技术概述1.1 交换技术的意义1.2 三大交换技术简介 2. 电路交换技术2.1 理论介绍2.2 Python实现及代码详解2.3 案例分析 3. 分组交换技术3.1 理论介绍3.2 Python实现及代码详解3.3 案例分析 4. 报文交换技术4.1 …...
《杨辉三角》
题目描述 给出 n(1≤n≤20)n(1≤n≤20),输出杨辉三角的前 nn 行。 如果你不知道什么是杨辉三角,可以观察样例找找规律。 输入格式 无 输出格式 无 输入输出样例 输入 #1复制 6 输出 #1复制 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 C语言…...
ARM学习(35)单元测试框架以及MinGW GCC覆盖率报告
单元测试框架以及MinGW GCC覆盖率报告 1、单元测试与覆盖率简介 随着代码越写越多,越来越需要注意自测的重要性,基本可以提前解决90%的问题,所以就来介绍一下单元测试,单元测试是否测试充分,需要进行评价,覆盖率就是单元测试是否充分的评估工具。 例如跑过单元测试后,…...
边缘计算+人工智能:让设备更聪明的秘密
引言:日常生活中的“智能”设备 你是否发现,身边的设备正变得越来越“聪明”? 早上醒来时,智能音箱已经根据你的日程播放舒缓音乐;走进厨房,智能冰箱提醒你今天的食材库存;而在城市道路上&…...
neo4j知识图谱AOPC的安装方法
AOPC下载链接:aopc全版本github下载 APOC,全称为Awesome Procedures On Cypher,是Neo4j图数据库的一个非常强大和流行的扩展库。它极大地丰富了Cypher查询语言的功能,提供了超过450个过程(procedures)和函数…...
图像分割数据集植物图像叶片健康状态分割数据集labelme格式180张3类别
数据集格式:labelme格式(不包含mask文件,仅仅包含jpg图片和对应的json文件) 图片数量(jpg文件个数):180 标注数量(json文件个数):180 标注类别数:3 标注类别名称:["Healthy","nitrogen deficiency"…...
Python学习(二)—— 基础语法(上)
目录 一,表达式和常量和变量 1.1 表达式 1.2 变量 1.3 动态类型特性 1.4 输入 二,运算符 2.1 算术运算符 2.2 关系运算符 2.3 逻辑运算符 2.4 赋值运算符 2.5 练习 三,语句 3.1 条件语句 3.2 while循环 3.3 for循环 四&#…...
Cesium-(Primitive)-(CircleOutlineGeometry)
CircleOutlineGeometry 效果: CircleOutlineGeometry 是 CesiumJS 中的一个类,它用来描述在椭球体上圆的轮廓。以下是 CircleOutlineGeometry 的构造函数属性,以表格形式展示: 属性名类型默认值描述centerCartesian3圆心点在固定坐标系中的坐标。radiusnumber圆的半径,…...
计算机网络技术基础:2.计算机网络的组成
计算机网络从逻辑上可以分为两个子网:资源子网和通信子网。 一、资源子网 资源子网主要负责全网的数据处理业务,为全网用户提供各种网络资源与网络服务。资源子网由主机、终端、各种软件资源与信息资源等组成。 1)主机 主机是资源子网的主要…...
EasyExcel使用管道流连接InputStream和OutputStream
前言 Java中的InputSteam 是程序从其中读取数据, OutputSteam是程序可以往里面写入数据。 如果我们有在项目中读取数据库的记录, 在转存成Excel文件, 再把文件转存到OSS中。 生成Excel使用的是阿里的EasyExcel 。 他支持Output的方式写出文件内容。 而…...
OpenWebUI连接不上Ollama模型,Ubuntu24.04
这里写自定义目录标题 问题介绍解决方法 问题介绍 操作系统 Ubuntu24.04Ollama 使用默认安装方法(官网https://github.com/ollama/ollama) curl -fsSL https://ollama.com/install.sh | sh 安装在本机OpenWebUI 使用默认docker安装方法(官网…...
C#C++获取当前应用程序的安装目录和工作目录
很多时候,用户自己点击打开read.exe加载的时候都没有问题,读取ini配置文件也没有问题。但是如果应用程序是开机启动呢?32位Windows系统当前目录是C盘的windows\system32;而64位系统软件启动后默认的当前目录是:C:\Wind…...
Linux中vi和vim的区别详解
文章目录 Linux中vi和vim的区别详解一、引言二、vi和vim的起源与发展三、功能和特性1、语法高亮2、显示行号3、编辑模式4、可视化界面5、功能扩展6、插件支持 四、使用示例1、启动编辑器2、基本操作 五、总结 Linux中vi和vim的区别详解 一、引言 在Linux系统中,vi和…...
2021 年 6 月青少年软编等考 C 语言四级真题解析
目录 T1. 数字三角形问题思路分析T2. 大盗思路分析T3. 最大子矩阵思路分析T4. 小球放盒子思路分析T1. 数字三角形问题 上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。 注…...
UE5编辑器下将RenderTarget输出为UTexture并保存
在使用UE5开发项目时,RenderTarget是一种非常强大的工具,常用于生成实时纹理效果、后处理和调试。而将RenderTarget的内容转换为UTexture并储存,是许多编辑器内的需求都需要的功能。 1.材质球输出至Texture 首先创建一个Actor类,…...
【漏洞复现】CVE-2024-34102 Magento Open Source XXE漏洞
目录 漏洞介绍 影响版本 环境搭建 查看版本 漏洞复现 手动复现 漏洞 poc Magento Open Source 是一个免费开源的电子商务平台,适合中小企业或开发团队通过自定义代码和插件创建在线商店。它由社区开发和支持,功能强大但需要更多的技术投入。Adobe…...
soul大数据面试题及参考答案
如何看待数据仓库? 数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。 从数据存储角度看,它整合了来自多个数据源的数据。这些数据源可能包括业务系统数据库、日志文件等各种结构化和非结构化数据。例如,在电商企业中,它会整合订…...
GLM-4-Plus初体验
引言:为什么高效的内容创作如此重要? 在当前竞争激烈的市场环境中,内容创作已成为品牌成功的重要支柱。无论是撰写营销文案、博客文章、社交媒体帖子,还是制作广告,优质的内容不仅能够帮助品牌吸引目标受众的注意力&a…...
基于springboot+vue的高校校园交友交流平台设计和实现
文章目录 系统功能部分实现截图 前台模块实现管理员模块实现 项目相关文件架构设计 MVC的设计模式基于B/S的架构技术栈 具体功能模块设计系统需求分析 可行性分析 系统测试为什么我? 关于我项目开发案例我自己的网站 源码获取: 系统功能 校园交友平台…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
