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

行为树详解(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)——节点参数配置化

【分析】 行为树是否足够灵活强大依赖于足够丰富的各类条件节点和动作节点&#xff0c;在实现这些节点时&#xff0c;不可避免的&#xff0c;节点本身需要有一些参数供配置。 这些参数可以分为静态的固定值的参数以及动态读取设置的参数。 静态参数直接设置为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)&#xff0c;输出杨辉三角的前 nn 行。 如果你不知道什么是杨辉三角&#xff0c;可以观察样例找找规律。 输入格式 无 输出格式 无 输入输出样例 输入 #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%的问题,所以就来介绍一下单元测试,单元测试是否测试充分,需要进行评价,覆盖率就是单元测试是否充分的评估工具。 例如跑过单元测试后,…...

边缘计算+人工智能:让设备更聪明的秘密

引言&#xff1a;日常生活中的“智能”设备 你是否发现&#xff0c;身边的设备正变得越来越“聪明”&#xff1f; 早上醒来时&#xff0c;智能音箱已经根据你的日程播放舒缓音乐&#xff1b;走进厨房&#xff0c;智能冰箱提醒你今天的食材库存&#xff1b;而在城市道路上&…...

neo4j知识图谱AOPC的安装方法

AOPC下载链接&#xff1a;aopc全版本github下载 APOC&#xff0c;全称为Awesome Procedures On Cypher&#xff0c;是Neo4j图数据库的一个非常强大和流行的扩展库。它极大地丰富了Cypher查询语言的功能&#xff0c;提供了超过450个过程&#xff08;procedures&#xff09;和函数…...

图像分割数据集植物图像叶片健康状态分割数据集labelme格式180张3类别

数据集格式&#xff1a;labelme格式(不包含mask文件&#xff0c;仅仅包含jpg图片和对应的json文件) 图片数量(jpg文件个数)&#xff1a;180 标注数量(json文件个数)&#xff1a;180 标注类别数&#xff1a;3 标注类别名称:["Healthy","nitrogen deficiency"…...

Python学习(二)—— 基础语法(上)

目录 一&#xff0c;表达式和常量和变量 1.1 表达式 1.2 变量 1.3 动态类型特性 1.4 输入 二&#xff0c;运算符 2.1 算术运算符 2.2 关系运算符 2.3 逻辑运算符 2.4 赋值运算符 2.5 练习 三&#xff0c;语句 3.1 条件语句 3.2 while循环 3.3 for循环 四&#…...

Cesium-(Primitive)-(CircleOutlineGeometry)

CircleOutlineGeometry 效果: CircleOutlineGeometry 是 CesiumJS 中的一个类,它用来描述在椭球体上圆的轮廓。以下是 CircleOutlineGeometry 的构造函数属性,以表格形式展示: 属性名类型默认值描述centerCartesian3圆心点在固定坐标系中的坐标。radiusnumber圆的半径,…...

计算机网络技术基础:2.计算机网络的组成

计算机网络从逻辑上可以分为两个子网&#xff1a;资源子网和通信子网。 一、资源子网 资源子网主要负责全网的数据处理业务&#xff0c;为全网用户提供各种网络资源与网络服务。资源子网由主机、终端、各种软件资源与信息资源等组成。 1&#xff09;主机 主机是资源子网的主要…...

EasyExcel使用管道流连接InputStream和OutputStream

前言 Java中的InputSteam 是程序从其中读取数据&#xff0c; OutputSteam是程序可以往里面写入数据。 如果我们有在项目中读取数据库的记录&#xff0c; 在转存成Excel文件, 再把文件转存到OSS中。 生成Excel使用的是阿里的EasyExcel 。 他支持Output的方式写出文件内容。 而…...

OpenWebUI连接不上Ollama模型,Ubuntu24.04

这里写自定义目录标题 问题介绍解决方法 问题介绍 操作系统 Ubuntu24.04Ollama 使用默认安装方法&#xff08;官网https://github.com/ollama/ollama&#xff09; curl -fsSL https://ollama.com/install.sh | sh 安装在本机OpenWebUI 使用默认docker安装方法&#xff08;官网…...

C#C++获取当前应用程序的安装目录和工作目录

很多时候&#xff0c;用户自己点击打开read.exe加载的时候都没有问题&#xff0c;读取ini配置文件也没有问题。但是如果应用程序是开机启动呢&#xff1f;32位Windows系统当前目录是C盘的windows\system32&#xff1b;而64位系统软件启动后默认的当前目录是&#xff1a;C:\Wind…...

Linux中vi和vim的区别详解

文章目录 Linux中vi和vim的区别详解一、引言二、vi和vim的起源与发展三、功能和特性1、语法高亮2、显示行号3、编辑模式4、可视化界面5、功能扩展6、插件支持 四、使用示例1、启动编辑器2、基本操作 五、总结 Linux中vi和vim的区别详解 一、引言 在Linux系统中&#xff0c;vi和…...

2021 年 6 月青少年软编等考 C 语言四级真题解析

目录 T1. 数字三角形问题思路分析T2. 大盗思路分析T3. 最大子矩阵思路分析T4. 小球放盒子思路分析T1. 数字三角形问题 上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。 注…...

UE5编辑器下将RenderTarget输出为UTexture并保存

在使用UE5开发项目时&#xff0c;RenderTarget是一种非常强大的工具&#xff0c;常用于生成实时纹理效果、后处理和调试。而将RenderTarget的内容转换为UTexture并储存&#xff0c;是许多编辑器内的需求都需要的功能。 1.材质球输出至Texture 首先创建一个Actor类&#xff0c…...

【漏洞复现】CVE-2024-34102 Magento Open Source XXE漏洞

目录 漏洞介绍 影响版本 环境搭建 查看版本 漏洞复现 手动复现 漏洞 poc Magento Open Source 是一个免费开源的电子商务平台&#xff0c;适合中小企业或开发团队通过自定义代码和插件创建在线商店。它由社区开发和支持&#xff0c;功能强大但需要更多的技术投入。Adobe…...

soul大数据面试题及参考答案

如何看待数据仓库? 数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。 从数据存储角度看,它整合了来自多个数据源的数据。这些数据源可能包括业务系统数据库、日志文件等各种结构化和非结构化数据。例如,在电商企业中,它会整合订…...

GLM-4-Plus初体验

引言&#xff1a;为什么高效的内容创作如此重要&#xff1f; 在当前竞争激烈的市场环境中&#xff0c;内容创作已成为品牌成功的重要支柱。无论是撰写营销文案、博客文章、社交媒体帖子&#xff0c;还是制作广告&#xff0c;优质的内容不仅能够帮助品牌吸引目标受众的注意力&a…...

基于springboot+vue的高校校园交友交流平台设计和实现

文章目录 系统功能部分实现截图 前台模块实现管理员模块实现 项目相关文件架构设计 MVC的设计模式基于B/S的架构技术栈 具体功能模块设计系统需求分析 可行性分析 系统测试为什么我&#xff1f; 关于我项目开发案例我自己的网站 源码获取&#xff1a; 系统功能 校园交友平台…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...

力扣热题100 k个一组反转链表题解

题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...

Qemu arm操作系统开发环境

使用qemu虚拟arm硬件比较合适。 步骤如下&#xff1a; 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载&#xff0c;下载地址&#xff1a;https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解

文章目录 一、开启慢查询日志&#xff0c;定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...

C++--string的模拟实现

一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现&#xff0c;其目的是加强对string的底层了解&#xff0c;以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量&#xff0c;…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...