Unity3D 基于GraphView实现的节点编辑器框架详解
前言
在Unity3D游戏开发中,节点编辑器是一种强大的工具,它允许开发者以可视化的方式创建和编辑复杂的逻辑和流程。Unity提供了一个强大的UI工具包——GraphView,它使得创建自定义节点编辑器变得相对简单。本文将详细介绍如何使用GraphView实现一个节点编辑器框架,并提供技术详解和代码实现。
对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀!
一、GraphView简介
GraphView是Unity提供的一个用于创建节点编辑器的UI组件。它允许开发者以图形化的方式展示和编辑节点及其连接。GraphView提供了丰富的API,使得开发者可以轻松地自定义节点、边、面板和工具栏等。
二、节点编辑器框架设计
在创建一个节点编辑器框架时,我们需要考虑以下几个关键部分:
- 节点(Node):节点是编辑器中的基本元素,它代表了一个可以执行特定操作的单元。每个节点都应该有一个唯一的标识符、一个标题、一个或多个输入/输出端口,以及用于显示和操作节点的UI元素。
- 边(Edge):边用于连接节点,表示节点之间的数据流或逻辑依赖关系。在GraphView中,边通常由两个端口(一个输入端口和一个输出端口)组成。
- 面板(Panel):面板是节点的容器,它提供了用于添加、删除和移动节点的界面。面板还可以包含工具栏、小地图等辅助工具。
- 工具栏(Toolbar):工具栏提供了用于创建新节点、保存和加载编辑器状态、撤销和重做操作等功能的按钮和菜单。
- 数据存储:为了持久化编辑器状态,我们需要将节点的数据和连接关系存储在一个可序列化的数据结构中。在Unity中,ScriptableObject是一个常用的选择。
三、技术详解
- 创建节点和边:
- 节点可以通过继承GraphView的Node类来创建。在节点类中,我们需要重写BuildContextualMenu方法来添加右键菜单项,如添加输入/输出端口、删除节点等。
- 边可以通过GraphView的Edge类来创建。在创建边时,我们需要指定边的输入和输出端口,并处理边的绘制和连接逻辑。
- 管理节点和边的数据:
- 我们可以使用ScriptableObject来存储节点的数据和连接关系。每个节点可以有一个对应的ScriptableObject来存储其特定的数据。
- 连接关系可以通过存储边的输入和输出端口的标识符来表示。
- 实现撤销和重做功能:
- 撤销和重做功能可以通过维护一个操作历史记录来实现。每次对编辑器进行更改时,都可以将更改作为一个操作添加到历史记录中。
- 撤销操作可以回滚到历史记录中的上一个状态,重做操作可以恢复到下一个状态。
- 实现保存和加载功能:
- 保存功能可以将编辑器的当前状态序列化为一个文件或字符串,并保存到磁盘上。
- 加载功能可以从磁盘上读取文件或字符串,并将其反序列化为编辑器的状态。
四、代码实现
以下是一个简单的节点编辑器框架的代码实现示例:
using UnityEngine; | |
using UnityEditor; | |
using UnityEngine.UIElements; | |
using UnityEditor.UIElements; | |
// 定义一个用于存储节点数据的ScriptableObject | |
[CreateAssetMenu(fileName = "NewNodeGraph", menuName = "NodeGraph/NodeGraph")] | |
public class NodeGraph : ScriptableObject | |
{ | |
// 存储节点和边的数据 | |
public List<NodeBaseData> nodes = new List<NodeBaseData>(); | |
public List<NodeLinkData> edges = new List<NodeLinkData>(); | |
} | |
// 定义一个用于存储节点基础数据的类 | |
[Serializable] | |
public abstract class NodeBaseData | |
{ | |
public string GUID; | |
public string NodeName = "NodeBase"; | |
public Rect Position = Rect.zero; | |
// 其他节点数据 | |
} | |
// 定义一个用于存储边数据的类 | |
[Serializable] | |
public class NodeLinkData | |
{ | |
public string BaseNodeGUID; | |
public string OutputPortName; | |
public string TargetNodeGUID; | |
public string TargetPortName; | |
} | |
// 定义一个节点类,继承自GraphView的Node类 | |
public class MyNode : Node | |
{ | |
// 节点数据 | |
public NodeBaseData nodeData; | |
// 构造函数 | |
public MyNode() | |
{ | |
// 设置节点标题和样式 | |
title = "My Node"; | |
styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.unity.uielements/Editor/Resources/Styles/GraphView.uss")); | |
// 添加输入/输出端口 | |
var inputPort = new Port(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(float)); | |
inputPort.portName = "Input"; | |
inputContainer.Add(inputPort); | |
var outputPort = new Port(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(float)); | |
outputPort.portName = "Output"; | |
outputContainer.Add(outputPort); | |
// 添加右键菜单 | |
this.RegisterCallback<MouseDownEvent>(OnMouseDown); | |
} | |
// 处理右键菜单事件 | |
private void OnMouseDown(MouseDownEvent evt) | |
{ | |
if (evt.button == MouseButton.RightMouse) | |
{ | |
var menu = new GenericMenu(); | |
menu.AddItem(new GUIContent("Delete Node"), false, () => { DeleteNode(); }); | |
menu.ShowAsContext(); | |
evt.StopPropagation(); | |
} | |
} | |
// 删除节点 | |
private void DeleteNode() | |
{ | |
// 从GraphView中移除节点 | |
graphView.RemoveElement(this); | |
// 从NodeGraph中移除节点数据(需要自行实现) | |
} | |
} | |
// 定义一个节点视图类,继承自GraphView | |
public class MyGraphView : GraphView | |
{ | |
// 构造函数 | |
public MyGraphView(EditorWindow window, StyleSheet styleSheet) | |
{ | |
this.styleSheets.Add(styleSheet); | |
this.AddManipulator(new ContextualMenuManipulator(OnContextualMenu)); | |
this.AddManipulator(new SelectionDragManipulator()); | |
this.AddManipulator(new RectangleSelector()); | |
this.AddManipulator(new ZoomManipulator()); | |
this.AddManipulator(new PanManipulator()); | |
// 初始化节点和边(需要自行实现) | |
} | |
// 处理右键菜单事件 | |
private void OnContextualMenu(ContextualMenuPopulateEvent evt) | |
{ | |
var menu = new GenericMenu(); | |
menu.AddItem(new GUIContent("Create Node"), false, () => { CreateNode(); }); | |
menu.ShowAsContext(); | |
} | |
// 创建节点 | |
private void CreateNode() | |
{ | |
var newNode = new MyNode(); | |
newNode.SetPosition(new Rect(mousePosition, Vector2.one * 100)); | |
this.Add(newNode); | |
// 添加节点数据到NodeGraph中(需要自行实现) | |
} | |
} | |
// 定义一个编辑器窗口类,用于显示节点编辑器 | |
public class NodeEditorWindow : EditorWindow | |
{ | |
private MyGraphView graphView; | |
private NodeGraph nodeGraph; | |
// 构造函数 | |
[MenuItem("Window/Node Editor")] | |
public static void ShowWindow() | |
{ | |
var window = GetWindow<NodeEditorWindow>("Node Editor"); | |
window.minSize = new Vector2(800, 600); | |
} | |
// 初始化编辑器窗口 | |
private void OnEnable() | |
{ | |
// 加载或创建NodeGraph | |
nodeGraph = AssetDatabase.LoadAssetAtPath<NodeGraph>("Assets/NodeGraphs/MyNodeGraph.asset"); | |
if (nodeGraph == null) | |
{ | |
nodeGraph = ScriptableObject.CreateInstance<NodeGraph>(); | |
AssetDatabase.CreateAsset(nodeGraph, "Assets/NodeGraphs/MyNodeGraph.asset"); | |
AssetDatabase.SaveAssets(); | |
} | |
// 初始化GraphView | |
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.unity.uielements/Editor/Resources/Styles/GraphView.uss"); | |
graphView = new MyGraphView(this, styleSheet); | |
graphView.StretchToParentSize(); | |
rootVisualElement.Add(graphView); | |
// 初始化节点和边(根据nodeGraph加载数据) | |
// 需要自行实现 | |
} | |
// 保存编辑器状态 | |
private void OnDisable() | |
{ | |
// 保存nodeGraph到磁盘(需要自行实现) | |
} | |
} |
五、总结
本文介绍了如何使用Unity3D的GraphView组件创建一个简单的节点编辑器框架。我们详细讨论了节点编辑器框架的设计、技术实现和代码示例。通过自定义节点、边、面板和工具栏等组件,开发者可以轻松地创建出功能强大的节点编辑器,以满足游戏开发中的复杂需求。希望本文能为Unity3D开发者提供有价值的参考和指导。
更多教学视频
Unity3Dwww.bycwedu.com/promotion_channels/2146264125
相关文章:
Unity3D 基于GraphView实现的节点编辑器框架详解
前言 在Unity3D游戏开发中,节点编辑器是一种强大的工具,它允许开发者以可视化的方式创建和编辑复杂的逻辑和流程。Unity提供了一个强大的UI工具包——GraphView,它使得创建自定义节点编辑器变得相对简单。本文将详细介绍如何使用GraphView实…...
【C++】开源:Armadillo数值计算库配置与使用
😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍Armadillo数值计算库配置与使用。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更新不迷路🥞 文章目录 :smirk:1. Armadillo介绍:blush:2. 环境配置:s…...

