欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UnityEditor:通过反射实现的Class过滤器编辑器

程序员文章站 2022-07-14 11:17:44
...
之前因为策划有在Game运行模式时动态修改脚本,在退出后脚本参数保存的需求,用于在场景中动态地调整参数。研究了一下之前老外实现的一个插件PlayModePersist,也就是一个在运行模式下进行保存数据的插件。
PlayModePersist 这款插件不知道什么原因已经从Appstore下架了。有需要的同学可以自行下载。
通过阅读这款插件的源码,学到了两个比较有趣的实现:
1. 通过反射获取所有应用类,做成编辑器以供筛选。
2. 运行模式下,实时保存数据。

今天这篇文章就是学习第一个的方法。

首先,它完成后大概是这个样子的:

UnityEditor:通过反射实现的Class过滤器编辑器

普通查找
UnityEditor:通过反射实现的Class过滤器编辑器

模糊查找

UnityEditor:通过反射实现的Class过滤器编辑器


关于编辑器的学习总结,由于本人很赖之前虽然一直想总结下,但是其实别人已经总结的很好啦,就一直搁置了。。。
想要学习的同学可以参考下面文章

上代码,注释了主要的功能模块

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文件夹下即可使用。

UnityEditor:通过反射实现的Class过滤器编辑器


测试中用的是比较简单的正则做的模糊查找(其实只是蛋疼添加的功能),还有加入、移除按钮后的特殊处理大家可以自行扩展。

插件中的作用是,将选中类型注册到需要保存类型的List中,在结束运行模式后筛选的类型数据将会实时通过PropertyInfo、FieldInfo进行保存


在运行模式下保存数据的功能将在下篇文章补上 :)

已更新:

 Unity3D 如何在退出运行模式后保存修改数据


UnityEditor:通过反射实现的Class过滤器编辑器

相关标签: unity 编辑器