Unity编辑扩展:功能篇之Json数据编辑器

这篇具有很好参考价值的文章主要介绍了Unity编辑扩展:功能篇之Json数据编辑器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Unity编辑扩展:功能篇之Json数据编辑器

前言

编辑器扩展算是比较纯粹的功能开发,基本没有什么理论知识,都是一些Unity相关接口的使用与数据类型的设计操作等。在本篇文章主要的文字描述基本都是在做代码解释,为了使内容接受度更高,我会尽量描述到代码结构中的每个细节。如果有对此不太了解又很感兴趣的小伙伴可以尝试手动过一遍代码,相信很快很快就可以掌握编辑器开发方面的使用技巧

在前篇文章中有对编辑器扩展UI控件方面的一些基础内容做了简单的描述,大概说明了Unity编辑器界面相关控件的创建接口,链接为:

Unity编辑器扩展 UI控件篇

在掌握编辑器界面控件使用的基础上,利用该控件完成数据编辑工具,对于编辑器扩展来说,通常来说都是以数据编辑为基础的功能扩展。而数据的存储通常以Json为媒介来记录信息,掌握了Json数据的编辑后,可以很方便的在此基础上扩展自己的功能逻辑

一、 设定数据类结构

通常来说,功能模块使用数据的结构通常以类为基本单位。在本数据编辑工具开发案例中,需要先设定几个数据类,作为数据转换的基础

在文章开头的动图中,核心数据块的结构如下,排序数值字段与唯一标识身份ID,除此之外就是一些功能性数据。在本案例中设定了一些字符串与枚举类型的数据字段

Unity编辑扩展:功能篇之Json数据编辑器
为提升通用性,封装排序数值字段与唯一标识身份IDBaseData作为数据类的基类,基于其特性直接设定两个Int数据值类型即可。而后续所有需编辑数据类作为BaseData的派生类设定。如下面代码中的CharacterData,在继承基类的基础上设定自身需编辑器的核心数据字段,这里简单的设计了字符串与枚举等几个数据,该数据类的数据内容与上图中的UI节点相对应,后面提到的数据节点代指该类


public class BaseData
{
    /// 编辑器状态下排序
    public int SortNum;
    /// 唯一ID
    public int ID;
}

public class CharacterData : BaseData
{
    public string name;
    public CharacterType type;
    public DetialCharacterData detialData;
}

public class MainData
{
    public Dictionary<string, CharacterData> CharacterDatas;
}

除了需要编辑的数据类外,编辑器本身也需要设定缓存一组数据来维护界面显示的相关格式,如节点的位置、节点被选中的状态等状态。创建数据类命名为EditorNodeData,并代称节点编辑器数据,而关于类中相关字段的具体使用方式会在后面数据初始化时提到:

public class EditorNodeData 
{
    public int sortNum;
    public int DataID = 0;
    public bool isInstace;
    public bool isSelect;
    public Rect rect;
}
二、将缓存数据初始化

开始编辑器界面绘制前,定义一些数据字段作为临时缓存使用, 并对数据做初始化:

  • mainDataMainData 类型数据,用来管理由Json反序列化数据与序列化为Json数据,即Json数据编辑时的内存缓存数据载体
  • selectNode:记录选中节点的节点编辑器数据
  • editorNodes:所有节点编辑器数据
  • canvasScrollPosition:用于背景拖动的坐标缓存数据

在初始化数据时,除了对各种数据容器实例化外,比较重要的是读取Json内容并反序列化到mainData,用来载入上次编辑后的保存的节点数据内容,具体的反序列化过程会在后面的内容中提到

    private MainData mainData;
    private Rect viewRect;
    private Vector2 canvasScrollPosition;
    private EditorNodeData selectNode;
    private Dictionary<int, EditorNodeData> editorNodes;
    void InitData()
    {
        mainData = JsonToMainData();
        if(mainData == null) mainData = new MainData();
        if (mainData.CharacterData == null) mainData.CharacterData = new Dictionary<string, CharacterData>();
        editorNodes = new Dictionary<int, EditorNodeData>();
        viewRect = new Rect(0, 0, position.width, position.height);
        canvasScrollPosition = new Vector2(0, 0);
        InitNodasData();
    }
    