HackMyVM-Airbind靶机的测试报告
目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、信息搜集 2、Getshell 3、提权 使用ipv6绕过iptables 四、结论 一、测试环境 1、系统环境 渗透机:kali2021.1(192.168.101.127) 靶 机:debian(192.168.101.11…...

C语言----函数
目录 1. 定义: 2.三要素 3.格式 4. 函数声明 5. 函数调用 6.函数传参 6.1. 值传递 6.2. 地址传递 6.3. 数组传递 string函数族 1.strcpy 2. strlen 3. strcat 4.strcmp 递归函数 1. 定义: 一个完成特定功能的代码模块 2.三要素 功能、…...

MySQL图形化界面工具--DataGrip
之前介绍了在命令行进行操作,但是不够直观,本次介绍图形化界面工具–DataGrip。 安装DataGrip 官网链接:官网下载链接 常规的软件安装流程。 参考链接:DataGrip安装 使用DataGrip 添加数据源: 第一次使用最下面会…...
PyTorch AMP 混合精度中grad_scaler.py的scale函数解析
PyTorch AMP 混合精度中的 scale 函数解析 混合精度训练(AMP, Automatic Mixed Precision)是深度学习中常用的技术,用于提升训练效率并减少显存占用。在 PyTorch 的 AMP 模块中,GradScaler 类负责动态调整和管理损失缩放因子&…...

【Ubuntu20.04】Apollo10.0 Docker容器部署+常见错误解决
官方参考文档【点击我】 Apollo 10.0 版本开始,支持本机和Docker容器两种部署方式。 如果您使用本机部署方式,建议使用x86_64架构的Ubuntu 22.04操作系统或者aarch64架构的Ubuntu 20.04操作系统。 如果您使用Docker容器部署方式,可以使用x…...

【文献精读笔记】Explainability for Large Language Models: A Survey (大语言模型的可解释性综述)(二)
****非斜体正文为原文献内容(也包含笔者的补充),灰色块中是对文章细节的进一步详细解释! 3.1.2 基于注意力的解释(Attention-Based Explanation) 注意力机制可以揭示输入数据中各个部分之间的关系&#…...

朱姆沃尔特隐身战舰:从失败到威慑
前言 "朱姆沃尔特"号驱逐舰是美国海军雄心勃勃的项目,旨在重塑未来海战。它融合了隐身、自动化和强大火力,然而由于技术问题和预算超支,原计划建造32艘的目标被大幅缩减,最终只建造了三艘。该舰的设计特点包括“穿浪逆船…...
免费分享 | 基于极光优化算法PLO优化宽度学习BLS实现光伏数据预测算法研究附Matlab代码
研究内容 宽度学习系统(BLS)简介: BLS是一种新型的神经网络结构,由增强节点(Enhancement Nodes, ENs)和特征节点(Feature Nodes, FNs)组成,具有结构简单、训练速度快、泛…...
logback日志文件多环境配置路径
项目中遇到问题,springboot项目 本地jar包部署到现场后,经常遇到现场的日志存放的路径会更改,经过查阅,有两种方式,下面简单说明一下。 一、第一种 启动jar包时 添加参数 --logging.configF:\hgtest\config\logback.x…...

面试高频:一致性hash算法
这两天看到技术群里,有小伙伴在讨论一致性hash算法的问题,正愁没啥写的题目就来了,那就简单介绍下它的原理。下边我们以分布式缓存中经典场景举例,面试中也是经常提及的一些话题,看看什么是一致性hash算法以及它有那些…...

docker部署项目
docker部署项目 (加载tar包:docker image load -i mysql.tar) 一、jdk环境配置 1.jdk下载地址 --Java Archive | Oracle 中国 --选择好版本进入 --下载Linux x64 Compressed Archive的链接 2.解压 --创建文件夹:mkdir /ro…...

每天40分玩转Django:Django Celery
Django Celery 一、知识要点概览表 模块知识点掌握程度要求Celery基础配置、任务定义、任务执行深入理解异步任务任务状态、结果存储、错误处理熟练应用周期任务定时任务、Crontab、任务调度熟练应用监控管理Flower、任务监控、性能优化理解应用 二、基础配置实现 1. 安装和…...
df.groupby(pd.Grouper(level=1)).sum()
df.groupby(pd.Grouper(level1)).sum() 在 Python 中的作用是根据 DataFrame 的某一索引级别进行分组,并计算每个分组的总和。具体来说: df.groupby(...):这是 pandas 的分组操作,按照指定的规则将 DataFrame 分组。 pd.Grouper(…...
运动控制探针功能详细介绍(CODESYS+SV63N伺服)
汇川AM400PLC和禾川X3E伺服EtherCAT通信 汇川AM400PLC和禾川X3E伺服EtherCAT通信_汇川ethercat通信-CSDN博客文章浏览阅读1.2k次。本文详细介绍了如何使用汇川AM400PLC通过EtherCAT总线与禾川X3E伺服进行通信。包括XML硬件描述文件的下载与安装,EtherCAT总线的启用,从站添加…...

C语言基础18(GDB调试)
文章目录 GDBGDB概述什么是GDB**GDB**的主要功能 GDB的启动GDB常见的启动方式 GDB的退出GDB的常用命令GDB查看源代码指令———list(1)**GDB** 查看设置**------info****GDB** 查看内存**GDB** 设置断点**---break (b)****GDB** 设置观察点**---watch****GDB** 程序调试 GDB完整…...

《向量数据库指南》——应对ElasticSearch挑战,拥抱Mlivus Cloud的新时代
在当今数据驱动的商业环境中,向量数据库的应用正变得愈加重要。随着人工智能和机器学习的快速发展,尤其是在自然语言处理、图像识别及推荐系统等领域,向量数据库以其强大的存储和检索能力,迎来了广泛的应用机会。然而,在实际应用中,企业在选择和实施向量数据库方案时,常…...

c++的stl库中stack的解析和模拟实现
目录 1.stack的介绍和使用 1.1stack的介绍 1.2stack的使用 2.stack的模拟实现 1.stack的介绍和使用 1.1stack的介绍 1. stack 是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。 2. stac…...

C语言——字符函数和内存函数
目录 前言 字符函数 1strlen 模拟实现 2strcpy 模拟实现 3strcat 模拟实现 4strcmp 模拟实现 5strncpy 模拟实现 6strncat 模拟实现 7strncmp 模拟实现 8strstr 模拟实现 9strtok 10strerror 11大小写字符转换函数 内存函数 1memcpy 模拟实现 2…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...

快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...
书籍“之“字形打印矩阵(8)0609
题目 给定一个矩阵matrix,按照"之"字形的方式打印这个矩阵,例如: 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为:1,…...