[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node

这篇具有很好参考价值的文章主要介绍了[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:        

        这个框架最近自己终于补充完成了,使用文档和源码已经放在了Github上,可以在之前的文章中找到:

[Unity] 使用GraphView实现一个可视化节点的事件行为树系统(序章/Github下载)_Sugarzo的博客-CSDN博客_unity graphview

        [Unity] GraphView 可视化节点的事件行为树(一) Runtime Node,Unity工具,Unity框架开发,Unity,c#,开发语言,unity,游戏引擎

正文:

        本文将开始介绍Runtime部分的事件节点逻辑。在本框架中,因为Grapview的节点图属于Editor部分,在游戏运行时是不会被加载进来的。因此首先我们需要一个离开节点图,也可以在游戏实时运行中执行逻辑的节点结构。文章涉及的事件触发思想其实已经在我之前写过的一篇文章中了,也可以算作之前思想的延续。如果没看过的可以浅看一下:

[Unity] 状态机事件流程框架 (一)(C#事件系统,Trigger与Action)_Sugarzo的博客-CSDN博客_c# 事件系统

       在半年前自己的文章中,自己曾介绍了一种事件触发的框架:将每个节点作为一个游戏物品(GameObject GO),使用父子物品的结构作为执行顺序来触发事件。

[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node,Unity工具,Unity框架开发,Unity,c#,开发语言,unity,游戏引擎

         这种方式因为直接使用了父子游戏物品作为组织方式,当大规模使用起来还是有点约束,因为触发器节点和事件节点形成的是一对一的关系,子物品也只能按照顺序执行事件下去,当需要处理一些分支/循环逻辑的时候就不太方便了,而且游戏物品太多也会对性能优化产生一定的影响。但是里面的设计思想我们还是可以保留的。

        从实际图中,可以看到在新版本里,节点可以执行进行分支和多输入逻辑了。

[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node,Unity工具,Unity框架开发,Unity,c#,开发语言,unity,游戏引擎

[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node,Unity工具,Unity框架开发,Unity,c#,开发语言,unity,游戏引擎

        在本框架中,我们依然延续之前的思想,所有的节点都是继承自MonoBehaviour,节点作为一个Component附加在游戏物品上。这里选用Component当然这自然不是理论上的最高效率。这里设计有几个权衡,一是该节点的数据成员都是由Unity自带的Inspector绘制,在Unity给我们提供的API中,Editor.CreateEditor只支持UnityEngine.Object的派生类。(当然使用ScriptableObject也是可以的,但是就对比Mono少了生命周期函数了)


[ExcludeFromDocs]
public static Editor CreateEditor(UnityEngine.Object targetObject)
{
    Type editorType = null;
    return CreateEditor(targetObject, editorType);
}

        首先是事件节点状态的基类部分

 [Unity] GraphView 可视化节点的事件行为树(一) Runtime Node,Unity工具,Unity框架开发,Unity,c#,开发语言,unity,游戏引擎

        在状态基类里,需要表示的结构有:

        1.当前节点的状态机状态

        2.一些状态行为的虚函数

        3.该节点指向的下一节点(流,flow)

        4.这个节点在节点图中的位置(Vector2,仅Editor数据,将在后面章节运用)

        在Runtime中,只需有123点就可以运行节点逻辑了。

using UnityEngine;
using Sirenix.OdinInspector;
using System.Collections.Generic;

namespace SugarFrame.Node
{
    public enum EState
    {
        [LabelText("未执行")]
        None,
        [LabelText("正在进入")]
        Enter,
        [LabelText("正在执行")]
        Running,
        [LabelText("正在退出")]
        Exit,
        [LabelText("执行完成")]
        Finish,
    }
    public interface IStateEvent
    {
        void Execute();
        void OnEnter();
        void OnRunning();
        void OnExit();
    }

    public abstract class NodeState : MonoBehaviour
    {
#if UNITY_EDITOR
        [HideInInspector]
        public Vector2 nodePos;
#endif
        //流向下一节点的流
        public MonoState nextFlow;
    }

    public abstract class MonoState : NodeState, IStateEvent
    {
        [SerializeField,Space]
        protected EState state;

        [TextArea,Space]
        public string note;

        protected void TransitionState(EState _state)
        {
            state = _state;

            switch (state)
            {
                case EState.Enter:
                    OnEnter();
                    break;
                case EState.Running:
                    OnRunning();
                    break;
                case EState.Exit:
                    OnExit();
                    break;
            }
        }

        public virtual void Execute()
        {
            TransitionState(EState.Enter);
        }
        public virtual void OnEnter()
        {
            TransitionState(EState.Running);
        }
        public virtual void OnRunning()
        {
            TransitionState(EState.Exit);
        }
        public virtual void OnExit()
        {
            TransitionState(EState.Finish);
        }
    }
}


        接着设计触发器节点和事件节点基类逻辑,在之前的文章中,有写到过这两个节点的设计思想。这里其实设计起来也差不多:

[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node,Unity工具,Unity框架开发,Unity,c#,开发语言,unity,游戏引擎

using System.Collections;
using UnityEngine;
using Sirenix.OdinInspector;

namespace SugarFrame.Node
{
    public enum ExecutePeriod
    {
        None,
        Awake,
        Enable,
        Start,
        Update,
        DisEnable,
        Destroy,
    }

    public interface ITriggerEvent
    {
        void RegisterSaveTypeEvent();
        void DeleteSaveTypeEvent();
    }

    public abstract class BaseTrigger : MonoState,ITriggerEvent
    {
        
        [LabelText("生命周期执行")]
        public ExecutePeriod executePeriod = ExecutePeriod.None;
        [Header("允许状态未结束时依然可以执行")]
        public bool canExecuteOnRunning = false;
        [Header("只执行一次")]
        public bool runOnlyOnce = false;

        //(可选)在子类中实现下面两个方法
        public virtual void RegisterSaveTypeEvent()
        {
            //EventManager.StartListening("");
        }
        public virtual void DeleteSaveTypeEvent()
        {
            //EventManager.StopListening("");
        }

        [Button]
        public override void Execute()
        {
            if(!canExecuteOnRunning)
                if (state == EState.Enter || state == EState.Running || state == EState.Exit)
                    return;

            base.Execute();

            if (runOnlyOnce)
                Destroy(this);
        }

        public override void OnEnter()
        {
            base.OnEnter();

            if (nextFlow != null)
            {
                if (nextFlow is BaseAction nextAction)
                    nextAction.Execute(this);
                else
                    nextFlow.Execute();
            }
                
        }

        public override void OnRunning()
        {
            //Trigger不需要实现OnRunning,由Action回调OnExit退出
            //base.OnRunning();
        }

        public override void OnExit()
        {
            base.OnExit();

        }

        protected virtual void Awake()
        {
            if (executePeriod == ExecutePeriod.Awake)
                Execute();
        }

        Coroutine updateCoroutine = null;

        protected virtual void OnEnable()
        {
            if (executePeriod == ExecutePeriod.Enable)
                Execute();

            RegisterSaveTypeEvent();

            //使用协程模拟update,优化不选择ExecutePeriod.Update时的性能
            if (executePeriod == ExecutePeriod.Update)
                updateCoroutine = StartCoroutine(IEUpdate());
        }

        protected virtual void Start()
        {
            if (executePeriod == ExecutePeriod.Start)
                Execute();
        }

        protected virtual IEnumerator IEUpdate()
        {
            while(true)
            {
                yield return null;
                if (gameObject.activeSelf)
                    Execute();
                else
                    yield break;
            }
        }

        protected virtual void OnDisable()
        {
            if (executePeriod == ExecutePeriod.DisEnable)
                Execute();

            DeleteSaveTypeEvent();

            if (updateCoroutine != null)
            {
                StopCoroutine(updateCoroutine);
                updateCoroutine = null;
            }
                
        }

        protected virtual void OnDestroy()
        {
            if (executePeriod == ExecutePeriod.Destroy)
                Execute();
        }
    }
}


[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node,Unity工具,Unity框架开发,Unity,c#,开发语言,unity,游戏引擎

using Sirenix.OdinInspector;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SugarFrame.Node
{
    public abstract class BaseAction : MonoState
    {
        [Header("进入时等待一帧")]
        public bool wait1Frame = false;
        
        //在派生类中填写逻辑,并回调Runover()
        public abstract void RunningLogic(BaseTrigger emitTrigger);

        [Button]
        public override void Execute()
        {
            Execute(null);
        }

        public void Execute(BaseTrigger emitTrigger)
        {
            TransitionState(EState.Running);

            if (wait1Frame && gameObject.activeInHierarchy)
            {
                StartCoroutine(DelayFrame(RunningLogic, emitTrigger));
            }
            else
            {
                RunningLogic(emitTrigger);
            }  
        }

        public virtual void RunOver(BaseTrigger emitTrigger)
        {
            OnExitEvent?.Invoke();
            OnExitEvent = null;

            if (nextFlow)
            {
                //继续执行下一个节点
                if (nextFlow is BaseAction nextAction)
                    nextAction.Execute(emitTrigger);
                else
                    nextFlow.Execute();
            }
            else
            {
                //最后一个节点了,切换Trigger状态
                emitTrigger?.OnExit();
            }
            TransitionState(EState.Exit);
        }

        public override void OnRunning()
        {
            //不执行任何操作,由RunOver触发OnExit
        }

        [HideInInspector]
        public event Action OnExitEvent;

        IEnumerator DelayFrame(Action<BaseTrigger> action,BaseTrigger emitTrigger)
        {
            yield return null;
            action?.Invoke(emitTrigger);
        }
    }
}


         在这里,我们在基类中暴露两个API给Trigger和Action的派生类编辑。BaseAction节点是RunningLogic函数,在新建事件节点时只需要重写这个就可定义节点逻辑,RunOver决定函数何时结束。BaseTrigger是RegisterSaveTypeEvent和DeleteSaveTypeEvent,只需要拓展时自己决定什么时候调用Execute执行即可,当然也可以自己override GameObject的生命周期去实现触发器逻辑。

        以下是拓展这两个节点的脚本模板。

public class #TTT# : BaseAction
    {
        [Header("#TTT#")]
        public string content;

        public override void RunningLogic(BaseTrigger emitTrigger)
        {
            //Write Logic

            RunOver(emitTrigger);
        }
    }

public class #TTT# : BaseTrigger
    {
        //Called on Enable
        public override void RegisterSaveTypeEvent()
        {
            //EventManager.StartListening("",Execute);
        }

        //Called on DisEnable
        public override void DeleteSaveTypeEvent()
        {
            //EventManager.StopListening("",Execute);
        }
    }

        在没有设计出节点图框架前,我们可以手动在Inspector窗口连接触发器节点和事件节点的nextFlow属性,就可以看到Runtime部分的节点逻辑正常运行了。这里拿ButtonTrigger和DebugAction节点做示范:

        (调整nextFlow,按下Button,执行逻辑)

[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node,Unity工具,Unity框架开发,Unity,c#,开发语言,unity,游戏引擎

         其中两个节点写法:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace SugarFrame.Node
{
    public class ButtonTrigger : BaseTrigger
    {
        public List<Button> buttons;

        //Called on Enable
        public override void RegisterSaveTypeEvent()
        {
            foreach (var btn in buttons)
                btn?.onClick.AddListener(Execute);
        }

        //Called on DisEnable
        public override void DeleteSaveTypeEvent()
        {
            foreach (var btn in buttons)
                btn?.onClick.RemoveListener(Execute);
        }
    }
}
using UnityEngine;

namespace SugarFrame.Node
{
    public class DebugAction : BaseAction
    {
        [Header("Debug Action")]
        public string content;

        public override void RunningLogic(BaseTrigger emitTrigger)
        {
            Debug.Log(content);

            RunOver(emitTrigger);
        }
    }

}

        接着,我们把分支节点和序列节点这两个节点也实现了。在分支节点逻辑中,我们给派生类流一个判断虚函数的接口,根据判断结构设置nextFlow的流向即可。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SugarFrame.Node
{
    public abstract class BaseBranch : BaseAction
    {
        //流向下一节点的流
        [HideInInspector]
        public MonoState trueFlow;
        [HideInInspector]
        public MonoState falseFlow;

        //在派生类中实现该逻辑
        public abstract bool IfResult();

        public override void RunningLogic(BaseTrigger emitTrigger)
        {
            RunOver(emitTrigger);
        }

        public override void RunOver(BaseTrigger emitTrigger)
        {
            //判断下一节点的流向
            nextFlow = IfResult() ? trueFlow : falseFlow;

            if (nextFlow)
            {
                //继续执行下一个节点
                if (nextFlow is BaseAction nextAction)
                    nextAction.Execute(emitTrigger);
                else
                    nextFlow.Execute();
            }
            else
            {
                //最后一个节点了,切换Trigger状态
                emitTrigger?.OnExit();
            }
            TransitionState(EState.Finish);
        }
    }
}


        序列节点:可以负责多个流向的节点。这里就需要List<MonoState>了。因为当逻辑结束时,我们需要回调Trigger切换条件。而Sequence可能由多个触发器节点同时调用,每个流向的逻辑执行完时间就可能存在不同。这里的处理就复杂一点。借用了委托和缓存的思想,每次触发这个节点时都使用lambda注册一个数据结构用来标记这个sequence后续所有流向有没有被完成,只有当所有的流向都执行完成时才回调Trigger。

public abstract class BaseSequence : BaseAction
    {

        [HideInInspector]
        public List<MonoState> nextflows = new List<MonoState>();
        [Header("每个行为之间是否等待x秒,输入-1时等待1帧")]
        public float waitTimeEachAction = 0;
        [ReadOnly]
        public int runningAction = 0;

        public override void OnEnter()
        {           
            base.OnEnter();
        }


        /// <summary>
        /// 向下执行所有节点
        /// </summary>
        public override void RunningLogic(BaseTrigger emitTrigger)
        {
            if (nextflows != null && nextflows.Count > 0)
            {
                runningAction = nextflows.Count;
                StartCoroutine(StartActions(emitTrigger));
            }
            else
            {
                //Sequence节点输出为空,直接切换到结束状态
                RunOver(emitTrigger);
            }
        }

        private IEnumerator StartActions(BaseTrigger emitTrigger)
        {
            DataCache cache = new DataCache();
            cache.count = nextflows.Count;
            cache.trigger = emitTrigger;

            //继续所有节点
            foreach (var nextFlow in nextflows)
            {
                if (nextFlow is BaseAction nextAction)
                    nextAction.Execute();
                else
                    nextFlow.Execute();

                //依赖注入,当所有Action执行完成时回调Trigger
                if (nextFlow is BaseAction action)
                    action.OnExitEvent += delegate ()
                    {
                        cache.count--;
                        if(cache.count == 0)
                        {
                            cache.trigger?.OnExit();
                        }
                    };

                nextFlow.Execute();

                if (waitTimeEachAction > 0)
                    yield return new WaitForSeconds(waitTimeEachAction);
                if (waitTimeEachAction == -1)
                    yield return null;
            }
            yield return null;
        }


        private class DataCache
        {
            public BaseTrigger trigger;
            public int count;
        }
    }

        好了,在这里就已经讲完了Runtime部分的Node框架了。下一章节就会进入Editor部分的UI  Toolkit篇。文章来源地址https://www.toymoban.com/news/detail-731891.html

到了这里,关于[Unity] GraphView 可视化节点的事件行为树(一) Runtime Node的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity可视化Shader工具ASE介绍——6、通过例子说明ASE节点的连接方式

    阿赵的Unity可视化Shader工具ASE介绍目录   大家好,我是阿赵。继续介绍Unity可视化Shader编辑插件ASE的用法。上一篇已经介绍了很多ASE常用的节点。这一篇通过几个小例子,来看看这些节点是怎样连接使用的。   这篇的内容可能会比较长,最终是做了一个遮挡X光的效果,不

    2024年02月07日
    浏览(54)
  • 基于Python的淘宝行为数据可视化分析

    完成如下商业分析任务,通过数据分析和可视化展示,充分挖掘数据的价值,让数据更好地为业务服务: 流量分析 :PV/UV是多少,通过分析PV/UV能发现什么规律? 漏斗分析 :用户“浏览-收藏-加购-购买”的转化率是怎样的? 用户价值分析 :对电商平台什么样的用户是有价值

    2024年02月10日
    浏览(51)
  • 天池赛:淘宝用户购物行为数据可视化分析

    目录 前言 一、赛题介绍 二、数据清洗、特征构建、特征可视化 1.数据缺失值及重复值处理 2.日期分离,PV及UV构建 3.PV及UV可视化 4.用户行为可视化 4.1 各个行为的面积图(以UV为例) 4.2 各个行为的热力图 5.转化率可视化 三、RFM模型 1.构建R、F、M 2.RFM的数据统计分布 3.计算

    2024年01月22日
    浏览(46)
  • 基于Python电商用户行为的数据分析、机器学习、可视化研究

    有需要本项目的源码以及全套文档和相关资源,可以私信博主!!! 在数字化和互联网技术飞速发展的推动下,消费者的购买能力和消费观念呈现不断升级和变迁的趋势。用户消费数据的爆炸式增长,为我们提供了寻找潜在价值信息的机会。 本研究使用了阿里巴巴提供的淘

    2024年02月04日
    浏览(49)
  • 毕业设计 大数据电商用户行为分析及可视化(源码+论文)

    今天学长向大家介绍一个机器视觉的毕设项目,大数据电商用户行为分析及可视化(源码+论文) 项目运行效果: 毕业设计 基于大数据淘宝用户行为分析 项目获取: https://gitee.com/assistant-a/project-sharing 这是一份来自淘宝的用户行为数据,时间区间为 2017-11-25 到 2017-12-03,总计

    2024年02月22日
    浏览(50)
  • 【TIANCHI】天池大数据竞赛(学习赛)--- 淘宝用户购物行为数据可视化分析

    目录 前言 一、数据集的来源和各个字段的意义 二、数据分析 1.引入库 2.读入数据 3.查看数据数量级 4.PV(Page View)/UV访问量 5.漏斗模型 6.用户购买商品的频次分析。 7.ARPPU(average revenue per paying user)  计算 ARPPU  ARPPU出图 8.复购情况分析 计算用户购买频次 复购周期分析 总结

    2024年02月09日
    浏览(41)
  • python毕设选题 - flink大数据淘宝用户行为数据实时分析与可视化

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月01日
    浏览(72)
  • PyG基于DeepWalk实现节点分类及其可视化

    大家好,我是阿光。 本专栏整理了《图神经网络代码实战》,内包含了不同图神经网络的相关代码实现(PyG以及自实现),理论与实践相结合,如GCN、GAT、GraphSAGE等经典图网络,每一个代码实例都附带有完整的代码。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10 语言环

    2024年01月19日
    浏览(61)
  • 大数据毕设分享 flink大数据淘宝用户行为数据实时分析与可视化

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年01月21日
    浏览(61)
  • 【毕业设计】基于大数据的京东消费行为分析与可视化 - python 机器学习

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月04日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包