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

之前因为策划有在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进行保存


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

已更新:

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


  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值