【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) 数据分片就是按照一定的规则…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
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…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

C++中vector类型的介绍和使用
文章目录 一、vector 类型的简介1.1 基本介绍1.2 常见用法示例1.3 常见成员函数简表 二、vector 数据的插入2.1 push_back() —— 在尾部插入一个元素2.2 emplace_back() —— 在尾部“就地”构造对象2.3 insert() —— 在任意位置插入一个或多个元素2.4 emplace() —— 在任意…...
更新 Docker 容器中的某一个文件
🔄 如何更新 Docker 容器中的某一个文件 以下是几种在 Docker 中更新单个文件的常用方法,适用于不同场景。 ✅ 方法一:使用 docker cp 拷贝文件到容器中(最简单) 🧰 命令格式: docker cp <…...