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

news2024/12/15 20:05:20

【分析】

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

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

静态参数直接设置为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 BTConfigData
    public 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.coloradmin.cn/o/2260110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KMP 字符串匹配详解

一、KMP 的作用 KMP 用于解决字符串匹配问题&#xff0c;当出现字符串不匹配时&#xff0c;可以知道一部分之前已经匹配的文本内容&#xff0c;可以利用这些信息避免从头再去做匹配了。 二、题目 链接&#xff1a;28. 找出字符串中第一个匹配项的下标 - 力扣&#xff08;Lee…

ElasticSearch01-概述

零、文章目录 ElasticSearch01-概述 1、Elastic Stack &#xff08;1&#xff09;简介 官网地址&#xff1a;https://www.elastic.co/cn/ELK是一个免费开源的日志分析架构技术栈总称&#xff0c;包含三大基础组件&#xff0c;分别是Elasticsearch、Logstash、Kibana。但实际…

12.2【JAVA EXP4]next.js的各种问题,DEBUG,前端补强,前后端交互,springSecurity ,java 配置,h2数据库

在服务器组件中使用了 useState 这样的 React Hook。useState 只能在客户端组件中使用&#xff0c;而不能在服务器组件中使用。Next.js 的新架构&#xff08;App Router&#xff09;中&#xff0c;默认情况下&#xff0c;页面和布局组件是服务器组件&#xff0c;因此不能直接使…

MySQL相关文件

配置文件 — — — — — — /etc/my.cnf datadir/var/lib/mysql //数据目录 socket/var/lib/mysql/mysql.sock //定义套接字文件存储位置&#xff0c;套接字文件&#xff08;IP&#xff1a;port&#xff09;,用于接收客户端连…

数字产业化和产业数字化到底是什么?

“数字产业化”和“产业数字化”在很多官方文件和领导人讲话中都是成对出现的&#xff0c;这两个术语看起来非常相似&#xff0c;但它们作为数字经济的两个重要组成部分&#xff0c;既有联系又有区别。 在谈数字产业化和产业数字化之前&#xff0c;我这里需要先给大家介绍一个概…

3D一览通在线协同设计,助力汽车钣金件设计与制造数字化升级

汽车行业已迎来智能化的汹涌浪潮&#xff0c;在此背景下&#xff0c;零部件制造商唯有积极应对&#xff0c;以智能制造为核心驱动力&#xff0c;方能跟上行业发展步调&#xff0c;在激烈的市场竞争中抢占先机。作为整车制造不可或缺的核心组件之一&#xff0c;汽车钣金件亦需紧…

基于Sharding-jdbc实现水平分库、垂直分库、读写分离

一、实现水平分库 需求说明 水平分库是把同一个表的数据按一定规则拆到不同的数据库中&#xff0c;每个库可以放在不同的服务器上。 接下来咱们继续对快速入门中的例子进行完善。 实现步骤 将原有order_db库拆分为order_db_1、order_db_2 CREATE DATABASE order_db_1 CHAR…

MATLAB中Simulink的信号线

Simulink以模块为最小单位,通过信号线互相连接&#xff0c;用户可通过GUI调配每个模块的参数,且仿真的结果能够以数值和图像等形象化方式具现出来。信号线可以传递一维数据、多维数据、向量数据或矩阵数据,甚至Bus型数据。Simulink使用不同的线形表示传递不同数据类型的信号线,…

【WRF安装】WRF编译错误总结1:HDF5库包安装

目录 1 HDF5库包安装有误&#xff1a;HDF5 not set in environment. Will configure WRF for use without.HDF5的重新编译 错误原因1&#xff1a;提示 overflow 错误1. 检查系统是否缺少依赖库或工具2. 检查和更新编译器版本3. 检查 ./configure 报错信息4. 检查系统环境变量5.…

Flutter 内嵌 unity3d for android

