草庐IT

Unity自定义属性面板Inspector面板和userData数据保存

Thinbug 2023-09-08 原文

最近写一个小的编辑器功能,比较简单和入门,这里做一个简单教程。

效果图



因为最后可能需要重新编辑生成好的预设文件,要返回编辑,所以一些数据要保存下来,所以利用了userData保存数据。
下面的图是保存预设后的meta文件,可以看到userData数据。

实现方式

本来这个在业务中,大概是通过3个图片生成一个地图预设,最后把一些关键输入保存到meta文件的userData数据中。文章中删除了业务代码,只有自定义面板部分,代码不是全面的代码。

这里只是讲解下面板是如何摆放组件并处理。

首先我们创建一个MazeEditor.cs脚本,主要用来记录我们填写的数据。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//UTF8 说明
public class MazeEditor : MonoBehaviour
{
    [HideInInspector]
    public GameObject OriginObject; //起点
    [HideInInspector]
    public GameObject StarObject; //星星

    [HideInInspector]
    public string mapName;

    [HideInInspector]
    public Object mapPrefab;

    [HideInInspector]
    [TooltipAttribute("地板图")]
    public Sprite plane;

    [HideInInspector]
    [TooltipAttribute("高墙")]
    public Sprite wall;

    [HideInInspector]
    [TooltipAttribute("底障碍")]
    public Sprite slow_wall;

    [HideInInspector]
    public GameObject origin;

    [HideInInspector]
    public List<GameObject> stars;

    [HideInInspector]
    public bool mapCreated;

    [HideInInspector]
    public GameObject mapRoot;
    [HideInInspector]
    public GameObject starRoot;

    //某些星星被删除掉了。视图里也要删除
    public void StarDelete()
    {
        
    }

    public void Save()
    {
        
    }

    public void Clear()
    {
        mapCreated = false;
        stars.Clear();
        if (starRoot != null)
            GameObject.DestroyImmediate(starRoot.gameObject);
        if (mapRoot != null)
            GameObject.DestroyImmediate(mapRoot.gameObject);

        starRoot = null;
        mapRoot = null;
        plane = null;
        wall = null;
        slow_wall = null;
        mapName = "";
    }
}

然后是CreateMapEditor脚本,需要放入Editor文件夹。


using System.Collections.Generic;
using Unity.Plastic.Newtonsoft.Json;
using UnityEditor;
using UnityEngine;

//UTF8 说明

[CustomEditor(typeof(MazeEditor))]
public class CreateMapEditor : Editor
{
    int layerBuild;
    SerializedProperty m_Star;

    bool showsetting;
    bool showMenu1;
    bool showMenu2;
    void OnEnable()
    {
        // 从GameObject脚本中获取对象以显示在检查器中
        m_Star = serializedObject.FindProperty("stars");
    }
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI(); //显示
        MazeEditor myScript = (MazeEditor)target;
        if (!myScript.mapCreated)
        {
            showsetting = EditorGUILayout.Foldout(showsetting, "Flag");
            if (showsetting)
            {
                EditorGUILayout.LabelField("标记位置的对象,请勿修改。");

                myScript.OriginObject = (GameObject)EditorGUILayout.ObjectField("Origin (起点标记)", myScript.OriginObject, typeof(GameObject), true);
                myScript.StarObject = (GameObject)EditorGUILayout.ObjectField("Star (星星标记)", myScript.StarObject, typeof(GameObject), true);
                EditorGUILayout.Space(10);
            }
        }
        showMenu1 = EditorGUILayout.Foldout(showMenu1, "MapSetting");

        if (showMenu1)
        {


            myScript.mapName = EditorGUILayout.TextField("MapName (地图名)", myScript.mapName);



            myScript.plane = (Sprite)EditorGUILayout.ObjectField("Plane (地板图)", myScript.plane, typeof(Sprite), false);
            myScript.wall = (Sprite)EditorGUILayout.ObjectField("Wall (高墙)", myScript.wall, typeof(Sprite), false);
            myScript.slow_wall = (Sprite)EditorGUILayout.ObjectField("Slow Wall (减速物)", myScript.slow_wall, typeof(Sprite), false);

            if (myScript.mapRoot != null)
            {
                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(m_Star, new GUIContent("Stars (星星)"));
                if (EditorGUI.EndChangeCheck())
                {
                    myScript.StarDelete();
                }
            }


            //绘制按钮
            EditorGUILayout.BeginHorizontal();


            if (GUILayout.Button("创建地图"))
            {
                myScript.mapCreated = true;
                Create();
            }

            if (myScript.mapCreated)
            {
                if (GUILayout.Button("创建星星"))
                {
                    
                }
                if (GUILayout.Button("保存"))
                {
                    SaveMap();
                    Clear();
                }
                if (GUILayout.Button("清空"))
                {
                    Clear();
                }
            }
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.Space(10);
        }


