unity编辑器xml数据库插件
程序和数据分离的意义我就不多说了,大家自己脑补或者百度。在使用unity开发时,数据的调试是非常频繁和重要的。我们可以制作一个简单的编辑器插件,将数据显示在Inspector面板上,并进行编辑操作。这样测试人员就可以非常方便的管理测试数据了。
需求很简单,具体的效果是,能读取资源内的类,将其属性显示在面板上,可以对此进行增删改查的操作。如下图所示(对象组,相当于数据库所有的表。对象,相当于表的所有记录)。
当需要创建一条新记录的时候,先填上主键,然后点击创建按钮。编辑完成后点击插入即可。
xml数据库文件如下图
要实现这个功能,需要的知识是,C#的反射类,unity的编辑器类,数据库。通过反射,自动解析对象,获取对象的成员变量名和值。Unity编辑器类没什么好说的,就是一些组件方法的使用。考虑到跨平台等问题,我选择xml作为存储数据库。编辑器内运行,存储量不大,所以性能方面的问题也就不说了。
好,接下来说一说设计的事。首先是对象的反射。基本类型的存储没有问题,难点是数组的存取有点变态。我找了很多资料也不能自动创建某一类型的数组。既然不能自动,然后就使用半自动判断了,无非是if else之类的,看看这个属性是不是某一类型的数组。
下面是代码。
using System; using System.Reflection; using UnityEngine; using System.Collections.Generic; using System.Runtime.InteropServices; public class ClassAnalyze<T> { private string[] cols; public string[] Cols { get { return cols; } set { cols = value; } } private string[] values; public string[] Values { get { return values; } set { values = value; } } public string ClazzName { get { return tempClazz.GetType().Name; } } private PropertyInfo[] property; private T tempClazz; public static System.Object CreateObject(string objName) { return Assembly.GetExecutingAssembly().CreateInstance(objName); } public T GetClazz(string[] values) {//将数值赋给对象,然后再获取 SetClazz(values); this.Values = values; if (tempClazz == null) { return default(T); } return tempClazz; } public void SetClazz(string[] values) {//将数值赋给对象,然后再获取 if (tempClazz != null && this.Values.Length == values.Length) { this.Values = values; for (int i = 0; i < property.Length; i++) { if (tempClazz.GetType().GetProperty(Cols[i]).PropertyType.IsArray) { var tempArr = StringToArr(tempClazz.GetType().GetProperty(Cols [i]).GetValue(tempClazz, null), values[i].Split(new char[] { ‘|‘ })); property[i].SetValue(tempClazz, tempArr, null); } else { property[i].SetValue(tempClazz, Convert.ChangeType(values[i], property[i].PropertyType), null); } } } } private System.Object StringToArr(System.Object arr, string[] values) { if (arr is string[]) { arr = new string[values.Length]; for (int i = 0; i < values.Length; i++) { (arr as string[])[i] = values[i]; } return (string[])arr; } else if (arr is int[]) { arr = new int[values.Length]; for (int i = 0; i < values.Length; i++) { (arr as int[])[i] = int.Parse(values[i]); } return (int[])arr; } else if (arr is Single[]) { arr = new Single[values.Length]; for (int i = 0; i < values.Length; i++) { (arr as Single[])[i] = Single.Parse(values[i]); } return (Single[])arr; } else if (arr is float[]) { arr = new float[values.Length]; for (int i = 0; i < values.Length; i++) { (arr as float[])[i] = float.Parse(values[i]); } return (float[])arr; } else if (arr is double[]) { arr = new double[values.Length]; for (int i = 0; i < values.Length; i++) { (arr as double[])[i] = double.Parse(values[i]); } return (double[])arr; } else if (arr is long[]) { arr = new long[values.Length]; for (int i = 0; i < values.Length; i++) { (arr as long[])[i] = long.Parse(values[i]); } return (long[])arr; } else if (arr is System.Object[]) { arr = new System.Object[values.Length]; for (int i = 0; i < values.Length; i++) { (arr as System.Object[])[i] = values[i]; } return (System.Object[])arr; } return arr; } private string ArrToString(System.Object arr) { string values = ""; if (arr is System.Object[]) { foreach (var value in arr as System.Object[]) { values += value + "|"; } } else if (arr is string[]) { foreach (var value in arr as string[]) { values += value + "|"; } } else if (arr is int[]) { foreach (var value in arr as int[]) { values += value + "|"; } } else if (arr is Single[]) { foreach (var value in arr as Single[]) { values += value + "|"; } } else if (arr is float[]) { foreach (var value in arr as float[]) { values += value + "|"; } } else if (arr is double[]) { foreach (var value in arr as double[]) { values += value + "|"; } } else if (arr is long[]) { foreach (var value in arr as long[]) { values += value + "|"; } } values = values.TrimEnd(new char[] { ‘|‘ }); return values; } public void AnalyzeClazz() { if (tempClazz != null) { property = tempClazz.GetType().GetProperties(); Cols = new string[property.Length]; Values = new string[property.Length]; for (int i = 0; i < property.Length; i++) { Cols[i] = property[i].Name; string value = ""; if (tempClazz.GetType().GetProperty(Cols[i]).PropertyType.IsArray) { value = ArrToString(tempClazz.GetType().GetProperty(Cols[i]).GetValue(tempClazz, null)); } else { value = Convert.ToString(tempClazz.GetType().GetProperty(Cols[i]).GetValue(tempClazz, null)); } Values[i] = value; } } } private ClassAnalyze() { } public ClassAnalyze(T tempClazz) { this.tempClazz = tempClazz; AnalyzeClazz(); } public void Close() { tempClazz = default(T); Cols = null; Values = null; property = null; } public System.Object GetValue(T t, string colName) { return tempClazz.GetType().GetProperty(colName).GetValue(tempClazz, null); } public void SetValue(T t, string colName,string value) { for (int i = 0; i < property.Length; i++) { if (property[i].Name == colName) { if (property[i].PropertyType.IsArray) { var tempArr = StringToArr(property[i].GetValue(tempClazz, null), value.Split(new char[] { ‘|‘ })); property[i].SetValue(tempClazz, tempArr, null); } else { property[i].SetValue(tempClazz, Convert.ChangeType(value, property[i].PropertyType), null); } break; } } } public override string ToString() { string values = ""; for (int i = 0; i < Cols.Length; i++) { values += Cols[i] + ":{" + this.Values[i] + "} "; } return base.ToString() + ": " + values; } }
反射搞定后就是设计xml数据库了,具体的功能看个人需求。关于操作xml还是很简单的。
using System.Xml; using System.Collections.Generic; using System.IO; using System; using UnityEngine; public abstract class DB { /*public abstract bool CheckTable(string tableName); public abstract bool CheckDB(string dBName); public abstract void CreateTable(string tableName); public abstract void CreateDB(string dBName);*/ public abstract bool Insert(string tableName, string[] cols, string[] values, string key); public abstract bool Update(string tableName, string[] cols, string[] values, string key); public abstract bool UpdateAll(string tableName); public abstract bool Delete(string tableName, string key); public abstract bool DeleteAll(string tableName); public abstract string[] Select(string tableName, string key); public abstract List<string[]> SelectAll(string tableName); public abstract void Connect(string path); public abstract void Close(); public abstract string[] SelectAllObjectsName(string tableName); } public class XmlSql : DB { //public static string values[0] = "values[0]"; private XmlDocument xmlDoc; private string path; private string rootName; public XmlSql() { xmlDoc = new XmlDocument(); } public XmlSql(string path) { xmlDoc = new XmlDocument(); Connect(path); } public override void Connect(string path) { if (xmlDoc == null) xmlDoc = new XmlDocument(); if (!CheckDB(path)) { this.path = path; rootName = path.Substring(path.LastIndexOf("/")+1,path.LastIndexOf(".")- path.LastIndexOf("/")-1); CreateDB(rootName); } else { this.path = path; xmlDoc.Load(this.path); rootName = xmlDoc.LastChild.LocalName; } } public override void Close() { if (xmlDoc != null) xmlDoc.Save(path); GC.Collect(); } public XmlNode CheckTable(string tableName) { XmlNode root = xmlDoc.SelectSingleNode(rootName); if (root.SelectSingleNode(tableName) != null) { return root.SelectSingleNode(tableName); } return CreateTable(root,tableName); } public bool CheckDB(string dBName) { return File.Exists(dBName); } public XmlNode CreateTable(XmlNode root,string tableName) { XmlNode table = xmlDoc.CreateElement(tableName); root.AppendChild(table); xmlDoc.Save(path); return table; } public XmlNode CreateDB(string dBName) { File.CreateText(path).Close(); XmlDeclaration xmlDeclaration = xmlDoc.CreateXmlDeclaration("1.0", "utf-8", null); XmlNode root = xmlDoc.CreateElement(dBName); xmlDoc.AppendChild(xmlDeclaration); xmlDoc.AppendChild(root); xmlDoc.Save(path); return root; } public override bool Insert(string tableName, string[] cols, string[] values,string key) { if (key == null || key == "") key = values[0]; key = key.Replace(" ",""); XmlNode table = CheckTable(tableName); XmlNode obj = table.SelectSingleNode(key);//按照key值确定元素对象 if (obj != null) {//待插入数据已经存在,插入失败 return false; } XmlElement element = xmlDoc.CreateElement(key); for(int i=0;i<cols.Length;i++){ XmlElement e = xmlDoc.CreateElement(cols[i]); e.InnerText = values[i].Replace(" ", ""); element.AppendChild(e); } table.AppendChild(element); xmlDoc.Save(path); return true; } public override bool Update(string tableName, string[] cols, string[] values,string key) { if (key == null || key == "") key = values[0]; key = key.Replace(" ", ""); XmlNode table = CheckTable(tableName); XmlNode obj = table.SelectSingleNode(key);//按照key值确定元素对象 if (obj == null) {//待更新数据不存在,更新失败 return false; } for (int i = 0; i < cols.Length; i++) { obj.SelectSingleNode(cols[i]).InnerText = values[i].Replace(" ", ""); } xmlDoc.Save(path); return true; } public override bool UpdateAll(string tableName) { return false; } public override string[] Select(string tableName, string key) { XmlNode table = CheckTable(tableName); if (key == null || key == "") { if (table.ChildNodes.Count < 1) { return null; } key = table.ChildNodes[0].LocalName; } key = key.Replace(" ", ""); XmlNode obj = table.SelectSingleNode(key);//按照key值确定元素对象 if (obj == null) { return null; } string[] values = new string[obj.ChildNodes.Count]; for (int i = 0; i < values.Length; i++) { values[i] = obj.ChildNodes.Item(i).InnerText.Replace(" ", ""); } return values; } public override string[] SelectAllObjectsName(string tableName) { XmlNode table = CheckTable(tableName); string[] values = new string[table.ChildNodes.Count]; for (int i = 0; i < values.Length; i++) { values[i] = table.ChildNodes[i].LocalName; } return values; } public override List<string[]> SelectAll(string tableName) { XmlNode table = CheckTable(tableName); if (table.ChildNodes.Count == 0) { return null; } List<string[]> elements = new List<string[]>(); for(int i=0;i<table.ChildNodes.Count;i++){ string[] values = new string[table.ChildNodes[i].ChildNodes.Count]; for (int j = 0; j < table.ChildNodes[i].ChildNodes.Count; j++) { values[j] = table.ChildNodes[i].ChildNodes.Item(j).InnerText.Trim(); } elements.Add(values); } return elements; } public override bool Delete(string tableName, string key) { XmlNode table = CheckTable(tableName); if (key == null || key == "") key = table.ChildNodes[0].LocalName; key = key.Replace(" ", ""); XmlNode obj = table.SelectSingleNode(key);//按照key值确定元素对象 if (obj == null) return false; obj.RemoveAll(); table.RemoveChild(obj); xmlDoc.Save(path); return true; } public override bool DeleteAll(string tableName) { XmlNode table = CheckTable(tableName); table.RemoveAll(); xmlDoc.Save(path); return true; } }
接下来就是编辑器的设计。没什么高大上的东西,就是一些读写和调用的方法,以及一些逻辑上的处理。
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Reflection; using System; public class MyInspector : MonoBehaviour { //这个类是一个编辑器类,作用是展示存储的xml数据 //选择对象,然后进行编辑 //如果数据库中有这个数值,将其读取并显示 //如果没有,则按需求创建数据 //public string path; public string defaultKey = "PriKey"; public ObjectMessage objMessage; public string DataPath = "gameData.xml"; public MySql mySql; public bool IsHave = false; public bool IsShow = false; //private System.Object tempObj; public void GetDataBase(string dataPath) { DB db = MyDataBase.GetDataBase().Connection(dataPath); mySql = new MySql(db); } public void SaveMessage(string objName) { if (mySql != null) { System.Object obj = Assembly.GetExecutingAssembly().CreateInstance(objName); ClassAnalyze<System.Object> tempAnalyze = new ClassAnalyze<System.Object>(obj); obj=tempAnalyze.GetClazz(objMessage.values); if (IsHave) { mySql.Update(obj, defaultKey); } else { mySql.Insert(obj, defaultKey); ReadObj(objMessage.NamesOfModel[objMessage.indexOfModel]); } } } public void RemoveMessage(string objName, string key) { if (mySql != null) { System.Object obj = Assembly.GetExecutingAssembly().CreateInstance(objName); ClassAnalyze<System.Object> tempAnalyze = new ClassAnalyze<System.Object>(obj); tempAnalyze.SetValue(obj, defaultKey, key); if (IsHave) { mySql.Delete(obj, defaultKey); IsHave = false; ClearObjMessage(); } } } public void ClearObjMessage() { for (int i = 0; i < objMessage.values.Length; i++) { objMessage.values[i] = ""; } } public void ReadModel() { TextAsset[] tas = Resources.LoadAll<TextAsset>(objMessage.objectPath); objMessage. NamesOfModel = new string[tas.Length]; for (int i = 0; i < tas.Length; i++) { objMessage.NamesOfModel[i] = tas[i].name; } } public void ReadObj(string tableName) { if(mySql!=null) objMessage.NamesOfObj = mySql.SelectAllObjectsName(tableName); } public void CheckData(string objName, string key) { System.Object obj = Assembly.GetExecutingAssembly().CreateInstance(objName); ClassAnalyze<System.Object> tempAnalyze = new ClassAnalyze<System.Object>(obj); tempAnalyze.SetValue(obj, defaultKey, key); objMessage.cols = tempAnalyze.Cols; obj = mySql.Select(obj, defaultKey); IsHave = (obj != null); if (IsHave) { tempAnalyze = new ClassAnalyze<System.Object>(obj); objMessage.values = tempAnalyze.Values; } else { objMessage.values = new string[objMessage.cols.Length]; } } } [Serializable] public class ObjectMessage { public string objectPath = "Model";//对象所处的路径(基于Resources的相对路径) public string[] NamesOfModel; public string[] NamesOfObj; public int indexOfModel = 0; public int indexOfObj = 0; public string PriKey = "PriKey"; public string[] cols; public string[] values; }
下面这个脚本要放在Editor目录下面
using UnityEngine; using System.Collections; using UnityEditor; [CustomEditor(typeof(MyInspector))] public class EditorInspector : Editor { //重写展示面板 //用户选择要加载的对象 //提供一个主键,用户填写主键的值 //默认主键是游戏对象名称 //判断此对象是否已经保存在数据库中 //如果已经存在此主键,则,加载数据到面板 //如果没有此主键,提示用户是否创建 // private SerializedObject TargetObj; private MyInspector MyPlane; private string[] shows= new string[]{"创建","加载"}; private void InitPlane() { TargetObj = new SerializedObject(target);//获取编辑对象目标 MyPlane = target as MyInspector;//转化为编辑对象 CheckData();//检测数据 } private void CheckData() { if (MyPlane.objMessage == null)//检查信息对象是否为空 { MyPlane.objMessage = new ObjectMessage();//对象信息 } if (MyPlane.objMessage.NamesOfModel == null || MyPlane.objMessage.NamesOfModel.Length < 1) {//检查对象数组 MyPlane.ReadModel();//读取对象 } if (MyPlane.objMessage.NamesOfObj == null || MyPlane.objMessage.NamesOfObj.Length < 1) {//检查对象数组 MyPlane.ReadObj(MyPlane.objMessage.NamesOfModel[MyPlane.objMessage.indexOfModel]);//读取对象 } if (MyPlane.objMessage.PriKey == null || MyPlane.objMessage.PriKey == "") {//检查对象主键名称 //设置主键信息 MyPlane.objMessage.PriKey = MyPlane.gameObject.name.Replace(" ", ""); } if (MyPlane.mySql == null) {//检查数据库的连接状态 //获取数据库连接 MyPlane.GetDataBase(MyPlane.DataPath); } } void OnEnable() { InitPlane(); } public override void OnInspectorGUI() { TargetObj.Update();//更新目标数据 //主键值 //CheckData(); int lastModel = MyPlane.objMessage.indexOfModel; //对象选择列表 MyPlane.objMessage.indexOfModel = EditorGUILayout.Popup("对象组", MyPlane.objMessage.indexOfModel, MyPlane.objMessage.NamesOfModel); if (lastModel != MyPlane.objMessage.indexOfModel) { //当改变对象时,更新主键值 //更新主键值集合 MyPlane.ReadObj(MyPlane.objMessage.NamesOfModel[MyPlane.objMessage.indexOfModel]); } int lastobj = MyPlane.objMessage.indexOfObj; if (MyPlane.objMessage.NamesOfObj.Length > 0) { MyPlane.objMessage.indexOfObj = EditorGUILayout.Popup("对象", MyPlane.objMessage.indexOfObj, MyPlane.objMessage.NamesOfObj); } if (lastobj != MyPlane.objMessage.indexOfObj || lastModel != MyPlane.objMessage.indexOfModel) { //主键值集合下标改变时 //更新主键值 if (MyPlane.objMessage.NamesOfObj.Length>0) MyPlane.objMessage.PriKey = MyPlane.objMessage.NamesOfObj[MyPlane.objMessage.indexOfObj]; } string lastKey = MyPlane.objMessage.PriKey; //显示主键文本框 MyPlane.objMessage.PriKey = EditorGUILayout.TextField("主键", MyPlane.objMessage.PriKey); //路径 string lastPath = MyPlane.DataPath; MyPlane.DataPath = EditorGUILayout.TextField("数据路径",MyPlane.DataPath); //判断选择的对象列表 //更新对象信息 if (MyPlane.objMessage.indexOfModel != lastModel || lastKey != MyPlane.objMessage.PriKey || lastPath != MyPlane.DataPath || lastobj != MyPlane.objMessage.indexOfObj)//改变了一些数据时重新读取数据 { MyPlane.IsHave = false;//标注数据改动 CheckData();//读取数据前保证系统参数无误,以及各个对象正确加载 MyPlane.CheckData(MyPlane.objMessage.NamesOfModel[MyPlane.objMessage.indexOfModel], MyPlane.objMessage.PriKey); } //当存在时直接加载 //当不存在时,点击创建时加载 bool _show = MyPlane.IsShow;//记录上一次的状态 if (MyPlane.IsHave || (MyPlane.IsShow = EditorGUILayout.Foldout(MyPlane.IsShow, shows[MyPlane.IsShow ? 1 : 0]))) { if (!_show && !MyPlane.IsHave)//数据不存在而且点击了创建的时候 { //仅执行一次,保证数据不被一直刷新而导致的无法读写 //当数据不存在,而且点击创建时 清除信息 MyPlane.ClearObjMessage();//清除数据的缓存 } } //也要只进行一次的读写,保证可以进行修改操作 if (MyPlane.IsHave || MyPlane.IsShow)//当数据存在或者点击创建时加载数据 { for (int i = 0; i < MyPlane.objMessage.cols.Length; i++) { if (MyPlane.defaultKey == MyPlane.objMessage.cols[i]) { MyPlane.objMessage.values[i] = MyPlane.objMessage.PriKey; continue; } MyPlane.objMessage.values[i] = EditorGUILayout.TextField(MyPlane.objMessage.cols[i], MyPlane.objMessage.values[i]); } if (GUILayout.Button("Save")) { MyPlane.SaveMessage(MyPlane.objMessage.NamesOfModel[MyPlane.objMessage.indexOfModel]); } if (MyPlane.IsHave&& GUILayout.Button("Remove")) { MyPlane.RemoveMessage(MyPlane.objMessage.NamesOfModel[MyPlane.objMessage.indexOfModel], MyPlane.objMessage.PriKey); MyPlane.ReadObj(MyPlane.objMessage.NamesOfModel[MyPlane.objMessage.indexOfModel]); } } TargetObj.ApplyModifiedProperties(); } void show(System.Object message) { Debug.Log(message); } }
部分源码如上。省去了一些源码和路径加载等东西。如果想要完整版,可以联系我。
本文地址:http://www.cnblogs.com/jqg-aliang/p/4767026.html
时间: 2024-10-12 21:49:14