前言&#xff1a; 最近刚整完 unity3d hybridCLR 更新代码和资源&#xff0c;我们 趁热打铁 将 Unity3D 嵌入 Flutter 应用中。实现在 Flutter 使用 Unity3D, 可以做 小游戏 大游戏&#xff1b; 之前都是 内嵌 Webview 来实现的。虽然 CocosCreator 做出来的效果也不错&#xf…

鸿蒙开发:一个轻盈的上拉下拉刷新组件

前言 老早之前开源了一个刷新组件&#xff0c;提供了很多常见的功能&#xff0c;也封装了List&#xff0c;Grid&#xff0c;WaterFlow&#xff0c;虽然功能多&#xff0c;但也冗余比较多&#xff0c;随着时间的前去&#xff0c;暴露的问题就慢慢增多&#xff0c;虽然我也提供了…

Oracle plsqldev1106 安装及TNS配置

Oracle plsqldev1106 安装及TNS配置 下载好安装包&#xff0c;直接双击安装 点击 I Agree 默认是C盘的&#xff0c;我改了D盘&#xff0c;根据自己实际情况修改 这里用默认的for current user 也可以&#xff0c;我选了for all user 点Finish&#xff0c;等待安装完成即可 …

【卷积神经网络】AlexNet实践

构建模型 模版搭建 # 定义一个AlexNet模型类def __init__(self):# 调用父类的构造函数&#xff08;如果继承自nn.Module的话&#xff09;super(AlexNet, self).__init__()# ReLU激活函数self.ReLU nn.ReLU()# 卷积层1&#xff1a;输入1个通道&#xff08;灰度图&#xff09;&a…

Linux驱动开发(13):输入子系统–按键输入实验

计算机的输入设备繁多&#xff0c;有按键、鼠标、键盘、触摸屏、游戏手柄等等&#xff0c;Linux内核为了能够将所有的输入设备进行统一的管理&#xff0c; 设计了输入子系统。为上层应用提供了统一的抽象层&#xff0c;各个输入设备的驱动程序只需上报产生的输入事件即可。 下…

计算机毕设-基于springboot的某学院兼职平台的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

Unity3D仿星露谷物语开发3之动画系统初探

1、目标 我们希望使用已有的资源建一个动画demo&#xff0c;以此熟悉基于已有Animator/Animation资源的使用方法。 以Tree的动画系统为例&#xff0c;资源位于&#xff1a; 2、创建流程 &#xff08;1&#xff09;创建tree空对象 上面两个都是空对象。 &#xff08;2&#…

怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev

本文引用怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev 在 vscode 设置项中配置 gopls 的 ui.navigation.importShortcut 为 Definition 即可。 "gopls": {"ui.navigation.importShortcut": "Definition" }ui.navigation.i…

Java:183 基于SSM的高校食堂系统

项目介绍 基于SSM的食堂点餐系统 角色:管理员、用户、食堂 前台用户可以实现商品浏览&#xff0c;加入购物车&#xff0c;加入收藏&#xff0c;预定&#xff0c;选座&#xff0c;个人信息管理&#xff0c;收货信息管理&#xff0c;收藏管理&#xff0c;评论功能&#xff0c;…

[COLM 2024] V-STaR: Training Verifiers for Self-Taught Reasoners

本文是对 STaR 的改进方法&#xff0c;COLM 是 Conference On Language Models&#xff0c;大模型领域新出的会议&#xff0c;在国际上很知名&#xff0c;不过目前还没有被列入 ccf list&#xff08;新会议一般不会列入&#xff09;&#xff1b;作者来自高校、微软研究院和 Goo…

端点鉴别、安全电子邮件、TLS

文章目录 端点鉴别鉴别协议ap 1.0——发送者直接发送一个报文表明身份鉴别协议ap 2.0——ap1.0 的基础上&#xff0c;接收者对报文的来源IP地址进行鉴别鉴别协议ap 3.0——使用秘密口令&#xff0c;口令为鉴别者和被鉴别者之间共享的秘密鉴别协议ap 3.1——对秘密口令进行加密&…