【Unity3D】实现可视化链式结构数据(节点数据)

news2024/12/23 0:40:29

关键词: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;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全核心目标CIA

网络安全的核心目标是为关键资产提供机密性(Confidentiality)、可用性(Availablity)、完整性(Integrity)。作为安全基础架构中的主要的安全目标和宗旨&#xff0c;机密性、可用性、完整性频频出现&#xff0c;被简称为CIA&#xff0c;也被成为你AIC&#xff0c;只是顺序不同而已…

[项目代码] YOLOv8 遥感航拍飞机和船舶识别 [目标检测]

项目代码下载链接 &#xff1c;项目代码&#xff1e;YOLO 遥感航拍飞机和船舶识别&#xff1c;目标检测&#xff1e;https://download.csdn.net/download/qq_53332949/90163939YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为…

去雾Cycle-GAN损失函数

文章目录 GAN-LossIdentity-LossDP-lossCycle-Loss G和F都是生成器 G是hazy → \to → gt F是gt → \to → hazy D y D_y Dy​判别无雾图是真实还是生成的&#xff1f; D x D_x Dx​判别有雾图是真实还是生成的&#xff1f; GAN-Loss 在 DAM-CCGAN 中存在两个判别器 D x D_x D…

2024年企业中生成式 AI 的现状报告

从试点到生产&#xff0c;企业 AI 格局正在被实时改写。我们对 600 名美国企业 IT 决策者进行了调查&#xff0c;以揭示新兴的赢家和输家。 从试点到生产 2024 年标志着生成性人工智能成为企业关键任务的一年。这些数字讲述了一个戏剧性的故事&#xff1a;今年人工智能支出飙升…

组件十大传值

一、defineProps 和 defineEmits defineProps 用于定义子组件接收的 props&#xff0c;即父组件传递给子组件的数据。 接收父组件传递的数据&#xff1a;定义子组件可以接受的属性及其类型。类型检查&#xff1a;确保传递的数据符合预期的类型。 defineEmits 用于定义子组件…

WPF 依赖属性和附加属性

除了普通的 CLR 属性&#xff0c; WPF 还有一套自己的属性系统。这个系统中的属性称为依赖属性。 1. 依赖属性 为啥叫依赖属性&#xff1f;不叫阿猫阿狗属性&#xff1f; 通常我们定义一个普通 CLR 属性&#xff0c;其实就是获取和设置一个私有字段的值。假设声明了 100 个 …

递归实现指数型枚举(递归)

92. 递归实现指数型枚举 - AcWing题库 每个数有选和不选两种情况 我们把每个数看成每层&#xff0c;可以画出一个递归搜索树 叶子节点就是我们的答案 很容易写出每dfs函数 dfs传入一个u表示层数 当层数大于我们n时&#xff0c;去判断每个数字的选择情况&#xff0c;输出被选…

mac 安装graalvm

Download GraalVM 上面链接选择jdk的版本 以及系统的环境下载graalvm的tar包 解压tar包 tar -xzf graalvm-jdk-<version>_macos-<architecture>.tar.gz 移入java的文件夹目录 sudo mv graalvm-jdk-<version> /Library/Java/JavaVirtualMachines 设置环境变…

【WPS安装】WPS编译错误总结:WPS编译失败+仅编译成功ungrib等

WPS编译错误总结&#xff1a;WPS编译失败仅编译成功ungrib等 WPS编译过程问题1&#xff1a;WPS编译失败错误1&#xff1a;gfortran: error: unrecognized command-line option ‘-convert’; did you mean ‘-fconvert’?解决方案 问题2&#xff1a;WPS编译三个exe文件只出现u…

Redis 集群实操:强大的数据“分身术”

目录 Redis Cluster集群模式 1、介绍 2、架构设计 3、集群模式实操 4、故障转移 5、常用命令 Redis Cluster集群模式 1、介绍 redis3.0版本推出的Redis Cluster 集群模式&#xff0c;每个节点都可以保存数据和整个集群状态&#xff0c;每个节点都和其他所有节点连接。Cl…

