行为树详解(4)——节点参数配置化

ops/2024/12/16 6:49:45/

【分析】

行为树是否足够灵活强大依赖于足够丰富的各类条件节点和动作节点,在实现这些节点时,不可避免的,节点本身需要有一些参数供配置。

这些参数可以分为静态的固定值的参数以及动态读取设置的参数。

静态参数直接设置为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;}}


http://www.ppmy.cn/ops/142316.html

相关文章

Python毕业设计选题:基于django+vue的疫情数据可视化分析系统

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 员工管理 疫情信息管理 检测预约管理 检测结果…

23种设计模式之命令模式

目录 1. 简介2. 代码2.1 Order &#xff08;命令接口&#xff09;2.2 Stock &#xff08;接收者类&#xff09;2.3 Buy &#xff08;具体命令类&#xff09;2.4 Sell &#xff08;具体命令类&#xff09;2.5 Broker &#xff08;调用者类&#xff09;2.6 Test &#xff08;测试…

国科大智能设备安全-APK逆向分析实验

APK逆向分析实验 使用APK常用逆向分析工具&#xff0c;对提供的移动应用程序APK文件进行逆向分析&#xff0c;提交逆向后代码和分析报告。具体任务如下&#xff1a; 任务一&#xff1a;安装并熟悉Apktool、Jadx等APK常用逆向工具的使用方法&#xff0c;对提供的Facebook Updat…

VMware17版本 命令安装VMtools的方法

若是VMware17版本的虚拟机&#xff0c;虚拟机不再直接提供VMtools的安装包&#xff0c;那么可以通过以下方法来安装VMtools工具。 问题&#xff1a; 解决&#xff1a;使用命令来安装VMtools sudo apt-get install open-vm-tools sudo apt-get install open-vm-tools-desktop …

CSS中相对、固定、绝对及粘性定位的应用场景

在CSS中&#xff0c;不同的定位方式&#xff08;相对定位、固定定位、绝对定位和粘性定位&#xff09;各自有其特定的使用场景。以下是这些定位方式的详细说明和使用场景&#xff1a; 1. 相对定位&#xff08;Relative Positioning&#xff09; 使用场景&#xff1a; 微调元…

2024年特别报告,「十大生活方式」研究数据报告

“一朵花成轻奢品、一只玩偶掀抢购狂潮、一片荒地变文旅圣地…” 近年爆火的野兽派、Jellycat、阿那亚等诸多品牌&#xff0c;与消费者选择的生活方式息息相关。 今年小红书的内容种草、直播电商&#xff0c;也都依循着“生活方式”的轨迹。生活方式的价值所向&#xff0c;可…

群控系统服务端开发模式-应用开发-获取登录者今天操作日志

一、后端api开放路由 在根目录下route文件夹下app.php文件中&#xff0c;在perimission的group中添加如下代码&#xff1a; Route::get(member/personal_log,permission.Member/personalLog);// 获取个人信息操作接口 二、后端api添加方法 在根目录下app文件夹下controller文…

Spring基础分析01-Spring的核心特性与优势

大家好&#xff0c;今天和大家一起学习一下Spring的核心特性与Spring的优势~ Spring是一个轻量级的Java开发框架&#xff0c;它通过简化企业级应用程序的复杂性而闻名。其核心特性包括但不限于IoC/DI、AOP、数据访问抽象、事务管理以及Web应用支持等。这些特性共同作用&#x…