返回

ETReferenceCollector

结构

  • ReferenceCollectorData

    该类主要用来保存引用信息,并使其能在Inspector面板显示

    [Serializable]//使其能在Inspector面板显示,并且可以被赋予相应值
    public class ReferenceCollectorData
    {
        public string key;
        //Object并非C#基础中的Object,而是 UnityEngine.Object
        public Object gameObject;
    }
    
  • ReferenceCollectorDataComparer

    该类主要用于对比两个ReferenceCollectorData是否重复。

    //继承IComparer对比器
    public class ReferenceCollectorDataComparer : IComparer<ReferenceCollectorData>
    {
        public int Compare(ReferenceCollectorData x, ReferenceCollectorData y)
        {
            //StringComparison.Ordinal会使用序号排序规则比较字符串,因为是byte级别的比较,所以准确性和性能都不错
            return string.Compare(x.key, y.key, StringComparison.Ordinal);
        }
    }
    
  • ReferenceCollector

    • 容器

          //用于序列化的List
          public List<ReferenceCollectorData> data = new List<ReferenceCollectorData>();
          //Object并非C#基础中的Object,而是 UnityEngine.Object
          private readonly Dictionary<string, Object> dict = new Dictionary<string, Object>();
      
    • 继承ISerializationCallbackReceiver并实现OnAfterDeserializeOnBeforeSerialize两个回调函数,使其能在序列化后做一些操作

          /// <summary>
          /// 在反序列化后运行
          /// </summary>
          public void OnAfterDeserialize()
          {
              dict.Clear();
              foreach (ReferenceCollectorData referenceCollectorData in data)
              {
                  //筛选重复项
                  if (!dict.ContainsKey(referenceCollectorData.key))
                  {
                      //添加到字典中
                      dict.Add(referenceCollectorData.key, referenceCollectorData.gameObject);
                  }
              }
          }
      
  • UNITY_EDITOR方法

    #if UNITY_EDITOR
        //添加新的元素
        public void Add(string key, Object obj)
        {
            //SerializedObject和SerializedProperty是提供通用的方式编辑对象属性的类
            SerializedObject serializedObject = new SerializedObject(this);
            //根据PropertyPath读取数据
            //如果不知道具体的格式,可以右键用文本编辑器打开一个prefab文件(如Bundles/UI目录中的几个)
            //因为这几个prefab挂载了ReferenceCollector,所以搜索data就能找到存储的数据
            SerializedProperty dataProperty = serializedObject.FindProperty("data");
            int i;
            //遍历data,看添加的数据是否存在相同key
            for (i = 0; i < data.Count; i++)
            {
                if (data[i].key == key)
                {
                    break;
                }
            }
            //不等于data.Count意为已经存在于data List中,直接赋值即可
            if (i != data.Count)
            {
                //根据i的值获取dataProperty,也就是data中的对应ReferenceCollectorData,不过在这里,是对Property进行的读取,有点类似json或者xml的节点
                SerializedProperty element = dataProperty.GetArrayElementAtIndex(i);
                //对对应节点进行赋值,值为gameobject相对应的fileID
                //fileID独一无二,单对单关系,其他挂载在这个gameobject上的script或组件会保存相对应的fileID
                element.FindPropertyRelative("gameObject").objectReferenceValue = obj;
            }
            else
            {
                //等于则说明key在data中无对应元素,所以得向其插入新的元素
                dataProperty.InsertArrayElementAtIndex(i);
                SerializedProperty element = dataProperty.GetArrayElementAtIndex(i);
                element.FindPropertyRelative("key").stringValue = key;
                element.FindPropertyRelative("gameObject").objectReferenceValue = obj;
            }
            //应用与更新
            EditorUtility.SetDirty(this);
            serializedObject.ApplyModifiedProperties();
            serializedObject.UpdateIfRequiredOrScript();
        }
        //删除元素,知识点与上面的添加相似
        public void Remove(string key)
        {
            SerializedObject serializedObject = new SerializedObject(this);
            SerializedProperty dataProperty = serializedObject.FindProperty("data");
            int i;
            for (i = 0; i < data.Count; i++)
            {
                if (data[i].key == key)
                {
                    break;
                }
            }
            if (i != data.Count)
            {
                dataProperty.DeleteArrayElementAtIndex(i);
            }
            EditorUtility.SetDirty(this);
            serializedObject.ApplyModifiedProperties();
            serializedObject.UpdateIfRequiredOrScript();
        }
    
        public void Clear()
        {
            SerializedObject serializedObject = new SerializedObject(this);
            //根据PropertyPath读取prefab文件中的数据
            //如果不知道具体的格式,可以直接右键用文本编辑器打开,搜索data就能找到
            var dataProperty = serializedObject.FindProperty("data");
            dataProperty.ClearArray();
            EditorUtility.SetDirty(this);
            serializedObject.ApplyModifiedProperties();
            serializedObject.UpdateIfRequiredOrScript();
        }
    
        public void Sort()
        {
            SerializedObject serializedObject = new SerializedObject(this);
            data.Sort(new ReferenceCollectorDataComparer());
            EditorUtility.SetDirty(this);
            serializedObject.ApplyModifiedProperties();
            serializedObject.UpdateIfRequiredOrScript();
        }
    #endif
    

