最近写一个小的编辑器功能,比较简单和入门,这里做一个简单教程。
因为最后可能需要重新编辑生成好的预设文件,要返回编辑,所以一些数据要保存下来,所以利用了userData保存数据。
下面的图是保存预设后的meta文件,可以看到userData数据。
本来这个在业务中,大概是通过3个图片生成一个地图预设,最后把一些关键输入保存到meta文件的userData数据中。文章中删除了业务代码,只有自定义面板部分,代码不是全面的代码。
这里只是讲解下面板是如何摆放组件并处理。
首先我们创建一个MazeEditor.cs脚本,主要用来记录我们填写的数据。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//UTF8 说明
public class MazeEditor : MonoBehaviour
{
[HideInInspector]
public GameObject OriginObject; //起点
[HideInInspector]
public GameObject StarObject; //星星
[HideInInspector]
public string mapName;
[HideInInspector]
public Object mapPrefab;
[HideInInspector]
[TooltipAttribute("地板图")]
public Sprite plane;
[HideInInspector]
[TooltipAttribute("高墙")]
public Sprite wall;
[HideInInspector]
[TooltipAttribute("底障碍")]
public Sprite slow_wall;
[HideInInspector]
public GameObject origin;
[HideInInspector]
public List<GameObject> stars;
[HideInInspector]
public bool mapCreated;
[HideInInspector]
public GameObject mapRoot;
[HideInInspector]
public GameObject starRoot;
//某些星星被删除掉了。视图里也要删除
public void StarDelete()
{
}
public void Save()
{
}
public void Clear()
{
mapCreated = false;
stars.Clear();
if (starRoot != null)
GameObject.DestroyImmediate(starRoot.gameObject);
if (mapRoot != null)
GameObject.DestroyImmediate(mapRoot.gameObject);
starRoot = null;
mapRoot = null;
plane = null;
wall = null;
slow_wall = null;
mapName = "";
}
}
然后是CreateMapEditor脚本,需要放入Editor文件夹。
using System.Collections.Generic;
using Unity.Plastic.Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
//UTF8 说明
[CustomEditor(typeof(MazeEditor))]
public class CreateMapEditor : Editor
{
int layerBuild;
SerializedProperty m_Star;
bool showsetting;
bool showMenu1;
bool showMenu2;
void OnEnable()
{
// 从GameObject脚本中获取对象以显示在检查器中
m_Star = serializedObject.FindProperty("stars");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI(); //显示
MazeEditor myScript = (MazeEditor)target;
if (!myScript.mapCreated)
{
showsetting = EditorGUILayout.Foldout(showsetting, "Flag");
if (showsetting)
{
EditorGUILayout.LabelField("标记位置的对象,请勿修改。");
myScript.OriginObject = (GameObject)EditorGUILayout.ObjectField("Origin (起点标记)", myScript.OriginObject, typeof(GameObject), true);
myScript.StarObject = (GameObject)EditorGUILayout.ObjectField("Star (星星标记)", myScript.StarObject, typeof(GameObject), true);
EditorGUILayout.Space(10);
}
}
showMenu1 = EditorGUILayout.Foldout(showMenu1, "MapSetting");
if (showMenu1)
{
myScript.mapName = EditorGUILayout.TextField("MapName (地图名)", myScript.mapName);
myScript.plane = (Sprite)EditorGUILayout.ObjectField("Plane (地板图)", myScript.plane, typeof(Sprite), false);
myScript.wall = (Sprite)EditorGUILayout.ObjectField("Wall (高墙)", myScript.wall, typeof(Sprite), false);
myScript.slow_wall = (Sprite)EditorGUILayout.ObjectField("Slow Wall (减速物)", myScript.slow_wall, typeof(Sprite), false);
if (myScript.mapRoot != null)
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Star, new GUIContent("Stars (星星)"));
if (EditorGUI.EndChangeCheck())
{
myScript.StarDelete();
}
}
//绘制按钮
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("创建地图"))
{
myScript.mapCreated = true;
Create();
}
if (myScript.mapCreated)
{
if (GUILayout.Button("创建星星"))
{
}
if (GUILayout.Button("保存"))
{
SaveMap();
Clear();
}
if (GUILayout.Button("清空"))
{
Clear();
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
}
showMenu2 = EditorGUILayout.Foldout(showMenu2, "Edit");
if (showMenu2)
{
myScript.mapPrefab = (Object)EditorGUILayout.ObjectField("MapPrefab (地图预设)", myScript.mapPrefab, typeof(Object), false);
if (GUILayout.Button("修改预设"))
{
Edit();
}
}
}
public void Create()
{
MazeEditor myScript = (MazeEditor)target;
if (myScript.plane == null)
{
Debug.Log("地面图片还没有。");
return;
}
//这里省略了业务代码。
}
public void Clear()
{
MazeEditor myScript = (MazeEditor)target;
myScript.Clear();
}
public void SaveMap()
{
MazeEditor myScript = (MazeEditor)target;
if (myScript.mapName.Length == 0)
{
Debug.LogWarning("请输入保存的名称。");
return;
}
string pathprefab = "Assets/ResAll/maps/" + myScript.mapName + ".prefab";
string bundlename = "reboot/maps/" + myScript.mapName;
myScript.mapRoot.name = myScript.mapName;
bool ok;
PrefabUtility.SaveAsPrefabAsset(myScript.mapRoot, pathprefab, out ok);
if (!ok)
{
Debug.LogError("保存失败:" + pathprefab);
return;
}
AssetImporter assetImporter = AssetImporter.GetAtPath(pathprefab); //得到Asset
assetImporter.assetBundleName = bundlename;
assetImporter.assetBundleVariant = "map";
MapData md = new MapData(); //一个保存Json的数据结构。
md.mapName = myScript.mapName;
md.spPlane = AssetDatabase.GetAssetPath(myScript.plane);
md.spWall = AssetDatabase.GetAssetPath(myScript.wall);
md.spSlow = AssetDatabase.GetAssetPath(myScript.slow_wall);
md.origin = myScript.origin.transform.position;
md.stars = new List<Vector3>();
for (int i = 0; i < myScript.stars.Count; i++)
{
md.stars.Add((Vector3)myScript.stars[i].transform.position);
}
var settings = new Newtonsoft.Json.JsonSerializerSettings();
settings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
string str = Newtonsoft.Json.JsonConvert.SerializeObject(md, settings);
assetImporter.userData = str;
assetImporter.SaveAndReimport();
AssetDatabase.Refresh();
Debug.Log("保存成功:" + pathprefab);
myScript.Save();
}
void Edit()
{
MazeEditor myScript = (MazeEditor)target;
Object prefab = myScript.mapPrefab;
Clear(); //必须放在获取路径的后面
string pathprefab = AssetDatabase.GetAssetPath(prefab);
AssetImporter assetImporter = AssetImporter.GetAtPath(pathprefab); //得到Asset
string str = assetImporter.userData;
MapData md = Newtonsoft.Json.JsonConvert.DeserializeObject<MapData>(str);
myScript.mapName = md.mapName;
myScript.plane = (Sprite)AssetDatabase.LoadAssetAtPath(md.spPlane, typeof(Sprite));
myScript.wall = (Sprite)AssetDatabase.LoadAssetAtPath(md.spWall, typeof(Sprite));
myScript.slow_wall = (Sprite)AssetDatabase.LoadAssetAtPath(md.spSlow, typeof(Sprite));
Debug.Log("预设读取完毕:" + pathprefab);
}
}
首先要声明[CustomEditor(typeof(MazeEditor))],和继承自Editor,这样才能和Maze脚本联系起来。
[CustomEditor(typeof(MazeEditor))]
public class CreateMapEditor : Editor
核心就是要覆写OnInspectorGUI函数。
base.OnInspectorGUI();表示显示MazeEditor.cs的布局显示。
MazeEditor myScript = (MazeEditor)target;
通过这个可以获得MazeEditor的方法和属性。
showsetting = EditorGUILayout.Foldout(showsetting, “Flag”);
这个是一个箭头收放容器,点击箭头可以显示和隐藏。
EditorGUILayout.LabelField
是文本显示。
myScript.OriginObject = (GameObject)EditorGUILayout.ObjectField(“Origin (起点标记)”, myScript.OriginObject, typeof(GameObject), true);
这个就是可以放入一个对象,把对象给脚本的的变量,最后的是类型,true表示可以把Hierarchy里的对象拖入,否则只能拖入Assets文件夹里的资源。
后面的图片可以是:
myScript.plane = (Sprite)EditorGUILayout.ObjectField(“Plane (地板图)”, myScript.plane, typeof(Sprite), false);
因为类型是Sprite我们可以直接看到图片选择框。
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Star, new GUIContent("Stars (星星)"));
if (EditorGUI.EndChangeCheck())
{
myScript.StarDelete();
}
这两个是用于检测数据变化的,当调整了面板,可以去做一切其他事情。
EditorGUILayout.BeginHorizontal();
这个是布局,我们可以看到按钮可以横向摆放,最后结束用EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
是间距。
userData数据可以保存在meta文件中,它可以通过assetImporter.userData = str;的方式保存,因为是字符串,我这里用json转化成string的方式保存方便使用。
这就是一个简单的Inspector的自定义面板。
还有很多可以使用的组件,可以参考官方文档。