完成对mainData的反序列化后,通过对数据节点编辑初始化编辑器界面对应的数据对象。具体到细节中,对排序字段与ID字段来说,字节读取mainData数据即可。同时由于反序列化而来的数据已存在实体,默认设定isInstancetrue。最为关键的编辑器辅助数据就是rect矩形定位字段,用来确定当前数据具象化的UI节点在场景中的位置

    public void InitNodasData()
    {
        if (mainData == null) return;
        foreach (var data in mainData.CharacterDatas.Values)
        {
            EditorNodeData node = new EditorNodeData();
            node.sortNum = data.SortNum;
            node.DataID = data.ID;
            node.rect = new Rect(20 + node.sortNum * 250, 80, 230, 160);
            node.isInstance = true;
            if (250 + node.sortNum * 250 > viewRect.width) 
            {
                viewRect.width += 250;
            }
            editorNodes.Add(node.sortNum, node);
        }
    }
三、数据类序列化与Json数据反序列化

由于Unity内置的Json处理工具JsonUtility对于ListDictionary支持不是很友好,在本案例中就选择使用LitJson作为对Json数据序列化与反序列化的处理工具。LitJson可以直接从Github上下载获取,链接地址:LitJson

既然要通过Json为介质媒体存储数据,首先创建以.json为后缀的文本文件并导入项目中,当然也可以直接使用Txt文件。然后获取到项目文件所在的路径,在前篇编辑器基础介绍中有描述到定位项目Asset文件路径的接口,并结合项目Asset的相对路径设定全局路径:

    public static string jsonFIlePath
    {
        get
        {
            return Application.dataPath + "/Datas/dataDemo.json";
        }
	}

前面数据初始化的时候,提到需要将本地存储数据反序列化到内存中。具体操作就是在得到存储数据的文本文件路径后,就可以通过该路径来获取到文件中的字节流并通过LitJson的接口方法将其反序列化,转换为实例化的数据结构MainData。由于本案例操作数据量较小,直接主线程内操作即可,如果Json数据量过大,可以考虑协程异步读取数据避免主线程的卡顿

    private MainData JsonToMainData()
    {
        byte[] bts = File.ReadAllBytes(jsonFIlePath);
        if (bts.Length == 0) return null;
        string str=System.Text.Encoding.UTF8.GetString(bts);
        if(string.IsNullOrEmpty(str)) return null;
        return JsonMapper.ToObject<MainData>(str);
    }

类似Json数据的反序列化,对于Json数据的序列化的过程做一个上面的反向操作即可。不过在序列化之前,需要对缓存数据做处理,即剔除未实例化数据的排序序号,使得编辑数据排序保持连续:

    private void WriteDataToText()
    {
        SortMainData();
        string str = JsonMapper.ToJson(mainData);
        byte[] bts = System.Text.Encoding.UTF8.GetBytes(str);
        File.WriteAllBytes(jsonFIlePath, bts);       
    }
    public void SortMainData()
    {
        if (mainData == null) return;
        if (!editorNodes.ContainsKey(mainData.CharacterDatas.Count)) return;
        int index = 0;
        for (int i = 0; i < editorNodes.Count; i++)
        {
            if (editorNodes[i].isInstance)
            {
                CharacterData data = mainData.CharacterDatas[editorNodes[i].DataID.ToString()];
                data.SortNum -= index;   
            }
            else
            {
				index++;
            }
        }
    }

需要注意的是,在前面的数据准备阶段对MainData数据结构设计时,是以CharacterDataID作为CharacterDatas的键,而比较特殊的地方就是将本来为int类型ID的键转换成了字符串类型,这是因为LitJson在对于Dictionary类型数据处理时,只支持以字符串为键的格式设定。不过可以通过修改其源码的类型判断方法来让其支持其他类型

如果想了解具体修改方法可以查看该链接:
修改LitJson以支持int类型的字典Key

创建编辑器界面

在上篇文章已经介绍了编辑器界面的创建细节,所以这里就不再做详细的描述,如果对代码中的内容不理解,可以翻阅前篇文章或者Unity官方提供的文档介绍:

public class JsonEditor : EditorWindow
{
    private static JsonEditor editorWindow;
    [MenuItem("Editor/JsonEditor &1")]
    public static JsonEditor CreateWindow()
    {
        editorWindow = GetWindow<JsonEditor>( "Json数据编辑器");
        editorWindow.autoRepaintOnSceneChange = true;
        editorWindow.Show();
        return editorWindow;        
    }
}
编辑器内容绘制与功能实现

最上栏Button列表:
Unity编辑扩展:功能篇之Json数据编辑器