ReferenceCollectorEditor

主要重构了ReferenceCollector组件的Inspector面板

在OnInspectorGUI中实现了如下几个功能

  • 按钮模块(这一部分按钮事件实际上是在ReferenceCollector的UNITY_EDITOR方法实现的)

    • 添加引用
    • 删除所有
    • 删除空引用
    • 排序
  • 搜索框

  • 字典模块

  • 拖动功能

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
//Object并非C#基础中的Object,而是 UnityEngine.Object
using Object = UnityEngine.Object;

//自定义ReferenceCollector类在界面中的显示与功能
[CustomEditor(typeof(ReferenceCollector))]
//没有该属性的编辑器在选中多个物体时会提示“Multi-object editing not supported”
[CanEditMultipleObjects]
public class ReferenceCollectorEditor : Editor
{
    private ReferenceCollector referenceCollector;
    //搜索Key
    private string _searchKey = "";
    //搜索框对应对象
    private Object heroPrefab;


    //输入在textfield中的字符串
    private string searchKey
    {
        get
        {
            return _searchKey;
        }
        set
        {
            if (_searchKey != value)
            {
                _searchKey = value;
                heroPrefab = referenceCollector.Get<Object>(searchKey);
            }
        }
    }

    private void OnEnable()
    {
        //将被选中的gameobject所挂载的ReferenceCollector赋值给编辑器类中的ReferenceCollector,方便操作
        referenceCollector = (ReferenceCollector)target;
    }

