UnityEditor:通过反射实现的Class过滤器编辑器
程序员文章站
2022-07-14 11:17:44
...
之前因为策划有在Game运行模式时动态修改脚本,在退出后脚本参数保存的需求,用于在场景中动态地调整参数。研究了一下之前老外实现的一个插件PlayModePersist,也就是一个在运行模式下进行保存数据的插件。
PlayModePersist 这款插件不知道什么原因已经从Appstore下架了。有需要的同学可以自行下载。
通过阅读这款插件的源码,学到了两个比较有趣的实现:
1. 通过反射获取所有应用类,做成编辑器以供筛选。
2. 运行模式下,实时保存数据。
今天这篇文章就是学习第一个的方法。
首先,它完成后大概是这个样子的:
普通查找
模糊查找
关于编辑器的学习总结,由于本人很赖之前虽然一直想总结下,但是其实别人已经总结的很好啦,就一直搁置了。。。
想要学习的同学可以参考下面文章
上代码,注释了主要的功能模块
using System;
using UnityEngine;
using UnityEditor;
using System.Reflection;
using System.Collections.Generic;
using System.Text.RegularExpressions;
public class FilterTestWindow : EditorWindow
{
private const string mDefaultKey = "Filter_Test";
private static List<FilterTestObject> mDefaultTypeList;
private static List<string> mSelectedTypeList;
string mSearchPath = ""; //检索字段
Vector2 scrollPos; //滚动条位置
bool isShowOnlyEnabled = false; //仅显示选中的类
[MenuItem("Window/Filter Test Settings")]
static void Init()
{
if (mSelectedTypeList == null)
LoadDefaults();
FilterTestWindow window = (FilterTestWindow)EditorWindow.GetWindow(typeof(FilterTestWindow), true, "Filter Test");
window.minSize = new Vector2(325, 300); //限制最小尺寸
//window.maxSize = new Vector2(325, 800); //限制最大尺寸
}
//通过EditorPrefs的key 收集选中类型信息
static void LoadDefaults()
{
mSelectedTypeList = new List<string>();
string rawDefaults = EditorPrefs.GetString(mDefaultKey);
string[] classParts = rawDefaults.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string classPart in classParts)
{
mSelectedTypeList.Add(classPart.Trim());
}
}
public void OnEnable()
{
if (mDefaultTypeList == null)
{
mDefaultTypeList = new List<FilterTestObject>();
Assembly asm = typeof(Component).Assembly;
Dictionary<string, bool> tempTypeList = new Dictionary<string, bool>();
string tempTypeName;
string tempNamespace;
string[] words;
//加入选中类型 分离命名空间和类名
foreach (string defaultTypeName in mSelectedTypeList)
{
words = defaultTypeName.Split('.');
if (words.Length == 1)
{
tempNamespace = "";
tempTypeName = defaultTypeName;
}
else
{
tempNamespace = words[0];
tempTypeName = words[1];
}
mDefaultTypeList.Add(new FilterTestObject(tempNamespace, tempTypeName, true));
tempTypeList.Add(tempTypeName, true);
}
//加入UnityEngine默认类型
foreach (Type type in asm.GetTypes())
{
if (typeof(Component).IsAssignableFrom(type))
{
if (type != typeof(Component) && type != typeof(Behaviour) && type != typeof(MonoBehaviour))
{
if (!tempTypeList.ContainsKey(type.Name))
{
mDefaultTypeList.Add(new FilterTestObject("UnityEngine", type.Name, false));
tempTypeList.Add(type.Name, false);
}
}
}
}
//加入所有自定义类型
Assembly[] currDomainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
string asmPath;
foreach (Assembly currDomainAsm in currDomainAssemblies)
{
asmPath = currDomainAsm.CodeBase;
if (asmPath.EndsWith("Assembly-UnityScript.dll") || asmPath.EndsWith("Assembly-CSharp.dll"))
{
foreach (System.Type type in currDomainAsm.GetTypes())
{
if (typeof(MonoBehaviour).IsAssignableFrom(type))
{
if (!tempTypeList.ContainsKey(type.Name))
{
mDefaultTypeList.Add(new FilterTestObject("", type.Name, false));
tempTypeList.Add(type.Name, false);
}
}
}
}
}
//按字母顺序排列
SortList();
}
}
private static void SortList()
{
FilterTestComparer comparer = new FilterTestComparer();
mDefaultTypeList.Sort(comparer);
}
//绘制窗口
public void OnGUI()
{
GUILayout.Label("Filter Test Window", EditorStyles.boldLabel);
EditorGUILayout.Space();
mSearchPath = EditorGUILayout.TextField(new GUIContent("Filter"), mSearchPath, GUILayout.Width(300.0f));
EditorGUILayout.Space();
//开始滚动条布局
scrollPos = EditorGUILayout.BeginScrollView(scrollPos, false, false, GUILayout.Width(300), GUILayout.Height(position.height - 140));
int listCount = 0;
//加入简易正则筛选测试
bool bRegexMode = false;
Regex regex = null;
string regexStr = "";
if (mSearchPath.StartsWith("*"))
{
bRegexMode = true;
regexStr = "\\w" + mSearchPath.Substring(1, mSearchPath.Length - 1).ToLower();
regex = new Regex(regexStr);
}
foreach (FilterTestObject testObj in mDefaultTypeList)
{
//正则测试条件
if (mSearchPath == "" || bRegexMode && regex.Match(testObj.TypeName.ToLower()).Success|| testObj.TypeName.ToLower().StartsWith(mSearchPath.ToLower()))
//if (mSearchPath == "" || autoPersistObj.TypeName.ToLower().StartsWith(mSearchPath.ToLower()))
{
if (!isShowOnlyEnabled || testObj.IsSelected)
{
GUILayout.BeginHorizontal(GUILayout.Width(250));
GUILayout.Label(testObj.TypeName, GUILayout.Width(200));
if (!testObj.IsSelected)
{
if (GUILayout.Button("Add"))
{
AddDefault(testObj.GetFullClassName());
testObj.IsSelected = true;
if (EditorApplication.isPlaying || EditorApplication.isPaused)
{
//Do Something...For Extension
}
}
}
else
{
if (GUILayout.Button("Remove"))
{
RemoveDefault(testObj.GetFullClassName());
testObj.IsSelected = false;
if (EditorApplication.isPlaying || EditorApplication.isPaused)
{
//Do Something...For Extension
}
}
}
GUILayout.EndHorizontal();
listCount++;
}
}
}
//筛选提示
if (listCount == 0)
{
if (isShowOnlyEnabled && mSearchPath == "")
{
GUILayout.Label("No Components Are Selected");
}
else
{
GUILayout.Label("Please Broaden Your Search");
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Show Only Enabled"); //仅显示选中类
isShowOnlyEnabled = EditorGUILayout.Toggle(isShowOnlyEnabled, GUILayout.ExpandWidth(true));
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
if (mSelectedTypeList.Count > 0)
{
EditorGUILayout.Space();
if (GUILayout.Button("Remove All"))//移除所有选中
{
foreach (FilterTestObject obj in mDefaultTypeList)
{
if (obj.IsSelected)
{
RemoveDefault(obj.GetFullClassName());
obj.IsSelected = false;
}
}
}
}
EditorGUILayout.Space();
if (GUILayout.Button("Print")) //简易拓展功能 打印所有选中类型
{
foreach (FilterTestObject obj in mDefaultTypeList)
{
if (obj.IsSelected)
{
Debug.Log(obj.GetFullClassName());
}
}
}
EditorGUILayout.Space();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
}
public static void AddDefault(string className)
{
if (!mSelectedTypeList.Contains(className))
{
mSelectedTypeList.Add(className);
SaveDefaults();
}
}
public static void RemoveDefault(string className)
{
if (mSelectedTypeList.Contains(className))
{
mSelectedTypeList.Remove(className);
SaveDefaults();
}
}
//保存信息到EditorPrefs
private static void SaveDefaults()
{
string defaultStr = string.Join("|", mSelectedTypeList.ToArray());
EditorPrefs.SetString(mDefaultKey, defaultStr);
}
}
public class FilterTestObject
{
private string typeName;
private string namespaceStr;
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value; }
}
public FilterTestObject(string namespaceStr, string typeName, bool bSelected)
{
this.typeName = typeName;
this.namespaceStr = namespaceStr;
this.isSelected = bSelected;
}
public string TypeName
{
get { return typeName; }
}
public string GetFullClassName()
{
if (namespaceStr.Length > 0) return namespaceStr + "." + typeName;
else return typeName;
}
}
public class FilterTestComparer : IComparer<FilterTestObject>
{
public int Compare(FilterTestObject x, FilterTestObject y)
{
if (x == null) return (y == null ? 0 : -1);
if (y == null) return 1;
return x.TypeName.CompareTo(y.TypeName);
}
}
将以上脚本放在Editor文件夹下即可使用。
测试中用的是比较简单的正则做的模糊查找(其实只是蛋疼添加的功能),还有加入、移除按钮后的特殊处理大家可以自行扩展。
插件中的作用是,将选中类型注册到需要保存类型的List中,在结束运行模式后筛选的类型数据将会实时通过PropertyInfo、FieldInfo进行保存
在运行模式下保存数据的功能将在下篇文章补上 :)
已更新: