行为树详解(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的架构技术栈 具体功能模块设计系统需求分析 可行性分析 系统测试为什么我? 关于我项目开发案例我自己的网站 源码获取: 系统功能 校园交友平台…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...

如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...