    //OnInspectorGUI可以自定义对Inspector面板的绘制
    public override void OnInspectorGUI()
    {
        //使ReferenceCollector支持撤销操作,还有Redo,不过没有在这里使用
        Undo.RecordObject(referenceCollector, "Changed Settings");
        //获取所有引用对象
        var dataProperty = serializedObject.FindProperty("data");

        //-----------------------按钮栏------------------------

        //开始水平布局
        GUILayout.BeginHorizontal();

        //下面几个if都是点击按钮就会返回true调用里面的东西
        if (GUILayout.Button("添加引用"))
        {
            //添加新的元素,具体的函数注释
            // Guid.NewGuid().GetHashCode().ToString() 就是新建后默认的key
            AddReference(dataProperty, Guid.NewGuid().GetHashCode().ToString(), null);
        }
        if (GUILayout.Button("全部删除"))
        {
            referenceCollector.Clear();
            //dataProperty.ClearArray();
        }
        if (GUILayout.Button("删除空引用"))
        {
            referenceCollector.DelNull();
            //DelNullReference();
        }
        if (GUILayout.Button("排序"))
        {
            referenceCollector.Sort();
        }
        //结束水平对齐
        GUILayout.EndHorizontal();

        //-----------------搜索栏---------------------
        //开始新的水平对齐
        EditorGUILayout.BeginHorizontal();
        //可以在编辑器中对searchKey进行赋值,只要输入对应的Key值,就可以点后面的删除按钮删除相对应的元素
        //输入框
        searchKey = EditorGUILayout.TextField(searchKey);
        //添加的可以用于选中Object的框,这里的object也是(UnityEngine.Object
        //第三个参数为是否只能引用scene中的Object
        //对象框
        EditorGUILayout.ObjectField(heroPrefab, typeof(Object), false);
        //删除按键
        if (GUILayout.Button("删除"))
        {
            referenceCollector.Remove(searchKey);
            heroPrefab = null;
        }
        //结束水平对齐
        GUILayout.EndHorizontal();
        //留白空间
        EditorGUILayout.Space();
        //---------------字典-----------------
        var delList = new List<int>();
        SerializedProperty property;
        //遍历ReferenceCollector中data list的所有元素,显示在编辑器中
        for (int i = referenceCollector.data.Count - 1; i >= 0; i--)
        {
            //开始新的水平对齐
            GUILayout.BeginHorizontal();
            //这里的知识点在ReferenceCollector中有说
            property = dataProperty.GetArrayElementAtIndex(i).FindPropertyRelative("key");
            //输入文本框
            property.stringValue = EditorGUILayout.TextField(property.stringValue, GUILayout.Width(150));
            property = dataProperty.GetArrayElementAtIndex(i).FindPropertyRelative("gameObject");
            //对象框
            property.objectReferenceValue = EditorGUILayout.ObjectField(property.objectReferenceValue, typeof(Object), true);
            if (GUILayout.Button("X"))
            {
                //将元素添加进删除list
                delList.Add(i);
            }
            //结束水平对齐
            GUILayout.EndHorizontal();
        }

        //----------------------拖动-----------------------
        var eventType = Event.current.type;
        //在Inspector 窗口上创建区域,向区域拖拽资源对象,获取到拖拽到区域的对象
        if (eventType == EventType.DragUpdated || eventType == EventType.DragPerform)
        {
            // 在拖放上显示一个复制图标
            DragAndDrop.visualMode = DragAndDropVisualMode.Copy;

            if (eventType == EventType.DragPerform)
            {
                DragAndDrop.AcceptDrag();
                foreach (var o in DragAndDrop.objectReferences)
                {
                    //将元素添加到字典中
                    AddReference(dataProperty, o.name, o);
                }
            }

            Event.current.Use();
        }

        //遍历删除list,将其删除掉
        foreach (var i in delList)
        {
            dataProperty.DeleteArrayElementAtIndex(i);
        }

        //应用修改属性,并更新脚本
        serializedObject.ApplyModifiedProperties();
        serializedObject.UpdateIfRequiredOrScript();
    }

    //添加元素,具体知识点在ReferenceCollector中说了
    private void AddReference(SerializedProperty dataProperty, string key, Object obj)
    {
        int index = dataProperty.arraySize;
        dataProperty.InsertArrayElementAtIndex(index);
        var element = dataProperty.GetArrayElementAtIndex(index);
        element.FindPropertyRelative("key").stringValue = key;
        element.FindPropertyRelative("gameObject").objectReferenceValue = obj;
    }   
}

相关扩展类

GameObjectHelper主要实现了一个对GameObject查找引用的快捷扩展

	public static class GameObjectHelper
    {
        /// <summary>
        /// 通过对象挂载的引用集脚本获取对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="gameObject"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T Get<T>(this GameObject gameObject, string key) where T : class
        {
            try
            {
                return gameObject.GetComponent<ReferenceCollector>().Get<T>(key);
            }
            catch (Exception e)
            {
                throw new Exception($"获取{gameObject.name}的ReferenceCollector key失败, key: {key}", e);
            }
        }
    }

Licensed under CC BY-NC-SA 4.0
0