数据结构day5:单向循环链表 代码作业

一、loopLink.h #ifndef __LOOPLINK_H__ #define __LOOPLINK_H__#include <stdio.h> #include <stdlib.h>typedef int DataType;typedef struct node {union{int len;DataType data;};struct node* next; }loopLink, *loopLinkPtr;//创建 loopLinkPtr create();//…

植物大战僵尸杂交版v3.0.2最新版本(附下载链接)

B站游戏作者潜艇伟伟迷于12月21日更新了植物大战僵尸杂交版3.0.2版本&#xff01;&#xff01;&#xff01;&#xff0c;有b站账户的记得要给作者三连关注一下呀&#xff01; 不多废话下载链接放上&#xff1a; 夸克网盘链接&#xff1a;&#xff1a;https://pan.quark.cn/s/5c…

Unity 圆形循环复用滚动列表

一.在上一篇垂直循环复用滚动列表的基础上&#xff0c;扩展延申了圆形循环复用滚动列表。实现此效果需要导入垂直循环复用滚动列表里面的类。 1.基础类 using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using …

京东大数据治理探索与实践 | 京东零售技术实践

01背景和方案 在当今的数据驱动时代&#xff0c;数据作为关键生产要素之一&#xff0c;其在商业活动中的战略价值愈加凸显&#xff0c;京东也不例外。 作为国内领先的电商平台&#xff0c;京东在数据基础设施上的投入极为巨大&#xff0c;涵盖数万台服务器、数 EB 级存储、数百…

android:sharedUserId 应用进程声明介绍

背景 adb install 安装系统软件报错,原因是签名不一致,进程改变。 代码分析 AndroidManifest.xml 定义的 android:sharedUserId 应用归属进程不同,从phone切换到system。 初始配置 <manifest xmlns:android="http://schemas.android.com/apk/res/android"c…

Liveweb视频融合共享平台在果园农场等项目中的视频监控系统搭建方案

一、背景介绍 在我国的大江南北遍布着各种各样的果园&#xff0c;针对这些地处偏僻的果园及农场等环境&#xff0c;较为传统的安全防范方式是建立围墙&#xff0c;但是仅靠围墙仍然无法阻挡不法分子的有意入侵和破坏&#xff0c;因此为了及时发现和处理一些难以察觉的问题&…

交换机链路聚合(手动负载分担模式)(eNSP)

目录 交换机SW_C配置: 交换机-PC划分vlan: 交换机-交换机端口聚合: 交换机SW_D配置: 交换机-PC划分vlan: 交换机-交换机端口聚合: 验证: 链路聚合的端口清除: 交换机端口聚合的存在意义主要有以下几点: 增加带宽 提高冗余性和可靠性 实现负载均衡 降低成本 …

玩转树莓派Pico(19): 迷你气象站5——软件整合

一、前言 各个模块都已经测试了&#xff0c;硬件也组装完成&#xff0c;到了软件整合的步骤了。 目前我仅按照自己的经验来整合&#xff0c;肯定要踩坑的。以后除了多去开源网站看看大佬的代码&#xff0c;还要继续揣摩《无线电》杂志里的文章。很多文章对我来说比较高深&#…

30. 多进程编程

一、什么是进程 进程&#xff08;process&#xff09;则是一个执行中的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其它用于跟踪执行的辅助数据。操作系统管理其上所有进程的执行&#xff0c;并为这些进程合理分配时间。进程也可以通过派生新的进程来执行其它任务&a…

Unity Post请求发送fromdata数据content-type

wwwfrom 的 headers["Content-Type"]修改 错误代码&#xff1a; WWWForm form new WWWForm(); if (form.headers.ContainsKey("Content-Type")) {string boundary string.Format("--{0}", DateTime.Now.Ticks.ToString("x"));form…