2021.3.12更新tips:今天发现使用LitJson存字典的key值只能使用string型,想起来这边我还没有试过不同key类型,就测试了一波,发现这边就算把key值设为string,免去内部序列化的类型转化,还是不能被写入,看来序列化这一趴的坑点挺多的,mark一下准备找时间深入研究一下序列化方面的源码。
2021.3.4更新,之前研究了忘记同步上来了,上结论,不可以用dictionary以及list<list<>>这种嵌套的结构,不然不会被unity的setdirty方法所保存,但是可以使用系列化的类的嵌套,类中有单层的List<>是可以被序列化的。
2021.1.25更新,加入退出窗口时保存功能。

先说一下这个工具可以干神马呢~~~它可以让策划愉快的对游戏数据边调试边修改,方便他们调整数据用滴。也可以作为工程的自定义数据的存档,它会将数据存到.asset文件里面。

首先是自定义数据的数据结构,这里全部都可以自定义,但是类型最好用基础类型,数组或者list,因为本人踩了Dictionary的坑,貌似是无法识别到dictionary的改变导致不能储存,不知道有木有大神指点迷津= =

然后是最清晰明了的上图(如果偷懒不想改的话就按这个位置好了),灵活修改的部分已经在代码中标记出来了,直接删掉写入自己需要的排版和数据就完事了:

1.在resources下创建文件夹:

unity引擎游戏存档修改 unity游戏存档修改器_开发工具


2.右键创建.asset文件:

unity引擎游戏存档修改 unity游戏存档修改器_unity_02


3.不想找的话可以直接在edit栏找到对应的文件:

unity引擎游戏存档修改 unity游戏存档修改器_储存器_03


4.然后就可以添加对应的新信息:

unity引擎游戏存档修改 unity游戏存档修改器_unity_04


5.点击对应的数据就可以在弹出的窗口中进行数据的修改了,新窗口就可以免得策划点来点去点没了。。。

unity引擎游戏存档修改 unity游戏存档修改器_开发工具_05


unity引擎游戏存档修改 unity游戏存档修改器_unity引擎游戏存档修改_06

窗口样式自己按需排版就完事了
修改数据之后直接就可以实时保存,也写了提示窗口,避免点错带来删库跑路的后果~接下来就是代码了。
LevelData:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "GameDataAsset", menuName = "Creat GameData Asset")]
public class LevelData : ScriptableObject
{
    [Header("关卡字典")]
    [HideInInspector]
    [SerializeField] public List<InsideData> preinstallLevels;
    /// <summary>
    /// 找数据
    /// </summary>
    /// <param name="id">关卡id</param>
    /// <param name="data">数据</param>
    /// <returns></returns>
    public static bool FindData(int id,out InsideData data)
    {
        LevelData datas = Resources.Load("LevelDataAssest/LevelData") as LevelData;
        if (datas.preinstallLevels == null || datas.preinstallLevels.Count < 1)
        {
            //Debug.LogError("没数据");
            data = null;
            return false;
        }
        foreach (var v in datas.preinstallLevels)
        {
            if(v.levelID == id)
            {
                data = v;
                return true;
            }
        }
        //Debug.LogError("没这个ID的数据");
        data = null;
        return false;
    }

}
/// <summary>
/// 关卡信息,根据需要修改
/// ps.注意不可以用dictionary以及list<list<>>这种嵌套的结构,不然不会被unity的setdirty方法所保存,可以用序列化过的类嵌套
/// </summary>
[System.Serializable]
public class InsideData {
    [SerializeField] public int levelID;//章节ID
    //这里写入需要存的数据结构
}

