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);
}
}
沖~