编辑器界面的第一部分是五个全局操作功能按钮,用来做数据节点的增删查与界面和数据的总体管理

	private void UpButtonBlock()
    {
        if (GUI.Button(new Rect(10, 10, 130, 40), "添加节点"))
        {
            AddNode();
        }    
        if(GUI.Button(new Rect(160, 10, 130, 40),"删除选中节点"))
        {
            RemoveSelectNode();
            return;
        } 
        if (GUI.Button(new Rect(310, 10, 130, 40), "查找节点"))
        {
            isShowFindWindow = true;
        }
        if (GUI.Button(new Rect(460, 10, 130, 40), "手动保存节点数据"))
        {
            WriteDataToText();
        }
        if (GUI.Button(new Rect(610, 10, 130, 40), "关闭窗口"))
        {
            if (EditorUtility.DisplayDialog("提示", "确定是否关闭界面", "确定", "取消"))
            {
                Close();
            }
        }
    }

接下来根据UI功能设计数据处理逻辑代码

添加节点数据:

创建新的编辑器节点数据EditorNodeData 并初始化,加入到节点编辑缓存数据editorNodes中,其中关键节点的设计意义为:

  • sortNum:作为排序存在,根据当前已存在数据做累加
  • rect:为UI节点定位并设定大小
  • isInstance :添加节点后,如果未填入节点ID,不会实例化该数据状态,以该字段记录
    private void AddNode()
    {
        EditorNodeData node = new EditorNodeData();
        node.sortNum = editorNodes.Keys.Count;
        node.rect = new Rect(20 + node.sortNum * 250, 80, 230, 160);
        editorNodes.Add(node.sortNum, node);
        if ( 250 + node.sortNum * 250 > viewRect.width)
        {
            viewRect.width= viewRect.width + 250;
        }
        node.isInstance = false;
    }

由于增加节点个数,会使得内容窗口边长,如果不做处理,后面的节点内容无法显示,因此会在增加节点时,增大内容框的长度,而内容框的具体显示逻辑,会在后续的UI层逻辑时处理

删除选中节点数据:

维护一个节点数据CharacterData的引用做为选中节点数据的标定,如果该节点数据不为空时,可触发数据的删除逻辑,通过对mainData内的选中的CharacterData清除,并再次序列化与初始化所有数据,来达到节点编辑器数据数据editorNodes完整刷新的目的。虽然这种暴力这种方式算不上优雅,不过用起来倒是非常简单干脆

    private void RemoveSelectNode()
    {
        if (selectNode == null) return;
        if (EditorUtility.DisplayDialog("提示", "确定删除当前节点数据,该行为不可回溯", "确定", "取消"))
        {
            mainData.CharacterDatas.Remove(selectNode.DataID.ToString());
            foreach (var item in mainData.CharacterDatas.Values)
            {
                if(item.SortNum>selectNode.sortNum)
                {
                    item.SortNum--;
                }
            }
            selectNode = null;
            WriteDataToText();
            InitData();           
        }     
    }
  • 手动保存节点数据: 调用WriteDataToText()将编辑完的数据序列化并保存到本地文件内

  • 查找节点:通过bool类型isShowFindWindow 记录查找状态,在后续的界面逻辑处理切换到该状态

  • 关闭窗口:对于Unity中编辑器关闭界面,调用EditorWindow中的 Close完成关闭

核心节点绘制:

对于核心节点窗口有上面几种状态:
Unity编辑扩展:功能篇之Json数据编辑器

各个节点状态设定为:

  • 节点0:首次创建节点,在未填写ID时,无法填写其他数据
  • 节点1:完成ID填写后,设定一些未初始化的数据提示
  • 节点2:数据填写完整后的象时效果
  • 节点3:选中状态下的显示效果

创建一个新的节点时,由于尚未填写节点ID,所以无法直接创建节点数据的实例,需要设定提示输入数据节点的ID,同时对输入的ID做合法性判断,来确保其满足唯一性或者其他条件,例如在本案例中会通过字典查询方式来确保数据ID的唯一性