        showMenu2 = EditorGUILayout.Foldout(showMenu2, "Edit");
        if (showMenu2)
        {
            myScript.mapPrefab = (Object)EditorGUILayout.ObjectField("MapPrefab (地图预设)", myScript.mapPrefab, typeof(Object), false);
            if (GUILayout.Button("修改预设"))
            {
                Edit();
            }
        }
    }

    public void Create()
    {
        MazeEditor myScript = (MazeEditor)target;
        if (myScript.plane == null)
        {
            Debug.Log("地面图片还没有。");
            return;
        }
		//这里省略了业务代码。
    }


    public void Clear()
    {
        MazeEditor myScript = (MazeEditor)target;
        myScript.Clear();
    }

    public void SaveMap()
    {
        MazeEditor myScript = (MazeEditor)target;
        if (myScript.mapName.Length == 0)
        {
            Debug.LogWarning("请输入保存的名称。");
            return;
        }
        string pathprefab = "Assets/ResAll/maps/" + myScript.mapName + ".prefab";
        string bundlename = "reboot/maps/" + myScript.mapName;

        myScript.mapRoot.name = myScript.mapName;
        bool ok;
        PrefabUtility.SaveAsPrefabAsset(myScript.mapRoot, pathprefab, out ok);
        if (!ok)
        {
            Debug.LogError("保存失败:" + pathprefab);
            return;
        }
        AssetImporter assetImporter = AssetImporter.GetAtPath(pathprefab);  //得到Asset
        assetImporter.assetBundleName = bundlename;
        assetImporter.assetBundleVariant = "map";

        MapData md = new MapData();	//一个保存Json的数据结构。
        md.mapName = myScript.mapName;
        md.spPlane = AssetDatabase.GetAssetPath(myScript.plane);
        md.spWall = AssetDatabase.GetAssetPath(myScript.wall);
        md.spSlow = AssetDatabase.GetAssetPath(myScript.slow_wall);
        md.origin = myScript.origin.transform.position;
        md.stars = new List<Vector3>();
        for (int i = 0; i < myScript.stars.Count; i++)
        {
            md.stars.Add((Vector3)myScript.stars[i].transform.position);
        }

        var settings = new Newtonsoft.Json.JsonSerializerSettings();
        settings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        string str = Newtonsoft.Json.JsonConvert.SerializeObject(md, settings);
        assetImporter.userData = str;
        assetImporter.SaveAndReimport();
        AssetDatabase.Refresh();

        Debug.Log("保存成功:" + pathprefab);
        myScript.Save();
    }

    void Edit()
    {
        MazeEditor myScript = (MazeEditor)target;
        Object prefab = myScript.mapPrefab;
        Clear();    //必须放在获取路径的后面

        string pathprefab = AssetDatabase.GetAssetPath(prefab);

        AssetImporter assetImporter = AssetImporter.GetAtPath(pathprefab);  //得到Asset
        string str = assetImporter.userData;

        MapData md = Newtonsoft.Json.JsonConvert.DeserializeObject<MapData>(str);
        myScript.mapName = md.mapName;
        myScript.plane = (Sprite)AssetDatabase.LoadAssetAtPath(md.spPlane, typeof(Sprite));
        myScript.wall = (Sprite)AssetDatabase.LoadAssetAtPath(md.spWall, typeof(Sprite));
        myScript.slow_wall = (Sprite)AssetDatabase.LoadAssetAtPath(md.spSlow, typeof(Sprite));

        Debug.Log("预设读取完毕:" + pathprefab);
    }
}

脚本简要说明

CustomEditor

首先要声明[CustomEditor(typeof(MazeEditor))],和继承自Editor,这样才能和Maze脚本联系起来。

[CustomEditor(typeof(MazeEditor))]
public class CreateMapEditor : Editor

OnInspectorGUI

核心就是要覆写OnInspectorGUI函数。
base.OnInspectorGUI();表示显示MazeEditor.cs的布局显示。

获得脚本对象

MazeEditor myScript = (MazeEditor)target;
通过这个可以获得MazeEditor的方法和属性。

箭头收缩

showsetting = EditorGUILayout.Foldout(showsetting, “Flag”);
这个是一个箭头收放容器,点击箭头可以显示和隐藏。

LabelField

EditorGUILayout.LabelField
是文本显示。

ObjectField

myScript.OriginObject = (GameObject)EditorGUILayout.ObjectField(“Origin (起点标记)”, myScript.OriginObject, typeof(GameObject), true);
这个就是可以放入一个对象,把对象给脚本的的变量,最后的是类型,true表示可以把Hierarchy里的对象拖入,否则只能拖入Assets文件夹里的资源。

后面的图片可以是:
myScript.plane = (Sprite)EditorGUILayout.ObjectField(“Plane (地板图)”, myScript.plane, typeof(Sprite), false);
因为类型是Sprite我们可以直接看到图片选择框。

BeginChangeCheck和EndChangeCheck

				EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(m_Star, new GUIContent("Stars (星星)"));
                if (EditorGUI.EndChangeCheck())
                {
                    myScript.StarDelete();
                }

这两个是用于检测数据变化的,当调整了面板,可以去做一切其他事情。

BeginHorizontal

EditorGUILayout.BeginHorizontal();
这个是布局,我们可以看到按钮可以横向摆放,最后结束用EditorGUILayout.EndHorizontal();

Space

EditorGUILayout.Space(10);
是间距。

userData

userData数据可以保存在meta文件中,它可以通过assetImporter.userData = str;的方式保存,因为是字符串,我这里用json转化成string的方式保存方便使用。

结束

这就是一个简单的Inspector的自定义面板。

还有很多可以使用的组件,可以参考官方文档

有关Unity自定义属性面板Inspector面板和userData数据保存的更多相关文章

  1. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  2. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  3. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  4. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  5. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

  6. ruby - Nokogiri 剥离所有属性 - 2

    我有这个html标记:我想得到这个:我如何使用Nokogiri做到这一点? 最佳答案 require'nokogiri'doc=Nokogiri::HTML('')您可以通过xpath删除所有属性:doc.xpath('//@*').remove或者,如果您需要做一些更复杂的事情,有时使用以下方法遍历所有元素会更容易:doc.traversedo|node|node.keys.eachdo|attribute|node.deleteattributeendend 关于ruby-Nokog

  7. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

  8. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

    我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

  9. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  10. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

随机推荐