然后是放在Editor下的LevelDataEditor:

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
[CustomEditor(typeof(LevelData))]
public class LevelDataEditor : Editor
{
    public LevelData data;
    public LevelDataWindow addWindow;
    SerializedProperty dataProperty;
    ReorderableList nowList;
    private void OnEnable()
    {
        data = target as LevelData;
        if (data.preinstallLevels == null || data.preinstallLevels.Count < 1)
        {
            //Debug.Log("没数据");
        }
        dataProperty = serializedObject.FindProperty("preinstallLevels");
        nowList = new ReorderableList(serializedObject, dataProperty, false, true, true, true);
        nowList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
        {
            var element = dataProperty.GetArrayElementAtIndex(index);
            rect.height -= 4;
            rect.y += 2;
            GUIContent content = new GUIContent();
            content.text = "Level" + (index + 1);
            EditorGUI.PropertyField(rect, element, content);
        };
        nowList.onAddCallback = (ReorderableList l) =>
        {
            if (addWindow != null)
                addWindow.Close();
            addWindow = (LevelDataWindow)EditorWindow.GetWindow(typeof(LevelDataWindow), true, "LevelDataWindow");
            addWindow.minSize = new Vector2(200, 800);
            addWindow.Init(this);
            addWindow.Show();
        };

        nowList.onRemoveCallback = (ReorderableList l) =>
        {
            if (addWindow != null)
                addWindow.Close();
            if (EditorUtility.DisplayDialog("删除关卡信息", "你想删除选中的关卡信息吗?", "是", "否"))
            {
                data.preinstallLevels.RemoveAt(l.index);
            }
        };
        nowList.drawHeaderCallback = (rect) =>
            EditorGUI.LabelField(rect, "关卡数据");
        nowList.onSelectCallback = (ReorderableList l) =>
        {
            if (addWindow != null)
                addWindow.Close();
            addWindow = (LevelDataWindow)EditorWindow.GetWindow(typeof(LevelDataWindow), true, "LevelDataWindow");
            addWindow.minSize = new Vector2(200, 800);
            addWindow.Init(this, data.preinstallLevels[l.index]);
            addWindow.Show();
        };
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        if (GUILayout.Button("新增关卡信息", GUILayout.Height(25)))
        {
            if (addWindow != null)
                addWindow.Close();
            addWindow = (LevelDataWindow)EditorWindow.GetWindow(typeof(LevelDataWindow), true, "LevelDataWindow");
            addWindow.minSize = new Vector2(200, 800);
            addWindow.Init(this);
            addWindow.Show();
        }
        if (data.preinstallLevels == null || data.preinstallLevels.Count < 1)
        {
            EditorGUILayout.HelpBox("没有数据", MessageType.Warning);
            return;
        }
        nowList.DoLayoutList();
        if (GUILayout.Button("清除所有信息", GUILayout.Height(25)))
        {
            if (EditorUtility.DisplayDialog("删除关卡信息", "你想删除所有的关卡信息吗?", "是", "否"))
            {
                data.preinstallLevels.Clear();
                Save();
            }           
        }
        serializedObject.Update();
        if (GUI.changed)
        {
            Save();
        }
    }
    public void Save()
    {
        EditorUtility.SetDirty(target);
        serializedObject.ApplyModifiedProperties();
        EditorUtility.SetDirty(data);
    }
    [MenuItem("Edit/Level data")]
    private static void SelectLevelData()
    {
        Selection.activeObject = Resources.Load("LevelDataAssest/LevelData") as LevelData;
    }
}

以及修改数据的弹窗:LevelDataWindow

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

public class LevelDataWindow : EditorWindow 
{
    private LevelData datas;//存档数据集
    private InsideData data;//新加的数据
    private LevelDataEditor now;
    private void OnEnable()
    {
        datas = Resources.Load("LevelDataAssest/LevelData") as LevelData;
        minSize = new Vector2(0, 700f);        
    }
    private void OnGUI()
    {
        //这里加入GUI排版以及数据赋值
        if (GUI.changed)
        {
            if (IsAlreadyHaveID(data.levelID))
            {
                for(int i = 0; i < datas.preinstallLevels.Count; i++)
                {
                    if (datas.preinstallLevels[i].levelID == data.levelID)
                    {
                        datas.preinstallLevels[i] = data;
                    }
                }
            }
            else
            {
                datas.preinstallLevels.Add(data);
            }
            now.Save();
            Undo.RecordObject(datas, "保存数据");
            EditorUtility.SetDirty(datas);
        }
    }
    private void OnDisable()
    {
        now.Save();
        Undo.RecordObject(datas, "保存数据");
        EditorUtility.SetDirty(datas);
        AssetDatabase.SaveAssets();
    }
    public void Init(LevelDataEditor now,InsideData data = null)
    {
        this.now = now;
        if (data != null)
        {
            this.data = data;
        }      
    }
    public bool IsAlreadyHaveID(int id)
    {
        foreach(var v in datas.preinstallLevels)
        {
            if(id == v.levelID)
            {
                return true;
            }
        }
        return false;
    }
}

最后再附上一个实用滴样式查找工具,同样是放在Editor下的GUIStyleViewer:

using UnityEngine;
using UnityEditor;

public class GUIStyleViewer : EditorWindow {
    private Vector2 scrollVector2 = Vector2.zero;
    private string search = "";

    [MenuItem("Tools/GUIStyle查看器")]
    public static void InitWindow()
    {
        EditorWindow.GetWindow(typeof(GUIStyleViewer));
    }

    void OnGUI()
    {
        GUILayout.BeginHorizontal("HelpBox");
        GUILayout.Space(30);
        search = EditorGUILayout.TextField("", search, "SearchTextField", GUILayout.MaxWidth(position.x / 3));
        GUILayout.Label("", "SearchCancelButtonEmpty");
        GUILayout.EndHorizontal();
        scrollVector2 = GUILayout.BeginScrollView(scrollVector2);
        foreach (GUIStyle style in GUI.skin.customStyles)
        {
            if (style.name.ToLower().Contains(search.ToLower()))
            {
                DrawStyleItem(style);
            }
        }
        GUILayout.EndScrollView();
    }

    void DrawStyleItem(GUIStyle style)
    {
        GUILayout.BeginHorizontal("box");
        GUILayout.Space(40);
        EditorGUILayout.SelectableLabel(style.name);
        GUILayout.FlexibleSpace();
        EditorGUILayout.SelectableLabel(style.name, style);
        GUILayout.Space(40);
        EditorGUILayout.SelectableLabel("", style, GUILayout.Height(40), GUILayout.Width(40));
        GUILayout.Space(50);
        if (GUILayout.Button("复制GUIStyle名字"))
        {
            TextEditor textEditor = new TextEditor();
            textEditor.text = style.name;
            textEditor.OnFocus();
            textEditor.Copy();
        }
        GUILayout.EndHorizontal();
        GUILayout.Space(10);
    }
}

冲~