【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 Button
            if (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;
    }
}

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

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

相关文章

Group FLUX - Beta Sprint Essay2

文章目录 I. SCRUMAchievements from yesterday’s stand-up meeting to the present Commit recordFrontend-CommitsBackend-Commits PM ReportBurnup mapRunning image of our current program I. SCRUM Achievements from yesterday’s stand-up meeting to the present Zh…

硬盘清洁器 -一个功能出色的的文件与使用纪录清理工具,不仅可以将磁盘中不必要的暂存盘一次扫除,供大家学习研究参考

【核心功能】 1.硬件性能检测。 2.清理日常垃圾信息。 3.永久性删除文件。不可恢复擦除可用空间。 4.系统恢复和还原。 5.磁盘管理。 6.重复文件删除。坏链清除&#xff0c;删除非必要文件。 7.恢复删除文件。含电子照片、PDF、视频等。 8.批量重命名。 下载&#xff1a;https:…

[Linux] 进程信号概念 | 信号产生

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;青果大战linux 总有光环在陨落&#xff0c;总有新星在闪烁 为什么我的课设这么难…

流程引擎Activiti性能优化方案

流程引擎Activiti性能优化方案 Activiti工作流引擎架构概述 Activiti工作流引擎架构大致分为6层。从上到下依次为工作流引擎层、部署层、业务接口层、命令拦截层、命令层和行为层。 基于关系型数据库层面优化 MySQL建表语句优化 Activiti在MySQL中创建默认字符集为utf8&…

51c视觉~合集36

我自己的原文哦~ https://blog.51cto.com/whaosoft/12275223 #无监督盲超分算法MLMC 即插即用的解决方案 本文介绍了一种新的无监督盲超分辨率算法MLMC&#xff0c;该算法结合了元学习和马尔可夫链蒙特卡罗核估计&#xff0c;无需监督预训练或参数先验&#xff0c;即可实现…

Firecrawl教程①:自动化抓取与数据转化,赋能AI应用

Firecrawl教程①:自动化抓取与数据转化,赋能AI应用 前言一、功能特点1. 支持 LLM 可处理的数据格式2. 全面抓取网站3. 强大的操作支持4. 灵活的定制选项5. 支持多种编程语言 SDK二、如何开始使用 Firecrawl第一步:获取 API 密钥第二步:官网在线工具使用第三步:安装 Firecr…

关于目标检测YOLO 各版本区别v1-v11/X/R/P

概述 YOLO&#xff08;You Only Look Once&#xff0c;你只看一次&#xff09;是一系列开创性的实时目标检测模型&#xff0c;它们彻底改变了计算机视觉领域。由Joseph Redmon开发&#xff0c;后续版本由不同研究人员迭代&#xff0c;YOLO模型以其在图像中检测对象的高速度和准…

SpringBoot3整合FastJSON2如何配置configureMessageConverters

在 Spring Boot 3 中整合 FastJSON 2 主要涉及到以下几个步骤&#xff0c;包括添加依赖、配置 FastJSON 作为 JSON 处理器等。下面是详细的步骤&#xff1a; 1. 添加依赖 首先&#xff0c;你需要在你的 pom.xml 文件中添加 FastJSON 2 的依赖。以下是 Maven 依赖的示例&#…

java全栈day19--Web后端实战(java操作数据库3)

一、MyBatis 1.1介绍 前提引入&#xff1a; controller(控制层)作用&#xff1a;接受请求&#xff0c;响应数据 service(业务层)作用&#xff1a;负责具体的逻辑处理 dao(持久层)作用&#xff1a;数据访问层 一般的访问流程&#xff1a;浏览器发起请求过来&#xff0c;先…

社区版 IDEA 开发webapp 配置tomcat

1.安装tomcat 2.构建webapp项目结构 3.配置tomcat 安装smart tomcat插件 完成后settings会多一个选项tomcat server&#xff0c;然后我们把本地的tomcat配置过去。 4.为项目配置tomcat 配置项目路径&#xff0c;端口号。Context path 配置/ 表示直接用localhost就能访问 5.添加…