类似于ID,可以同样对于数据节点内字段的输入状态监控并添加风险提示,提升数据编辑的可靠性,如节点排序为1的节点编辑单位中,添加对角色名字是否为空字符串的判断提示,来尽量避免人为失误产生的数据格式错误

	private void CreateNodeUI(int sortNum)
    {
        EditorGUI.LabelField(new Rect(100, 0, 50, 30), "节点:" + sortNum.ToString(), titleFontStyle);
        if(editorNodes.TryGetValue(sortNum,out EditorNodeData node))
        {
            EditorGUILayout.Space();
            if(node.isInstance)
            {
                if(mainData.CharacterDatas.TryGetValue(node.DataID.ToString(),out CharacterData data))
                {
                    EditorGUILayout.IntField("ID", data.ID);
                    data.name = EditorGUILayout.TextField("角色名字:", data.name);
                    if (string.IsNullOrEmpty(data.name))
                    {
                        EditorGUILayout.HelpBox("名字为空,未填写内容", MessageType.Error);
                    }
                    data.type = (CharacterType)EditorGUILayout.EnumPopup("角色类型:", data.type);
                    if (node.isSelect)
                    {
                        if (GUI.Button(new Rect(0, 130, 250, 30), "处于选中状态")){}
                        GUI.Button(new Rect(0, 130, 30, 30), "", windowSelectStyle.box);
                    }
                    else
                    {
                        if (GUI.Button(new Rect(0, 130, 250, 30), "点我选中"))
                        {
                            ChangeSelectNode(node);
                        }
                    }                                       
                }
            }
            else
            {
                node.DataID = EditorGUILayout.DelayedIntField("ID", node.DataID);
                if (node.DataID == 0)
                {
                    EditorGUILayout.HelpBox("填写完ID后解锁功能(回车保存)", MessageType.Error);
                }
                else
                {
                    if (mainData.CharacterDatas.ContainsKey(node.DataID.ToString()))
                    {
                        EditorGUILayout.HelpBox("该ID已存在,不可使用", MessageType.Error);
                    }
                    else
                    {
                        CreateCharacterData(node);
                        node.isInstance = true;
                    }
                }
            }          
        }
        else
        {
            EditorGUILayout.HelpBox("系统出现未知错误", MessageType.Error);
        }
    }

在上面的节点界面中,节点标题与选中状态会额外设定格式,添加示意图突出选中状态。该效果的实现就需要通过手动设定控件皮肤格式Unity编辑扩展:功能篇之Json数据编辑器

    private void InitStyleData()
    {
        titleFontStyle = new GUIStyle();
        titleFontStyle.fontStyle = FontStyle.Bold;
        titleFontStyle.fontSize = 16;
        titleFontStyle.normal.textColor = Color.white;

        windowSelectStyle = new GUISkin();
        windowSelectStyle.box.normal.textColor = Color.blue;       
        windowSelectStyle.box.normal.background = AssetDatabase.LoadAssetAtPath<Texture2D>(textureAssetpath); 
    }

Unity中默认不存在对号的提示图片,需要从外部下载并放入项目内,然后通过资源路径加载

需要注意的是,为了避免游戏运行无用的资源被打包,会将该Texture2D放置于Editor路径下,而将其加载到内存中可以通过AssetDatabase.LoadAssetAtPath接口实现

节点数据查找:

Unity编辑扩展:功能篇之Json数据编辑器

通过ID来执行索引,查找mainData数据中是否存在该ID的节点数据,如果存在,将相关字段的数据编辑UI显示出来,不存在则显示提示内容

	private void FindNodeWindow(int id)
    {
        EditorGUI.LabelField(new Rect(100, 0, 50, 30), "查找数据节点", titleFontStyle);

        if (GUILayout.Button("关闭界面"))
        {
            isShowFindWindow = false;
            return;
        }
        EditorGUILayout.LabelField("请输入要查找的数据ID:");
        findDataKey = EditorGUILayout.IntField(findDataKey);
        EditorGUILayout.Space(20);
        if (mainData.CharacterDatas.TryGetValue(findDataKey.ToString(), out CharacterData data))
        {
            EditorGUILayout.LabelField("基本信息(不可修改):");
            EditorGUILayout.IntField("ID", data.ID);
            EditorGUILayout.TextField("名字:", data.name);

            if (data.detialData == null)
            {
                data.detialData = new DetialCharacterData();
            }
            EditorGUILayout.Space();
            EditorGUILayout.LabelField("详细信息(可修改):");
            EditorGUILayout.LabelField("详细描述:");
            data.detialData.describe = EditorGUILayout.TextField(data.detialData.describe, GUILayout.MaxHeight(50));
            data.detialData.IsHardStraight = EditorGUILayout.Toggle("是否无敌", data.detialData.IsHardStraight);
        }
        else
        {
            EditorGUILayout.HelpBox("不存在该ID的数据", MessageType.Warning);
        }
    }
实现窗口拖拽效果

将窗口设定为一个大的ScrollView,通过控制外边框与内嵌的框体大小,然后调整内框位置实现滑动,首先是外边框,通常将其与窗口宽高绑定,内框则需要根据窗口内的节点数量做计算。计算相关的代码在前面初始化阶段与添加几点编辑数据时有涉及到

而拖动效果需要监控数据的输入状态,通过定义EventUnity的事件监控系统,可以得到鼠标移动的速度向量。根据该速度向量就可以对ScrollView的内嵌框坐标位移,得到窗口的拖动效果

