【Unity3D】实现可视化链式结构数据(节点数据)
关键词:UnityEditor、可视化节点编辑、Unity编辑器自定义窗口工具
使用Newtonsoft.Json、UnityEditor相关接口实现
主要代码:Handles.DrawBezier(起点,终点,起点切线向量,终点切线向量,颜色,null, 线粗度) 绘制贝塞尔曲线
Handles.DrawAAPolyLine(线粗度,顶点1, 顶点2, ...) 根据线段点绘制不规则线段
GUI.Window(id, rect, DrawNodeWindow, 窗口标题);
void DrawNodeWindow(int id) 传入的id即GUI.Window第一个参数,一般传节点唯一标识ID。LinkObj类是节点类,里面有一个位置pos数据是存储该节点窗口位于编辑器的位置SerializableVector2类型是一个可被Json序列化的Vector2,不然无法被序列化。

using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;public class LinkObj
{public int id;public List<int> preList;public List<int> nextList;public SerializableVector2 pos;//位于编辑窗口的位置public LinkObj(int _id, SerializableVector2 _pos){id = _id;pos = _pos;preList = new List<int>();nextList = new List<int>();}
}public static class PathConfig
{public static string SaveJsonPath = Application.dataPath + "/MyGraphicsEditorDemo/Editor/LinkList.json";
}public class MyGraphicsEditorWindow : EditorWindow
{private Dictionary<int, LinkObj> linkObjDict = new Dictionary<int, LinkObj>();private int tempAddId;private Vector2 scrollViewPos;private Dictionary<int, Rect> linkObjRectDict = new Dictionary<int, Rect>();private LinkObj currentSelectLinkObj;private Color defaultColor;private Vector2 detailScrollViewPos;private bool isLoaded;[MenuItem("Tools/可视链结构编辑器")]private static void ShowWindow(){Debug.Log("打开可视链结构编辑器");var window = EditorWindow.GetWindow(typeof(MyGraphicsEditorWindow)) as MyGraphicsEditorWindow;window.minSize = new Vector2(1280, 500);window.Show(true);window.isLoaded = false;}MyGraphicsEditorWindow(){this.titleContent = new GUIContent("可视链结构编辑器");}private void OnGUI(){defaultColor = GUI.color;EditorGUILayout.BeginHorizontal(GUILayout.Width(position.width), GUILayout.Height(position.height));{//左面板(操作)EditorGUILayout.BeginVertical(GUILayout.Width(80));{EditorGUILayout.BeginHorizontal();{if (GUILayout.Button("加载")){//如果本地没有对应的json 文件,重新创建if (File.Exists(PathConfig.SaveJsonPath)){string json = File.ReadAllText(PathConfig.SaveJsonPath);linkObjDict = JsonConvert.DeserializeObject<Dictionary<int, LinkObj>>(json);if (linkObjDict == null){linkObjDict = new Dictionary<int, LinkObj>();}isLoaded = true;}else{isLoaded = false;Debug.LogError("加载失败,尚未存在json文件:" + PathConfig.SaveJsonPath);}}bool isExistFile = File.Exists(PathConfig.SaveJsonPath);if ((isLoaded || !isExistFile) && GUILayout.Button("保存")){//如果本地没有对应的json 文件,重新创建if (!isExistFile){File.Create(PathConfig.SaveJsonPath);}AssetDatabase.Refresh();string json = JsonConvert.SerializeObject(linkObjDict);File.WriteAllText(PathConfig.SaveJsonPath, json);Debug.Log("保存成功:" + json);AssetDatabase.SaveAssets();}}EditorGUILayout.EndHorizontal();EditorGUILayout.BeginVertical();{if (GUILayout.Button("添加节点")){LinkObj obj = new LinkObj(tempAddId, new SerializableVector2(scrollViewPos));if (!linkObjDict.ContainsKey(tempAddId)){linkObjDict.Add(tempAddId, obj);}else{Debug.LogError("节点ID已存在:" + tempAddId);}}tempAddId = int.Parse(EditorGUILayout.TextField("节点ID", tempAddId.ToString()));}EditorGUILayout.EndVertical();}EditorGUILayout.EndVertical();//中间面板(可视节点)EditorGUILayout.BeginVertical(GUILayout.Width(position.width - 500));{EditorGUILayout.LabelField(string.Format("所有链节点"), EditorStyles.boldLabel);EditorGUILayout.BeginHorizontal("box", GUILayout.Height(position.height));{scrollViewPos = EditorGUILayout.BeginScrollView(scrollViewPos, GUILayout.Height(position.height));{BeginWindows();if (linkObjDict != null && linkObjDict.Count > 0){foreach (var item in linkObjDict){int id = item.Key;var linkObj = item.Value;Rect oRect;if (!linkObjRectDict.TryGetValue(id, out oRect)){Rect windowRect = new Rect(180, 50, 180, 100);windowRect.x = linkObj.pos.x;windowRect.y = linkObj.pos.y;linkObjRectDict.Add(id, windowRect);}string str = string.Format("{0}-[节点]", id);if (currentSelectLinkObj != null && currentSelectLinkObj.id == id)GUI.color = Color.yellow;else if (currentSelectLinkObj != null && currentSelectLinkObj.preList.Exists((int x) => x == id))GUI.color = Color.blue;else if (currentSelectLinkObj != null && currentSelectLinkObj.nextList.Exists((int x) => x == id))GUI.color = Color.green;//绘画窗口linkObjRectDict[id] = GUI.Window(id, linkObjRectDict[id], DrawNodeWindow, str);GUI.color = defaultColor;foreach (int nextId in linkObj.nextList){Rect nextRect;if (linkObjRectDict.TryGetValue(nextId, out nextRect)){DrawNodeCurve(linkObjRectDict[id], nextRect, Color.red);}}}}EndWindows();}EditorGUILayout.EndScrollView();}EditorGUILayout.EndHorizontal();}EditorGUILayout.EndVertical();//右面板(编辑选中节点)EditorGUILayout.BeginVertical(GUILayout.Width(250));{EditorGUILayout.LabelField("节点属性", EditorStyles.boldLabel);EditorGUILayout.BeginHorizontal("box", GUILayout.Height(position.height));{EditorGUILayout.BeginVertical();{detailScrollViewPos = EditorGUILayout.BeginScrollView(detailScrollViewPos, GUILayout.Height(position.height));{if (currentSelectLinkObj != null){DrawCurrentLinkObj();}}EditorGUILayout.EndScrollView();}EditorGUILayout.EndVertical();}EditorGUILayout.EndHorizontal();}EditorGUILayout.EndVertical();}EditorGUILayout.EndHorizontal();}//绘画窗口函数private void DrawNodeWindow(int id){EditorGUILayout.LabelField(string.Format("节点ID:{0}", linkObjDict[id].id), EditorStyles.boldLabel);EditorGUILayout.BeginHorizontal();{//创建一个GUI Buttonif (GUILayout.Button("选择")){currentSelectLinkObj = linkObjDict[id];}GUI.color = Color.red;if (GUILayout.Button("删除")){if (EditorUtility.DisplayDialog("询问", "确认删除?", "确认", "取消")){linkObjDict.Remove(id);linkObjRectDict.Remove(id);return;}}GUI.color = defaultColor;}EditorGUILayout.EndHorizontal();//设置改窗口可以拖动GUI.DragWindow();var oItem = linkObjDict[id];Rect oRect;if (oItem != null && linkObjRectDict.TryGetValue(id, out oRect)){oItem.pos = new SerializableVector2(linkObjRectDict[id].position);}}//***描绘连线private void DrawNodeCurve(Rect start, Rect end, Color color, float fValue = 4){//根据不同相对位置决定线条的起点和终点 (看似复杂,实际简单,可优化写法)float startX, startY, endX, endY;//start左 end右时, 起点是start右侧中点, 终点是end左侧中点if (start.x < end.x && Mathf.Abs(start.x + start.width / 2 - end.x - end.width / 2) > 50){ startX = start.x + start.width; endX = end.x; startY = start.y + start.height / 2; endY = end.y + end.height / 2; }//start右 end左时, 起点是start左侧中点, 终点是end右侧中点else if (start.x >= end.x && Mathf.Abs(start.x + start.width / 2 - end.x - end.width / 2) > 50){ startX = start.x; endX = end.x + end.width; startY = start.y + start.height / 2; endY = end.y + end.height / 2; }else{//start上 end下时, 起点是start下侧中点, 终点是end上侧中点if (start.y > end.y){ startX = start.x + start.width / 2; startY = start.y; endX = end.x + end.width / 2; endY = end.y + end.height; }//start下 end上时, 起点是start上侧中点, 终点是end下侧中点else{ startX = start.x + start.width / 2; startY = start.y + start.height; endX = end.x + end.width / 2; endY = end.y; }}Vector3 startPos = new Vector3(startX, startY, 0);Vector3 endPos = new Vector3(endX, endY, 0);//根据起点和终点偏向给出不同朝向的Tan切线Vector3 startTan, endTan;if (start.x < end.x){startTan = startPos + Vector3.right * 50;endTan = endPos + Vector3.left * 50;}else{startTan = startPos + Vector3.left * 50;endTan = endPos + Vector3.right * 50;}//绘制线条 color颜色 fValue控制粗细Handles.DrawBezier(startPos, endPos, startTan, endTan, color, null, fValue);//绘制线条终点的2条斜线 形成箭头Handles.color = color;Vector2 to = endPos;Vector2 v1, v2;//与上方大同小异,根据相对位置得出不同的箭头线段点if (start.x < end.x && Mathf.Abs(start.x + start.width / 2 - end.x - end.width / 2) > 50){v1 = new Vector2(-8f, 8f);v2 = new Vector2(-8f, -8f);}else if (start.x >= end.x && Mathf.Abs(start.x + start.width / 2 - end.x - end.width / 2) > 50){v1 = new Vector2(8f, 8f);v2 = new Vector2(8f, -8f);}else{if (start.y > end.y){v1 = new Vector2(-8f, 8f);v2 = new Vector2(8f, 8f);}else{v1 = new Vector2(-8f, -8f);v2 = new Vector2(8f, -8f);}}//fValue粗细绘制由3个点构成的线段形成箭头Handles.DrawAAPolyLine(fValue, to + v1, to, to + v2);}// 当前选中节点详情编辑页面private void DrawCurrentLinkObj(){EditorGUILayout.LabelField(string.Format("节点ID:{0}", currentSelectLinkObj.id), EditorStyles.boldLabel);EditorGUILayout.Space(10);EditorGUILayout.LabelField("下一个节点");DrawListMember(currentSelectLinkObj.nextList);EditorGUILayout.Space(10);EditorGUILayout.LabelField("上一个节点");DrawListMember(currentSelectLinkObj.preList);}//列表显示private void DrawListMember(List<int> lst, bool isOnlyRead = false){EditorGUILayout.BeginVertical();{if (lst.Count != 0){for (int i = 0; i < lst.Count; i++){EditorGUILayout.BeginHorizontal();{GUILayout.Label((i + 1).ToString(), GUILayout.Width(25));lst[i] = EditorGUILayout.IntField(lst[i]);GUI.color = Color.red;if (GUILayout.Button("-", GUILayout.Width(30))){lst.RemoveAt(i);}GUI.color = defaultColor;}EditorGUILayout.EndHorizontal();}}if (GUILayout.Button("+")){lst.Add(lst.Count);}EditorGUILayout.EndVertical();}}
}
using Newtonsoft.Json;
using System.Collections.Generic;
using UnityEngine;[System.Serializable]
public class SerializableVector2
{public float x;public float y;[JsonIgnore]public Vector2 UnityVector{get{return new Vector2(x, y);}}public SerializableVector2(Vector2 v){x = v.x;y = v.y;}public static List<SerializableVector2> GetSerializableList(List<Vector2> vList){List<SerializableVector2> list = new List<SerializableVector2>(vList.Count);for (int i = 0; i < vList.Count; i++){list.Add(new SerializableVector2(vList[i]));}return list;}public static List<Vector2> GetSerializableList(List<SerializableVector2> vList){List<Vector2> list = new List<Vector2>(vList.Count);for (int i = 0; i < vList.Count; i++){list.Add(vList[i].UnityVector);}return list;}
}相关文章:
【Unity3D】实现可视化链式结构数据(节点数据)
关键词:UnityEditor、可视化节点编辑、Unity编辑器自定义窗口工具 使用Newtonsoft.Json、UnityEditor相关接口实现 主要代码: Handles.DrawBezier(起点,终点,起点切线向量,终点切线向量,颜色,n…...
Three.js推荐-可以和Three.js结合的动画库
在 Three.js 中,3D 模型、相机、光照等对象的变换(如位置、旋转、缩放)通常需要通过动画进行控制,以实现更加生动和富有表现力的效果。然而,Three.js 本身并没有内置的强大动画管理系统,尽管可以通过关键帧…...
增强现实(AR)和虚拟现实(VR)的应用
增强现实(AR)和虚拟现实(VR)是近年来迅速发展的技术,广泛应用于多个行业,提供沉浸式的体验和增强的信息交互。以下是AR和VR的定义及其在不同领域的具体应用。 相关学点: 2025年大数据、通信技术…...
告别机器人味:如何让ChatGPT写出有灵魂的内容
目录 ChatGPT的一些AI味道小问题 1.提供编辑指南 2.提供样本 3.思维链大纲 4.融入自己的想法 5.去除重复增加多样性 6.删除废话 ChatGPT的一些AI味道小问题 大多数宝子们再使用ChatGPT进行写作时,发现我们的老朋友ChatGPT在各类写作上还有点“机器人味”太重…...
【Threejs】从零开始(六)--GUI调试开发3D效果
请先完成前置步骤再进行下面操作:【Threejs】从零开始(一)--创建threejs应用-CSDN博客 一.GUI界面概述 GUI(Graphical User Interface)指的是图形化用户界面,广泛用在各种程序的上位机,能够通过…...
Cocos Creator 试玩广告开发
之前主要是使用Unity,这次刚好项目是试玩游戏的开发,所以临时学了Cocos来开发。所以这篇文章,更加关注从Unity转到Cocos开发的经历以及试玩的基本开发。 首先,我是没有使用过Cocos的,也没有接触过Ts语言,对于Ts的开发开…...
快速解决oracle 11g中exp无法导出空表的问题
在一些生产系统中,有些时候我们为了进行oracle数据库部分数据的备份和迁移,会使用exp进行数据的导出。但在实际导出的时候,我们发现导出的时候,发现很多空表未进行导出。今天我们给出一个快速解决该问题的办法。 一、问题复现 我…...
selenium 报错 invalid argument: invalid locator
环境: Python3.12.2 selenium4.0 报错信息: invalid argument: invalid locator 错误分析: selenium语法错误,find_element方法少写By.XPATH参数 错误语法如下: driver.find_element(//div[id"myid"]) 解决办…...
Flink2.0未来趋势中需要注意的一些问题
手机打字,篇幅不长,主要讲一下FFA中关于Flink2.0的未来趋势,直接看重点。 Flink Forward Asia 2024主会场有一场关于Flink2.0的演讲,很精彩,官方也发布了一些关于Flink2.0的展望和要解决的问题。 1.0时代和2.0时代避免…...
机械鹦鹉与真正的智能:大语言模型推理能力的迷思
编者按: 大语言模型真的具备推理能力吗?我们是否误解了"智能"的本质,将模式匹配误认为是真正的推理? 本文深入探讨了大语言模型(LLMs)是否真正具备推理能力这一前沿科学问题,作者的核…...
本地电脑使用命令行上传文件至远程服务器
将本地文件上传到远程服务器,在本地电脑中cmd使用该命令: scp C:/Users/"你的用户名"/Desktop/environment.yml ws:~/environment.yml 其中,C:/Users/“你的用户名”/Desktop/environment.yml是本地文件的路径, ~/en…...
【系统】Windows11更新解决办法,一键暂停
最近的windows更新整的我是措不及防,干啥都要关注一下更新的问题,有的时候还关不掉,我的强迫症就来了,非得关了你不可! 经过了九九八十一难的研究之后,终于找到了一个算是比较靠谱的暂停更新的方法&#x…...
34. Three.js案例-创建球体与模糊阴影
34. Three.js案例-创建球体与模糊阴影 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染 3D 场景的核心类。它负责将场景中的对象绘制到画布上。 构造器 new THREE.WebGLRenderer(parameters)参数类型描述parametersObject可选参数对象,包…...
Qt同步读取串口
头文件 #include "InsScpi.h" #include <QObject> #include <QSerialPort>class TestSerial : public QObject {Q_OBJECT public:explicit TestSerial(QObject *parent nullptr);//打开设备bool openDevice(const QString &portName);//关闭设备…...
如何用上AI视频工具Sora,基于ChatGPT升级Plus使用指南
没有GPT,可以参考这个教程:详情移步至底部参考原文查看哦~ 1.准备工作 详情移步至底部参考原文查看哦~ 详情移步至底部参考原文查看哦~ 4.Sora使用 详情移步至底部参考原文查看哦 参考文章:【包教包会】如何用上AI视频工具Soraÿ…...
对象的状态变化处理与工厂模式实现
一、引言 在 C 编程中,有效地处理对象的状态变化以及合理运用设计模式可以极大地提高代码的可维护性、可扩展性和可读性。本文将深入探讨 C 如何处理对象的状态变化以及如何实现工厂模式。 二、C 中对象的状态变化处理 使用成员变量表示状态 class GameCharacte…...
关于IP代理API,我应该了解哪些功能特性?以及如何安全有效地使用它来隐藏我的网络位置?
IP代理API是一种服务,允许用户通过访问经过中间服务器的网络连接来改变其公开的互联网协议地址(IP),从而达到隐藏真实地理位置的效果。以下是您在选择和使用IP代理API时应关注的一些功能和安全性考虑: 匿名度ÿ…...
在Linux上将 `.sh` 脚本、`.jar` 包或其他脚本文件添加到开机自启动
在Linux上将 .sh 脚本、.jar 包或其他脚本文件添加到开机自启动 在Linux环境中,有时需要将一些程序、脚本或应用程序设置为开机时自动启动。这对于那些需要在系统启动时启动的服务或应用非常有用。本文将介绍如何将 .sh 脚本、.jar 包或其他脚本文件添加到Linux系统…...
[Maven]构建项目与高级特性
有关于安装配置可以看我的另一篇文章:Maven下载安装配置与简介。 构建项目的生命周期和常用命令 这一节的内容熟记即可,要用了认得出来即可。 在Maven出现之前,项目构建的生命周期就已经存在。对项目进行清理、编译、测试、部署等一系列工作…...
【系统架构设计师】真题论文: 论数据分片技术及其应用(包括解题思路和素材)
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 真题题目(2020年 试题1)解题思路论文素材参考Hash 分片原理一致性 Hash 分片原理按照数据范围(Range Based)分片原理项目采用的分片方式的实现过程和效果真题题目(2020年 试题1) 数据分片就是按照一定的规则…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