重新定义页签!Choerodon UI Tabs让管理更高效

01 引言 Tabs 组件通过提供平级区域&#xff0c;将大块内容进行有效的收纳和展现&#xff0c;从而保持界面整洁。但在企业应用的快速发展中&#xff0c;这样传统的页签组件已无法满足我们对界面布局和个性化展示的追求。Choerodon UI Tabs 组件通过支持多级分组、个性化配置、…

Qt编译MySQL数据库驱动

目录 Qt编译MySQL数据库驱动 测试程序 Qt编译MySQL数据库驱动 &#xff08;1&#xff09;先找到MySQL安装路径以及Qt安装路径 C:\Program Files\MySQL\MySQL Server 8.0 D:\qt\5.12.12 &#xff08;2&#xff09;在D:\qt\5.12.12\Src\qtbase\src\plugins\sqldrivers\mysql下…

vulnhub靶场【DriftingBlues】之9 final

前言 靶机&#xff1a;DriftingBlues-6&#xff0c;IP地址192.168.1.66 攻击&#xff1a;kali&#xff0c;IP地址192.168.1.16 都采用虚拟机&#xff0c;网卡为桥接模式 主机发现 使用arp-scan -l或netdiscover -r 192.168.1.1/24 信息收集 使用nmap扫描端口 网站探测 访…

智慧公交指挥中枢,数据可视化 BI 驾驶舱

随着智慧城市的蓬勃发展&#xff0c;公共交通作为城市运营的核心枢纽&#xff0c;正朝着智能化和数据驱动的方向演进。通过整合 CAN 总线技术(Controller Area Network&#xff0c;控制器局域网总线)、车载智能终端、大数据分析及处理等尖端技术&#xff0c;构建的公交“大脑”…

页面无滚动条,里面div各自有滚动条

一、双滚动条左右布局 实现效果 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Doc…

美畅物联丨分布式锁实战:Spring Boot项目中的Redis应用

在分布式系统里&#xff0c;多个节点或许会同时对共享资源进行访问与操作。为防止出现数据不一致、资源竞争等状况&#xff0c;就需要一种机制来对这些并发访问加以协调&#xff0c;于是分布式锁就出现了。它如同一把全局的钥匙&#xff0c;在同一时刻仅有一个节点能够获取该钥…

[计算机网络]ARP协议的故事:小明找小红的奇妙旅程

1.ARP小故事 在一个繁忙的网络世界中&#xff0c;每个设备都有自己的身份标识——MAC地址&#xff0c;就像每个人的身份证号码一样。在这个故事里&#xff0c;我们的主角小明&#xff08;主机&#xff09;需要找到小红&#xff08;目标主机&#xff09;的MAC地址&#xff0c;才…

从RNN到Transformer:生成式AI自回归模型的全面剖析

个人主页&#xff1a;chian-ocean 文章专栏 生成式AI中的自回归模型详解 在生成式AI的飞速发展中&#xff0c;自回归模型作为核心技术之一&#xff0c;成为文本生成、语音合成、图像生成等领域的重要支柱。本文将全面探讨自回归模型的原理、架构、实际应用&#xff0c;并结合…

「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台

本篇将带你实现一个虚拟音乐控制台。用户可以通过界面控制音乐的播放、暂停、切换歌曲&#xff0c;并查看当前播放的歌曲信息。页面还支持调整音量和动态显示播放进度&#xff0c;是音乐播放器界面开发的基础功能示例。 关键词 UI互动应用音乐控制播放控制动态展示状态管理按钮…

用QT制作的倒计时软件

一、pro代码 RC_ICONS countdown.ico 二、mainwindow.cpp代码 #include "mainwindow.h" #include "ui_mainwindow.h"#include <QDateTime> #include <QMessageBox> #include <QSettings>MainWindow::MainWindow(QWidget *parent): QM…