行为树详解(5)——事件驱动

【分析】

如果行为树的节点很多,那么会存在要经过很多节点才会走到动作节点的情况。显然,性能上不如状态机。

每帧都需要重新遍历一系列节点才会走到动作节点,而实际上很多条件节点在数帧内不会有变化,这是造成性能问题的重要原因。

行为树每帧的Update其实就是在轮询这些条件,为避免轮询,我们自然而然的希望在一帧直接执行动作节点。

为此,需要将在Running状态的动作节点缓存,直接执行直到成功。

这种方式引起的问题是动作节点不一定总是会执行成功,可能在某一帧,某些条件改变了,动作需要被打断。

为了解决该问题,就需要给动作节点引入一些事件,当特定事件发生时,在动作节点内接收该事件,直接结束动作节点的运行,返回失败。

随后重新开始整个行为树的轮询,找到下一个动作节点。

这就是事件和轮询混合驱动的行为树。

事件本质上就是直接调用,当经过动作节点的路径较长,也即影响该动作节点的条件过多时,不可避免要通过代码调用发多种消息,这就失去了配置的意义。

需要将打断动作节点及节点的逻辑做成单独的Conditional节点,这些节点接收消息或监听变化,以决定是否打断其子节点。

通过轮询这些Conditional节点以简化发多种消息的调用。

当打断发生时,需要重新运行被打断节点的父节点确定新的分支走向。为此,需要将路径上所有节点做缓存。

这里要对控制节点做特殊处理,控制节点会控制自身的子节点运行,其状态受子节点影响,当打断存在时,子节点可能不需要再次运行,因此控制节点在控制子节点运行前需要知道当前是否被打断。

