XML生成编辑器工具类
- 初步创建编辑器自定义窗口
- 添加窗口内部UI
- 添加创建XML文件功能
- 创建Asset数据用于保存XML数据
- 自行更改Asset数据保存路径和名称
- 自动填入选取的Asset数据
- 添加勾选框决定是否要生成Asset数据
- 添加输入警示
- 添加数据为空时创建文件的警告窗口
- 主要工具类完整代码
初步创建编辑器自定义窗口
新建CustomTooL.cs脚本,并编写以下代码,因为我们要定义自己的编辑器窗口,因此脚本要放在Editor文件夹下,同时要继承EditorWindow类
public class CustomTool : EditorWindow
{
//XML文件名称
public string XmlName;
//根节点名称
public string root;
CustomTool()
{
this.titleContent = new GUIContent("XmlTool");
}
[MenuItem("CustomTool/XML")]
static void showWindow()
{
EditorWindow.GetWindow(typeof(CustomTool));
}
void OnGUI()
{
EditorGUILayout.BeginVertical();
ScrollPos = EditorGUILayout.BeginScrollView(ScrollPos);
//绘制标题
GUILayout.Space(10);
GUILayout.Label("Create/Update Xml");
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
}
这样在编辑器顶部工具栏中就会出现我们自己定义的编辑器窗口
打开窗口即可看到如下界面
添加窗口内部UI
首先先绘制两个文字输入框以及一个按钮,分别是记录XML文件名,XML文件根节点名称以及创建按钮,在OnGUI方法中编写如下代码
void OnGUI()
{
EditorGUILayout.BeginVertical();
ScrollPos = EditorGUILayout.BeginScrollView(ScrollPos);
//绘制标题
GUILayout.Space(10);
GUILayout.Label("Create/Update Xml");
//xml的名字
GUILayout.Space(10);
XmlName = EditorGUILayout.TextField("XML Name", XmlName);
//根节点名称
GUILayout.Space(10);
root = EditorGUILayout.TextField("Root Name", root);
//按钮
if (GUILayout.Button("Create/Update XML"))
{
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
保存后再次打开窗口就会发现窗口内已经绘制出了我们所需要的UI
添加创建XML文件功能
声明子节点的结构体
/// <summary>
/// 子节点结构体
/// </summary>
[System.Serializable]
public struct elmNew
{
//子节点名称
public string childRootName;
//id
public string attributeId;
//Name
public string attributeName;
//下层子节点结构体
public elmChildNode[] ElmChildNode;
}
/// <summary>
/// 子节点下层节点结构体
/// </summary>
[System.Serializable]
public struct elmChildNode
{
public string name;
public string innerText;
}
声明对应变量和序列化属性
//子节点
[SerializeField]
private List<elmNew> child = new List<elmNew>();
//序列化属性
protected SerializedProperty _assetLstProperty;
//序列化对象
protected SerializedObject _serializedObject;
void OnEnable()
{
//序列化子节点结构体
_serializedObject = new SerializedObject(this);
_assetLstProperty = _serializedObject.FindProperty("child");
}
编写CreateXML方法用于创建XML文件
/// <summary>
/// 创建Xml文件
/// </summary>
/// <param name="fileName"></param>
public void CreateXml(string fileName)
{
string filePath = Application.streamingAssetsPath + "/" + fileName + ".xml";
//创建Xml文件实例
XmlDocument xmlDoc = new XmlDocument();
//创建root节点
XmlElement root = xmlDoc.CreateElement(this.root);
for (int i = 0; i < child.Count; i++)
{
//继续创建子节点
XmlElement elmNew = xmlDoc.CreateElement(child[i].childRootName);
//配置节点的两个属性
elmNew.SetAttribute("id", child[i].attributeId);
elmNew.SetAttribute("name", child[i].attributeName);
//继续创建下一层节点
for (int j = 0; j < child[i].ElmChildNode.Length; j++)
{
XmlElement childnode = xmlDoc.CreateElement(child[i].ElmChildNode[j].name);
//设置节点数据
childnode.InnerText = child[i].ElmChildNode[j].innerText;
elmNew.AppendChild(childnode);
}
//把节点一层一层添加到XmlDoc里---------------->>>注意先后顺序
root.AppendChild(elmNew);
}
xmlDoc.AppendChild(root);
xmlDoc.Save(filePath);
Debug.Log("CreateXml OK");
}
OnGUI方法内绘制子节点输入框
//根节点名称
GUILayout.Space(10);
root = EditorGUILayout.TextField("Root Name", root);
//子节点信息
GUILayout.Space(10);
_serializedObject.Update();
//检查是否有修改
EditorGUI.BeginChangeCheck();
//显示
EditorGUILayout.PropertyField(_assetLstProperty, true);
//当检查结束时应用最新修改数据
if (EditorGUI.EndChangeCheck())
{
_serializedObject.ApplyModifiedProperties();
}
添加点击创建按钮后执行的方法
if (GUILayout.Button("Create/Update XML"))
{
CreateXml(XmlName);
}
随后打开窗口即可看到
输入数据并点击按钮生成文件
打开XML文件查看数据
创建Asset数据用于保存XML数据
新建脚本XmlAsset.cs,继承自ScriptableObject,同样放置在Editor文件夹下
public class XmlAsset : ScriptableObject
{
public string XmlName;
public string root;
public List<elmNew> child = new List<elmNew>();
}
回到CustomTool脚本,声明变量
//asset数据类
public XmlAsset asset;
声明保存Asset数据方法,注意AssetDatabase.CreateAsset()方法的路径参数要填写相对路径
/// <summary>
/// 保存Xml数据为Asset
/// </summary>
public void SaveAsset()
{
XmlAsset xmlAsset = ScriptableObject.CreateInstance<XmlAsset>();
xmlAsset.XmlName = XmlName;
xmlAsset.root = root;
xmlAsset.child = child;
AssetDatabase.CreateAsset(xmlAsset, "Assets/test.asset");
}
调用SaveAsset方法
if (GUILayout.Button("Create/Update XML"))
{
CreateXml(XmlName);
SaveAsset();
}
运行测试
自行更改Asset数据保存路径和名称
这种保存数据需要能够自己决定保存的路径,因此在创建数据文件时需要弹出窗口选择保存路径
首先新建两个类,用于调用Window打开窗口的系统函数
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class OpenFileName
{
public int structSize = 0;
public IntPtr dlgOwner = IntPtr.Zero;
public IntPtr instance = IntPtr.Zero;
public String filter = null;
public String customFilter = null;
public int maxCustFilter = 0;
public int filterIndex = 0;
public String file = null;
public int maxFile = 0;
public String fileTitle = null;
public int maxFileTitle = 0;
public String initialDir = null;
public String title = null;
public int flags = 0;
public short fileOffset = 0;
public short fileExtension = 0;
public String defExt = null;
public IntPtr custData = IntPtr.Zero;
public IntPtr hook = IntPtr.Zero;
public String templateName = null;
public IntPtr reservedPtr = IntPtr.Zero;
public int reservedInt = 0;
public int flagsEx = 0;
}
public class LocalDialog
{
//链接指定系统函数 打开文件对话框
[DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
public static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
public static bool GetOFN([In, Out] OpenFileName ofn)
{
return GetOpenFileName(ofn);
}
//链接指定系统函数 另存为对话框
[DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
public static extern bool GetSaveFileName([In, Out] OpenFileName ofn);
public static bool GetSFN([In, Out] OpenFileName ofn)
{
return GetSaveFileName(ofn);
}
}
回到CustomTool脚本,声明路径变量
public string assetSavePath;
声明OpenSaveWindow方法和GetRelativePath方法
/// <summary>
/// 绝对路径转相对路径
/// </summary>
/// <param name="filespec"></param>
/// <param name="folder"></param>
/// <returns></returns>
public static string GetRelativePath(string filespec, string folder)
{
const string directorySeparatorChar = "\\";
Uri pathUri = new Uri(filespec);
if (!folder.EndsWith(directorySeparatorChar))
{
folder += directorySeparatorChar;
}
Uri folderUri = new Uri(folder);
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace("/", directorySeparatorChar));
}
/// <summary>
/// 开启Window保存文件窗口
/// </summary>
/// <param name="title"></param>
/// <param name="_filter"></param>
void OpenSaveWindow(string title, string _filter)
{
OpenFileName openFileName = new OpenFileName();
openFileName.structSize = Marshal.SizeOf(openFileName);
openFileName.filter = _filter;
openFileName.file = new string(new char[256]);
openFileName.maxFile = openFileName.file.Length;
openFileName.fileTitle = new string(new char[64]);
openFileName.maxFileTitle = openFileName.fileTitle.Length;
openFileName.initialDir = Application.streamingAssetsPath.Replace('/', '\\');//默认路径
openFileName.title = title;
openFileName.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008;
if (LocalDialog.GetSaveFileName(openFileName))
{
assetSavePath = GetRelativePath(openFileName.file, Application.dataPath);
}
}
修改SaveAsset方法
/// <summary>
/// 保存Xml数据为Asset
/// </summary>
public void SaveAsset()
{
XmlAsset xmlAsset = ScriptableObject.CreateInstance<XmlAsset>();
OpenSaveWindow("保存Xml数据", "asset文件(*.asset)\0*.asset");
xmlAsset.XmlName = XmlName;
xmlAsset.root = root;
xmlAsset.child = child;
AssetDatabase.CreateAsset(xmlAsset, "Assets/" + assetSavePath + ".asset");
}
运行测试
自动填入选取的Asset数据
声明变量
//asset数据类
public XmlAsset asset;
在OnGUI内添加
GUILayout.Label("Create/Update Xml");
//读取asset数据
asset = EditorGUILayout.ObjectField("选取Xml数据", asset, typeof(XmlAsset), true) as XmlAsset;
if (asset != null)
{
XmlName = asset.XmlName;
root = asset.root;
child = asset.child;
}
//绘制文本
再次打开窗口测试
可以看到第一行多出来一个选择资源的选项框
选取数据后
添加勾选框决定是否要生成Asset数据
声明变量
private bool isSaveAsset;
在OnGUI内添加语句,并修改按钮判断
GUILayout.Space(10);
isSaveAsset = GUILayout.Toggle(isSaveAsset, "是否保存数据为Asset资源");
if (GUILayout.Button("Create/Update XML"))
{
CreateXml(XmlName);
if (isSaveAsset)
{
SaveAsset();
}
}
打开窗口测试就会发现按钮上一行多了个勾选项,当勾选时生成XML文件则会进行保存Asset数据操作,反之则只会生成XML文件
添加输入警示
首先要判断是否有数据输入,在子节点的结构体里添加属性,用于判断是否有输入数据
/// <summary>
/// 子节点结构体
/// </summary>
[System.Serializable]
public struct elmNew
{
public string childRootName;
public string attributeId;
public string attributeName;
public elmChildNode[] ElmChildNode;
/// <summary>
/// 是否有填入数据
/// </summary>
public bool HasContent
{
get
{
for (int i = 0; i < ElmChildNode.Length; i++)
{
return childRootName.Length > 0 && attributeId.Length > 0 && attributeName.Length > 0 &&
ElmChildNode.Length > 0 && ElmChildNode[i].HasContent;
}
return childRootName.Length > 0 && attributeId.Length > 0 && attributeName.Length > 0 &&
ElmChildNode.Length > 0;
}
}
}
/// <summary>
/// 子节点下层节点结构体
/// </summary>
[System.Serializable]
public struct elmChildNode
{
public string name;
public string innerText;
/// <summary>
/// 是否有填入数据
/// </summary>
public bool HasContent
{
get { return name.Length > 0 && innerText.Length > 0; }
}
}
声明剩余数据变量的判断方法
/// <summary>
/// 判断XML名称是否为空
/// </summary>
/// <returns></returns>
bool HasXmlNameContent()
{
return XmlName != null && XmlName.Length > 0;
}
/// <summary>
/// 判断根节点是否为空
/// </summary>
/// <returns></returns>
bool HasRootNameContent()
{
return root != null && root.Length > 0;
}
/// <summary>
/// 判断子节点是否填入数据
/// </summary>
/// <returns></returns>
bool HasChildContent()
{
if (child.Count < 0)
{
return false;
}
else
{
for (int i = 0; i < child.Count; i++)
{
if (!child[i].HasContent)
{
return false;
}
else if (i == child.Count - 1 && child[i].HasContent)
{
return true;
}
}
}
return false;
}
修改OnGUI的语句
//xml的名字
GUILayout.Space(10);
XmlName = EditorGUILayout.TextField("XML Name", XmlName);
if (!HasXmlNameContent())
{
EditorGUILayout.HelpBox("XML文件名不能为空!", MessageType.Error);
}
//根节点名称
GUILayout.Space(10);
root = EditorGUILayout.TextField("Root Name", root);
if (!HasRootNameContent())
{
EditorGUILayout.HelpBox("根节点不能为空!", MessageType.Error);
}
//子节点信息
GUILayout.Space(10);
_serializedObject.Update();
//检查是否有修改
EditorGUI.BeginChangeCheck();
//显示
EditorGUILayout.PropertyField(_assetLstProperty, true);
//当检查结束时应用最新修改数据
if (EditorGUI.EndChangeCheck())
{
_serializedObject.ApplyModifiedProperties();
}
if (!HasChildContent())
{
EditorGUILayout.HelpBox("子节点不能为空!", MessageType.Error);
}
打开窗口可以看到,当我们没有输入数据时会有出现警告
添加数据为空时创建文件的警告窗口
新建CreateXmlMessage.cs脚本,继承自EditorWindow,放置在Editor文件夹下
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class CreateXmlMessage : EditorWindow {
void OnGUI()
{
EditorGUILayout.LabelField("XML数据不能为空");
GUILayout.Space(100);
if (GUILayout.Button("Close"))
{
this.Close();
}
}
}
回到CustomTool脚本,声明CheckInputData()方法,用于判断所有输入框是否都已经填入数据
/// <summary>
/// 判断是否有数据填入
/// </summary>
/// <returns></returns>
bool CheckInputData()
{
if (!HasChildContent() || !HasRootNameContent() || !HasXmlNameContent())
{
CreateXmlMessage _mesWindow = ScriptableObject.CreateInstance<CreateXmlMessage>();
_mesWindow.position = new Rect(Screen.width / 2, Screen.height / 2, 250, 150);
_mesWindow.ShowPopup();
return false;
}
return true;
}
再次修改OnGUI内按钮判断
if (GUILayout.Button("Create/Update XML"))
{
if (CheckInputData())
{
CreateXml(XmlName);
if (isSaveAsset)
{
SaveAsset();
}
}
}
打开窗口测试,在不输入数据的情况下创建XML则会弹出提示窗口
主要工具类完整代码
CustomTool.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Xml;
using UnityEditor;
using UnityEngine;
/// <summary>
/// 子节点结构体
/// </summary>
[System.Serializable]
public struct elmNew
{
public string childRootName;
public string attributeId;
public string attributeName;
public elmChildNode[] ElmChildNode;
/// <summary>
/// 是否有填入数据
/// </summary>
public bool HasContent
{
get
{
for (int i = 0; i < ElmChildNode.Length; i++)
{
return childRootName.Length > 0 && attributeId.Length > 0 && attributeName.Length > 0 &&
ElmChildNode.Length > 0 && ElmChildNode[i].HasContent;
}
return childRootName.Length > 0 && attributeId.Length > 0 && attributeName.Length > 0 &&
ElmChildNode.Length > 0;
}
}
}
/// <summary>
/// 子节点下层节点结构体
/// </summary>
[System.Serializable]
public struct elmChildNode
{
public string name;
public string innerText;
/// <summary>
/// 是否有填入数据
/// </summary>
public bool HasContent
{
get { return name.Length > 0 && innerText.Length > 0; }
}
}
public class CustomTool : EditorWindow
{
//XML文件名称
public string XmlName;
//根节点名称
public string root;
//子节点
[SerializeField]
private List<elmNew> child = new List<elmNew>();
//序列化属性
protected SerializedProperty _assetLstProperty;
//序列化对象
protected SerializedObject _serializedObject;
public Vector2 ScrollPos;
//asset数据保存路径
public string assetSavePath;
//asset数据类
public XmlAsset asset;
private bool isSaveAsset;
CustomTool()
{
//设置窗口标题
this.titleContent = new GUIContent("XmlTool");
}
[MenuItem("CustomTool/XML")]
static void showWindow()
{
//获取窗口,如果没有则创建一个新的
EditorWindow.GetWindow(typeof(CustomTool));
}
void OnEnable()
{
//序列化子节点结构体
_serializedObject = new SerializedObject(this);
_assetLstProperty = _serializedObject.FindProperty("child");
}
void OnGUI()
{
EditorGUILayout.BeginVertical();
ScrollPos = EditorGUILayout.BeginScrollView(ScrollPos);
//绘制标题
GUILayout.Space(10);
GUILayout.Label("Create/Update Xml");
//读取asset数据
asset = EditorGUILayout.ObjectField("选取Xml数据", asset, typeof(XmlAsset), true) as XmlAsset;
if (asset != null)
{
XmlName = asset.XmlName;
root = asset.root;
child = asset.child;
}
//xml的名字
GUILayout.Space(10);
XmlName = EditorGUILayout.TextField("XML Name", XmlName);
if (!HasXmlNameContent())
{
EditorGUILayout.HelpBox("XML文件名不能为空!", MessageType.Error);
}
//根节点名称
GUILayout.Space(10);
root = EditorGUILayout.TextField("Root Name", root);
if (!HasRootNameContent())
{
EditorGUILayout.HelpBox("根节点不能为空!", MessageType.Error);
}
//子节点信息
GUILayout.Space(10);
_serializedObject.Update();
//检查是否有修改
EditorGUI.BeginChangeCheck();
//显示
EditorGUILayout.PropertyField(_assetLstProperty, true);
//当检查结束时应用最新修改数据
if (EditorGUI.EndChangeCheck())
{
_serializedObject.ApplyModifiedProperties();
}
if (!HasChildContent())
{
EditorGUILayout.HelpBox("子节点不能为空!", MessageType.Error);
}
GUILayout.Space(10);
isSaveAsset = GUILayout.Toggle(isSaveAsset, "是否保存数据为Asset资源");
if (GUILayout.Button("Create/Update XML"))
{
if (CheckInputData())
{
CreateXml(XmlName);
if (isSaveAsset)
{
SaveAsset();
}
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
/// <summary>
/// 创建Xml文件
/// </summary>
/// <param name="fileName"></param>
public void CreateXml(string fileName)
{
string filePath = Application.streamingAssetsPath + "/" + fileName + ".xml";
//创建Xml文件实例
XmlDocument xmlDoc = new XmlDocument();
//创建root节点
XmlElement root = xmlDoc.CreateElement(this.root);
for (int i = 0; i < child.Count; i++)
{
//继续创建子节点
XmlElement elmNew = xmlDoc.CreateElement(child[i].childRootName);
//配置节点的两个属性
elmNew.SetAttribute("id", child[i].attributeId);
elmNew.SetAttribute("name", child[i].attributeName);
//继续创建下一层节点
for (int j = 0; j < child[i].ElmChildNode.Length; j++)
{
XmlElement childnode = xmlDoc.CreateElement(child[i].ElmChildNode[j].name);
//设置节点数据
childnode.InnerText = child[i].ElmChildNode[j].innerText;
elmNew.AppendChild(childnode);
}
//把节点一层一层添加到XmlDoc里---------------->>>注意先后顺序
root.AppendChild(elmNew);
}
xmlDoc.AppendChild(root);
xmlDoc.Save(filePath);
Debug.Log("CreateXml OK");
}
/// <summary>
/// 保存Xml数据为Asset
/// </summary>
public void SaveAsset()
{
XmlAsset xmlAsset = ScriptableObject.CreateInstance<XmlAsset>();
OpenSaveWindow("保存Xml数据", "asset文件(*.asset)\0*.asset");
xmlAsset.XmlName = XmlName;
xmlAsset.root = root;
xmlAsset.child = child;
AssetDatabase.CreateAsset(xmlAsset, "Assets/" + assetSavePath + ".asset");
}
/// <summary>
/// 绝对路径转相对路径
/// </summary>
/// <param name="filespec"></param>
/// <param name="folder"></param>
/// <returns></returns>
public static string GetRelativePath(string filespec, string folder)
{
const string directorySeparatorChar = "\\";
Uri pathUri = new Uri(filespec);
if (!folder.EndsWith(directorySeparatorChar))
{
folder += directorySeparatorChar;
}
Uri folderUri = new Uri(folder);
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace("/", directorySeparatorChar));
}
/// <summary>
/// 开启Window保存文件窗口
/// </summary>
/// <param name="title"></param>
/// <param name="_filter"></param>
void OpenSaveWindow(string title, string _filter)
{
OpenFileName openFileName = new OpenFileName();
openFileName.structSize = Marshal.SizeOf(openFileName);
openFileName.filter = _filter;
openFileName.file = new string(new char[256]);
openFileName.maxFile = openFileName.file.Length;
openFileName.fileTitle = new string(new char[64]);
openFileName.maxFileTitle = openFileName.fileTitle.Length;
openFileName.initialDir = Application.streamingAssetsPath.Replace('/', '\\');//默认路径
openFileName.title = title;
openFileName.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008;
if (LocalDialog.GetSaveFileName(openFileName))
{
assetSavePath = GetRelativePath(openFileName.file, Application.dataPath);
}
}
/// <summary>
/// 判断XML名称是否为空
/// </summary>
/// <returns></returns>
bool HasXmlNameContent()
{
return XmlName != null && XmlName.Length > 0;
}
/// <summary>
/// 判断根节点是否为空
/// </summary>
/// <returns></returns>
bool HasRootNameContent()
{
return root != null && root.Length > 0;
}
/// <summary>
/// 判断子节点是否填入数据
/// </summary>
/// <returns></returns>
bool HasChildContent()
{
if (child.Count < 0)
{
return false;
}
else
{
for (int i = 0; i < child.Count; i++)
{
if (!child[i].HasContent)
{
return false;
}
else if (i == child.Count - 1 && child[i].HasContent)
{
return true;
}
}
}
return false;
}
/// <summary>
/// 判断是否有数据填入
/// </summary>
/// <returns></returns>
bool CheckInputData()
{
if (!HasChildContent() || !HasRootNameContent() || !HasXmlNameContent())
{
CreateXmlMessage _mesWindow = ScriptableObject.CreateInstance<CreateXmlMessage>();
_mesWindow.position = new Rect(Screen.width / 2, Screen.height / 2, 250, 150);
_mesWindow.ShowPopup();
return false;
}
return true;
}
}