需要注意的EditorWindow的控件刷新并不一定是每帧更新,如果希望编辑器界面可以实时响应我们的操作,通过GUI.changed = true来强制刷新界面状态


        canvasScrollPosition = GUI.BeginScrollView(new Rect(0,0, position.width, position.height), canvasScrollPosition, viewRect, true, true);        
        Event e = Event.current;
        if (e.isMouse)
        {
            if (canvasScrollPosition.x >= 0 && canvasScrollPosition.x <= viewRect.width)
            {
                canvasScrollPosition -= e.delta * 1.2f;
                GUI.changed = true;
            }
        }
        RefreshNoteDatas();
        GUI.EndScrollView();
总结

整个Json数据可视化编辑器的基础代码结构已经完成。不过其中还有一些需要完善的地方,比如数据的批量操作,节点数据的复制粘贴、节点灵活性排序等等的功能扩展文章来源地址https://www.toymoban.com/news/detail-462593.html

到了这里,关于Unity编辑扩展:功能篇之Json数据编辑器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity编辑器扩展(外挂)

    每日一句:未来的样子藏在现在的努力里 目录 什么是编译器开发 C#特性[System.Serializable] 特殊目录 命名空间 /*检视器属性控制*/     //添加变量悬浮提示文字     //给数值设定范围(最小0,最大150) //指定输入框,拥有5行 //默认显示5行,最多显示10行内容,再多用滚动条控

    2024年01月24日
    浏览(87)
  • Unity编辑器扩展之GenericMenu菜单扩展

    内容将会持续更新,有错误的地方欢迎指正,谢谢!   Unity编辑器扩展之GenericMenu自定义菜单       TechX 坚持将创新的科技带给世界! 拥有更好的学习体验 —— 不断努力,不断进步,不断探索 TechX —— 心探索、心进取! 助力快速掌握 GenericMenu 菜单扩展 为初学者节省宝贵的

    2024年02月01日
    浏览(75)
  • Unity 扩展自定义编辑器窗口

    在Assets文件夹路径下任意位置创建Editor文件夹,将扩展编辑器的代码放在Editor文件夹下 代码中首先引用命名空间 然后将创建的类继承自EditorWindow 然后通过扩展编辑器菜单功能调用创建窗口的方法 要注意方法中泛型参数需要传入的是自己代码的类,这个功能是根据后面OnGUI方

    2024年04月27日
    浏览(56)
  • Unity 编辑器扩展之 Attribute

    Unity内置属性[Attribute]是一种类似修饰功能的标签。可以对OnSceneGUI,InspectorGUI,MenuGUI,WindowGUI等实现各种各样的GUI扩展。用户只要添加上特性标签,就能够自由的使用这些扩展功能。下面列出一些常用的标签: 隐藏属性在Inspector面板上的显示。在继承了MonoBehaviour的类中,用

    2023年04月09日
    浏览(70)
  • 【Unity编辑器扩展】| Inspector监视器面板扩展

    前言 前面我们介绍了Unity中编辑器扩展的一些基本概念及基础知识,还有编辑器扩展中用到的相关特性Attribute介绍。 后面就来针对Uniity编辑器扩展中比较常用的模块进行学习介绍。 本文就来详细介绍一下Unity编辑器扩展中关于 Inspector面板 扩展功能学习。

    2024年02月08日
    浏览(65)
  • 盘点Unity几款编辑器扩展工具

    unity 编辑器一个不容忽视的强大之处就是非常易于自定义扩展工具,来满足各种各样的美术、策划及程序上的需求。今天为大家介绍Asset Store资源商店中几款实用的编辑器扩展工具,帮助大家直接在Unity编辑器中完成3D建模与调整工作,免去与其它软件进行数据转换的过程,从

    2024年04月09日
    浏览(48)
  • Unity编辑器扩展-第四集-获取物体的方法

    第三集链接:Unity编辑器扩展-第三集-添加按钮到组件菜单并且重置组件_菌菌巧乐兹的博客-CSDN博客  一、本节目标+效果展示 1.改选中单个物体的名字 2.改选中所有物体的名字  3.选中了所有的物体,但只改第一层物体的名称  4.来个有用的(选中的所有物体,按顺序起名)

    2024年02月14日
    浏览(58)
  • 【Unity UIToolkit】UIBuilder基础教程-制作简易的对话系统编辑器 3步教你玩转Unity编辑器扩展工具

    随着Unity开发的深入,基本的Unity编辑器界面并不能满足大部分玩家高阶开发的要求。为了提高开发的效率,有针对性的定制化扩展编辑器界面是提高开发效率的不错选择。 今天就给大家带来Unity官方提高的编辑器扩展工具UIToolkit(集成了UIBuilder和UI Debugger等插件)的使用教程。

    2024年02月04日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包