【代码实现】

    public class BehaviorTree
    {
        public string btName;
        public int btId;
        public UpdateType updateType;
        public DrivenType drivenType;
        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>();
        private Dictionary<Type, Dictionary<string, Delegate>> eventDic = new Dictionary<Type, Dictionary<string, Delegate>>();

        private Node curActionNode;

        private Stack<Node> activeNodes = new Stack<Node>();
        private List<ConditionalNode> conditionalNodes = new List<ConditionalNode>();  

        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;
                drivenType = data.drivenType;
                //通常初始化有两种写法,一种是在管理者中完成管理者自身以及被管理者的数据初始化;另一种是管理者完成自身的初始化后将被数据传递给被管理者,被管理者自身完成初始化
                //第二种方式更加灵活可变,且不需要关注行为树结构是怎么样的,只需每个节点做好自身的初始化
                //在第二种方式下涉及如何将数据递归传递的问题,为统一获取,将数据记录在管理者上,被管理者根据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(drivenType == DrivenType.Polling)
            {
                PollingUpdate(time);
            }
            else if(drivenType == DrivenType.PollingAndEvent)
            {
                if (curActionNode != null)
                {
                    var actionStatus = curActionNode.Update();
                    if(actionStatus != NodeStatus.Running)
                    {
                        curActionNode = null; 
                        PollingUpdate(time);
                    }
                    else
                    {
                        if (updateType == UpdateType.FixedTime)
                        {
                            curTime += time;
                        }
                    }
                }
                else
                {
                    PollingUpdate(time);
                }
            }
            else if(drivenType == DrivenType.EventDriven)
            {
                EventUpdate(time);
            }

        }

        private void PollingUpdate(float time)
        {
            if (updateType == UpdateType.EveryFrame)
            {
                Update();
            }
            else if (updateType == UpdateType.FixedTime)
            {
                curTime += time;
                if (curTime > updateTime)
                {
                    curTime = 0;
                    Update();
                }
            }
        }

        private void EventUpdate(float time)
        {
            int index = -1;
            for (int i = 0; i < conditionalNodes.Count; i++)
            {
                if (conditionalNodes[i].ConditionChanged()) //当条件节点条件改变,其后所有节点都需要重新计算,动作节点需要被打断
                {
                    index = i;
                    break;
                }
            }
   
            if (index != -1)
            {              
                while(activeNodes.Count> 0 && activeNodes.Peek().nodeId != conditionalNodes[index].nodeId)//移除其后所有Active节点
                {
                    PopNode(activeNodes.Peek());
                }

                conditionalNodes[index].Update();//重新运行该节点
                if (conditionalNodes[index].StatusChanged(out var curStatus))
                {                    
                    var curNode = (Node)conditionalNodes[index];
                    var parent = conditionalNodes[index].parent;
                    while (parent is ControlNode)//如果父节点是控制节点,其状态会与子节点相关,这里简化处理,直接重新运行
                    {
                        curNode = parent;
                        parent = parent.parent;
                    }
                    if(curNode != conditionalNodes[index])
                    {
                        while (activeNodes.Count > 0 && activeNodes.Peek().nodeId != curNode.nodeId)
                        {
                            PopNode(activeNodes.Peek());
                        }
                        curNode.Update();
                    }
                }

            }
            else
            {
                var state = activeNodes.Peek().Update();//没有打断,再次运行末尾的动作节点
                if(state != NodeStatus.Running)//运行结束,重新从根节点开始运行,也可以选择回退到上一个节点开始运行
                {
                    activeNodes.Clear();
                    conditionalNodes.Clear();
                    rootNode.Update();
                }
            }

        }

        public void PushNode(Node node)
        {
            if (drivenType != DrivenType.EventDriven)
                return;

            activeNodes.Push(node);
            if(node is ConditionalNode conditionalNode)
            {
                conditionalNodes.Add(conditionalNode);
            }
        }

        private void PopNode(Node node)
        {
            if (drivenType != DrivenType.EventDriven)
                return;

            node.End();
            activeNodes.Pop();
            if(node is ConditionalNode conditionalNode)
            {
                conditionalNodes.Remove(conditionalNode);
            }
        }

        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 void SetActionNode(Node node)
        {
            curActionNode = node;
        }

        private void RegisterEvent(string eventName, Delegate handler)
        {
            if (drivenType == DrivenType.Polling)
                return;

            if (!eventDic.TryGetValue(handler.GetType(), out var dic))
            {
                dic = new Dictionary<string, Delegate>();
                eventDic[handler.GetType()] = dic;
            }

            if (!dic.TryGetValue(eventName, out var value))
            {
                dic[eventName] = handler;
            }
            else
            {
                dic[eventName] = Delegate.Combine(value, handler);
            }
        }

        public void RegisterEvent(string eventNmae,Action action)
        {
            RegisterEvent(eventNmae, (Delegate)action);
        }

        public void RegisterEvent<T>(string eventNmae, Action<T> action)
        {
            RegisterEvent(eventNmae, (Delegate)action);
        }

        public void RegisterEvent<T1,T2>(string eventNmae, Action<T1,T2> action)
        {
            RegisterEvent(eventNmae, (Delegate)action);
        }

        public void RegisterEvent<T1, T2,T3>(string eventNmae, Action<T1, T2,T3> action)
        {
            RegisterEvent(eventNmae, (Delegate)action);
        }

        private Delegate GetDelegate(string eventName,Type type)
        {
            if (drivenType == DrivenType.Polling)
                return null;

            if (eventDic.TryGetValue(type, out var dic))
            {
                if (dic.TryGetValue(eventName, out var handler))
                {
                    return handler;
                }
            }
            return null;
        }
        public void SendEvent(string eventName)
        {            
            if(GetDelegate(eventName, typeof(Action)) is Action action)
            {
                action();
            }
        }

        public void SendEvent<T>(string eventName, T t)
        {
            if (GetDelegate(eventName, typeof(Action<T>)) is Action<T> action)
            {
                action(t);
            }
        }

        public void SendEvent<T1,T2>(string eventName, T1 t1,T2 t2)
        {
            if (GetDelegate(eventName, typeof(Action<T1,T2>)) is Action<T1,T2> action)
            {
                action(t1,t2);
            }
        }

        public void SendEvent<T1, T2,T3>(string eventName, T1 t1, T2 t2,T3 t3)
        {
            if (GetDelegate(eventName, typeof(Action<T1, T2,T3>)) is Action<T1, T2,T3> action)
            {
                action(t1, t2,t3);
            }
        }
    }

   public enum DrivenType
   {
       Polling,
       PollingAndEvent,
       EventDriven
   }
    public class Node
    {
        public string nodeName;
        public int nodeId;
        public NodeStatus status;
        public BehaviorTree owner;
        public Node parent;

        public void Init(NodeInfo nodeInfo, BehaviorTree owner)
        {
            this.owner = owner;
            this.nodeName = nodeInfo.nodeName;
            this.nodeId = nodeInfo.nodeId;
            OnInit(nodeInfo);
            owner.AddNode(this);
            //对于字段的配置可以通过字段名反射获取字段然后设置值,但这里允许的值是有限的,我们直接在每个节点写对应的处理
        }


        public NodeStatus Update()
        {
            owner.PushNode(this);
            return OnUpdate();
        }

        public void End()//方法名字叫Exit也是一样的
        {
            OnEnd();
        }

        protected virtual void OnInit(NodeInfo nodeInfo)
        {

        }

        protected virtual NodeStatus OnUpdate()
        {
            return NodeStatus.Success;
        }

        protected virtual void OnEnd()
        {

        }

        public virtual void Destroy()
        {

        }
    }

    public class ConditionalNode: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);
                subNode.parent = this;
            }
        }

        protected override NodeStatus OnUpdate()
        {
            return subNode.Update();
        }

        public virtual bool ConditionChanged()
        {
            return false;
        }

        public virtual bool StatusChanged(out NodeStatus status)
        {
            status = NodeStatus.Success;
            return false;
        }

    public class ActionNode:Node
    {
        private bool start;
        private bool stop;
        protected override NodeStatus OnUpdate()
        {
            if (!start)
            {
                Start();
                start = true;
            }
            var res = base.OnUpdate();
            if(stop)
            {
                res = NodeStatus.Failure;
            }
            if (res == NodeStatus.Running)
            {
                owner.SetActionNode(this);
            }
            return res;
        }

        private void Start()
        {
            OnStart();
        }

        protected virtual void OnStart()
        {
            //各动作节点自定义注册事件
        }

        protected override void OnEnd()
        {
            start = false;
            stop = false;
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/944476.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

虚幻引擎是什么?

Unreal Engine&#xff0c;是一款由Epic Games开发的游戏引擎。该引擎主要是为了开发第一人称射击游戏而设计&#xff0c;但现在已经被成功地应用于开发模拟游戏、恐怖游戏、角色扮演游戏等多种不同类型的游戏。虚幻引擎除了被用于开发游戏&#xff0c;现在也用于电影的虚拟制片…

多个微服务 Mybatis 过程中出现了Invalid bound statement (not found)的特殊问题

针对多个微服务的场景&#xff0c;记录一下这个特殊问题&#xff1a; 如果启动类上用了这个MapperScan注解 在resource 目录下必须建相同的 com.demo.biz.mapper 目录结构&#xff0c;否则会加载不到XML资源文件 。 并且切记是com/demo/biz 这样的格式创建&#xff0c;不要使用…

使用Excel制作通达信自定义外部数据,安排!!!

Excel相信大家电脑上都有这个工具&#xff0c;相比敲编程代码&#xff0c;用这个去做自定义数据对大多数人&#xff0c;应该是比较友好的。自定义数据分为外部序列数据&#xff0c;看了一下内容理解起来比较多&#xff0c;分两期给大家介绍。为了照顾电脑基础薄弱的朋友&#x…

win10、win11-鼠标右键还原、暂停更新

系统优化 win 10jihuo win 11jihuo鼠标右键还原暂停更新 update 2024.12.28win 10 jihuo winx&#xff0c;打开powershell管理员&#xff0c;输入以下命令,选择1并等待 irm https://get.activated.win | iex参考&#xff1a;https://www.bilibili.com/video/BV1TN411M72J/?sp…

QT集成IntelRealSense双目摄像头2,集成OpenGL

上一篇文章写了如何把IntelRealSense摄像头的SDK集成到QT项目&#xff0c;并成功采集数据&#xff0c;在没有用OpenCV的情况下完成色彩数据&#xff0c;以及深度数据的显示。 具体地址&#xff1a;https://blog.csdn.net/qujia121qu/article/details/144734163 本次主要写如何…

数据分析的分类和EDIT思维框架

为了服务于企业不同层次的决策&#xff0c;商业数据分析过程需要提供相应的数据科学产出物。 一般而言&#xff0c;数据分析需要经历从需求层、数据层、分析层到输出层四个阶段。 第一个阶段是需求层——确定目标&#xff0c;具体目标需要依据具体的层次进行分析&#xff1a…

面试场景题系列:设计URL短链

1.场景需求界定 1.缩短URL&#xff1a;提供一个长URL&#xff0c;返回一个短很多的URL。 2.重定向URL&#xff1a;提供一个缩短了的URL&#xff0c;重定向到原URL。 3.高可用、可扩展性和容错性考量。 •写操作&#xff1a;每天生成1亿个URL。 •每秒的写操作数&#xff1a…

Linux 基本指令

目录 1.常见指令 1.1 ls指令 1.2 pwd指令 1.3 cd指令 1.4 touch指令 1.5 mkdir指令 1.6 rm和rmdir指令 1.7 man指令 1.8 cp指令 1.9 mv指令 ​编辑 1.10 cat指令 1.11 more指令 1.12 less指令 1.13 head指令 1.14.tail指令 1.15 时间相关的指令 1.16 cal…

WEB UI 创建视图

1 视图名称 (点第1创建视图) 2 模型节点 可以空 3 上下文节点 4 新增节点下的属性 &#xff0c;参考结构(先建好的结构) 5 选择视图类型&#xff1a;&#xff08;表单&#xff0c; 列表&#xff09; 表单 &#xff1a;单条数据 列表 &#xff1a;多条数据&#xff08;表格…

redis cluster实验详解

华子目录 实验环境准备部署redis cluster添加节点删除节点redis cluster集群维护 实验 环境准备 再开3台主机 先把之前3台源码编译的redis删除 [rootredis-node1 ~]# cd /usr/local/redis/ [rootredis-node1 redis]# make uninstall[rootredis-node2 ~]# cd /usr/local/redi…

【详细讲解】hive优化

1、开启本地模式 大多数的Hadoop Job是需要Hadoop提供的完整的可扩展性来处理大数据集的。不过&#xff0c;有时Hive的输入数据量是非常小的。在这种情况下&#xff0c;为查询触发执行任务消耗的时间可能会比实际job的执行时间要多的多。对于大多数这种情况&#xff0c;Hive可…

Unity3d UGUI如何优雅的实现Web框架(Vue/Rect)类似数据绑定功能(含源码)

前言 Unity3d的UGUI系统与Web前端开发中常见的数据绑定和属性绑定机制有所不同。UGUI是一个相对简单和基础的UI系统&#xff0c;并不内置像Web前端&#xff08;例如 Vue.js或React中&#xff09;那样的双向数据绑定或自动更新UI的机制。UGUI是一种比较传统的 UI 系统&#xff…

828华为云征文|使用sysbench对Flexus X实例对mysql进行性能测评

目录 一、Flexus X实例概述 1.1?Flexus X实例 1.2?在mysql方面的优势 二、在服务器上安装MySQL 2.1 在宝塔上安装docker 2.2 使用宝塔安装mysql 2.3 准备测试数据库和数据库表 三、安装sysbench并进行性能测试 3.1 使用yum命令sysbench 3.2?运行?sysbench 并进行…

影刀进阶指令 | Kimi (对标ChatGPT)

文章目录 影刀进阶指令 | Kimi &#xff08;对标ChatGPT&#xff09;一. 需求二. 流程三. 实现3.1 流程概览3.2 流程步骤讲解1\. 确定问题2\. 填写问题并发送3\. 检测答案是否出完 四. 运维 影刀进阶指令 | Kimi &#xff08;对标ChatGPT&#xff09; 简单讲讲RPA调用kimi实现…

【教程】通过Docker运行AnythingLLM

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 官方教程&#xff1a;Local Docker Installation ~ AnythingLLM 1、先创建一个目录用于保存anythingllm的持久化文件&#xff1a; sudo mkdir /app su…

游戏引擎学习第65天

回顾我们在模拟区域更改方面的进展 目前我们正在进行游戏的架构调整&#xff0c;目标是建立一个引擎架构。我们正在实施的一个关键变化是引入模拟区域的概念&#xff0c;这样我们可以创建非常大的游戏世界&#xff0c;而这些世界的跨度不必受限于单个浮点变量。 通过这种方式…

【从零开始入门unity游戏开发之——C#篇35】C#自定义类实现Sort自定义排序

文章目录 一、List<T>自带的排序方法1、List<T>调用Sort()排序2、 能够使用 Sort() 方法进行排序的本质 二、自定义类的排序1、通过实现泛型IComparable<T> 接口&#xff08;1&#xff09;示例&#xff08;2&#xff09;直接调用 int 类型的 CompareTo 方法进…

YOLO系列正传(五)YOLOv4论文精解(上):从CSPNet、SPP、PANet到CSPDarknet-53

系列文章 YOLO系列基础 YOLO系列基础合集——小白也看得懂的论文精解-CSDN博客 YOLO系列正传 YOLO系列正传&#xff08;一&#xff09;类别损失与MSE损失函数、交叉熵损失函数-CSDN博客 YOLO系列正传&#xff08;二&#xff09;YOLOv3论文精解(上)——从FPN到darknet-53-C…

Redis 实战篇 ——《黑马点评》(上)

《引言》 在进行了前面关于 Redis 基础篇及其客户端的学习之后&#xff0c;开始着手进行实战篇的学习。因内容很多&#xff0c;所以将会分为【 上 中 下 】三篇记录学习的内容与在学习的过程中解决问题的方法。Redis 实战篇的内容我写的很详细&#xff0c;为了能写的更好也付出…

DevOps实战:用Kubernetes和Argo打造自动化CI/CD流程(2)

DevOps实战&#xff1a;用Kubernetes和Argo打造自动化CI/CD流程&#xff08;2&#xff09; 背景 Tips 翻遍国内外的文档&#xff0c;关于 Argo 作为 CI/CD 当前所有开源的文档&#xff0c;博客&#xff0c;argo官方文档。得出的结论是&#xff1a; argo官方给出的例子都相对…