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下创建文件夹:
2.右键创建.asset文件:
3.不想找的话可以直接在edit栏找到对应的文件:
4.然后就可以添加对应的新信息:
5.点击对应的数据就可以在弹出的窗口中进行数据的修改了,新窗口就可以免得策划点来点去点没了。。。
窗口样式自己按需排版就完事了
修改数据之后直接就可以实时保存,也写了提示窗口,避免点错带来删库跑路的后果~接下来就是代码了。
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);
}
}
冲~