using XGame.Framework.ObjectCollection; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using UnityEditor; using UnityEditorInternal; using UnityEngine; using System.Reflection; namespace XGame.Editor.ObjectCollection { [CustomPropertyDrawer(typeof(ObjectCollector))] public partial class ObjectCollectorEditor : PropertyDrawer { private static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; private ReorderableList collectList; private ReorderableList DrawsearchList; private List searchList = new List(); private SerializedProperty keys; private SerializedProperty values; private SerializedProperty bindTypes; private ObjectCollector objectCollector; //搜索框 private string m_InputSearchText; private GUIStyle TextFieldRoundEdge; private GUIStyle TextFieldRoundEdgeCancelButton; private GUIStyle TextFieldRoundEdgeCancelButtonEmpty; private GUIStyle TransparentTextField; private void SetData(SerializedProperty property) { var ob = property.serializedObject.targetObject; var selfFields = ob.GetType().GetFields(flags); var baseFields = ob.GetType().BaseType.GetFields(flags); var fields = selfFields.Concat(baseFields).ToArray(); foreach (var field in fields) { if (field.FieldType == typeof(ObjectCollector)) { objectCollector = field.GetValue(ob) as ObjectCollector; break; } } } private void OnInit(SerializedProperty property) { SetData(property); collectList.headerHeight = 0; DrawsearchList.headerHeight = 0; // 绘制 collectList.drawElementCallback = (Rect rect, int index, bool selected, bool focused) => { var key = keys.GetArrayElementAtIndex(index); var value = values.GetArrayElementAtIndex(index); var bindType = bindTypes.GetArrayElementAtIndex(index); EditorGUILayout.BeginHorizontal(); GUILayout.Space(10); // 3等分,中间间隔10像素 var copyRect = new Rect(rect.x, rect.y + 2, (rect.width - 20) / 3, EditorGUIUtility.singleLineHeight); if (GUI.Button(copyRect, "", "HelpBox")) { TextEditor textEditor = new TextEditor(); textEditor.text = key.stringValue; textEditor.OnFocus(); textEditor.Copy(); Debug.Log($"序列化器Key复制成功!Key:{key.stringValue}"); }; EditorGUI.BeginDisabledGroup(true); //key var keyRect = copyRect; EditorGUI.TextField(keyRect, key.stringValue); EditorGUI.EndDisabledGroup(); //value var valueRect = keyRect; valueRect.x += keyRect.width + 10; Type type = objectCollector.collectorType; var tmp = value.objectReferenceValue; value.objectReferenceValue = EditorGUI.ObjectField(valueRect, value.objectReferenceValue, type, true); if (tmp != value.objectReferenceValue) { if (value.objectReferenceValue != null) { if (CheckForSameValue(value.objectReferenceValue, index)) { value.objectReferenceValue = null; key.stringValue = ""; bindType.stringValue = ""; } else { string name = value.objectReferenceValue.name.Replace(" ", ""); if (!CheckForReName(name, index, value.objectReferenceValue)) { key.stringValue = name; bindType.stringValue = GetTypeName(value.objectReferenceValue); } else { value.objectReferenceValue = null; key.stringValue = ""; bindType.stringValue = ""; } } } else { key.stringValue = string.Empty; bindType.stringValue = string.Empty; } } else { if (value.objectReferenceValue != null) { string name = value.objectReferenceValue.name.Replace(" ", ""); key.stringValue = name; } else { key.stringValue = string.Empty; bindType.stringValue = string.Empty; } } //type if (value.objectReferenceValue != null) { var bindTypeVal = bindType.stringValue; var typeRect = valueRect; typeRect.x += valueRect.width + 10; var options = GetObjectTypes(value.objectReferenceValue); var selectIdx = 0; if (!string.IsNullOrEmpty(bindTypeVal)) { selectIdx = Array.FindIndex(options, (a) => bindTypeVal.Equals(a, StringComparison.OrdinalIgnoreCase)); if (selectIdx < 0) selectIdx = 0; } selectIdx = EditorGUI.Popup(typeRect, selectIdx, options); bindType.stringValue = options[selectIdx]; } EditorGUILayout.EndHorizontal(); }; collectList.onAddCallback = list => { values.arraySize++; var size = values.arraySize; keys.arraySize = size; bindTypes.arraySize = size; values.GetArrayElementAtIndex(size - 1).objectReferenceValue = null; keys.GetArrayElementAtIndex(size - 1).stringValue = ""; bindTypes.GetArrayElementAtIndex(size - 1).stringValue = ""; }; collectList.onRemoveCallback = list => { var index = list.index; if (list.index == -1) { index = values.arraySize - 1; } values.GetArrayElementAtIndex(index).objectReferenceValue = null; keys.GetArrayElementAtIndex(index).stringValue = null; bindTypes.GetArrayElementAtIndex(index).stringValue = null; values.DeleteArrayElementAtIndex(index); keys.DeleteArrayElementAtIndex(index); bindTypes.DeleteArrayElementAtIndex(index); }; DrawsearchList.drawElementCallback = (Rect rect, int index, bool selected, bool focused) => { var key = searchList[index]; SerializedProperty value = null; SerializedProperty bindType = null; for (int i = 0; i < keys.arraySize; i++) { if (keys.GetArrayElementAtIndex(i).stringValue == key) { value = values.GetArrayElementAtIndex(i); bindType = bindTypes.GetArrayElementAtIndex(i); break; } } EditorGUILayout.BeginHorizontal(); GUILayout.Space(10); //key var keyRect = new Rect(rect.x, rect.y + 2, (rect.width - 20) / 3, EditorGUIUtility.singleLineHeight); EditorGUI.BeginDisabledGroup(true); EditorGUI.TextField(keyRect, key); //value var valueRect = keyRect; valueRect.x += keyRect.width + 10; Type type = objectCollector.collectorType; value.objectReferenceValue = EditorGUI.ObjectField(valueRect, value.objectReferenceValue, type, true); //type if (value.objectReferenceValue != null) { var typeRect = valueRect; typeRect.x += valueRect.width + 10; EditorGUI.TextField(typeRect, bindType.stringValue); } EditorGUI.EndDisabledGroup(); EditorGUILayout.EndHorizontal(); }; } private string[] GetObjectTypes(UnityEngine.Object obj) { var types = new List(); GameObject go = null; if (obj is Component component) { go = component.gameObject; } else if (obj is GameObject tmpGo) { go = tmpGo; } if (go != null) { types.Add(typeof(GameObject).Name); types.Add(typeof(Transform).Name); var components = go.GetComponents(); foreach (var item in components) { types.Add(GetTypeName(item)); } } else { types.Add(GetTypeName(obj)); } return types.ToArray(); } const string UnityNamespace = "UnityEngine"; private string GetTypeName(object obj) { var type = obj.GetType(); if (type.Namespace == UnityNamespace) { return type.Name; } return type.FullName; } private bool CheckForSameValue(UnityEngine.Object value, int index) { for (int i = 0; i < values.arraySize; i++) { if (index == i) continue; var compareValue = values.GetArrayElementAtIndex(i).objectReferenceValue; if (compareValue == null) continue; if (value.GetInstanceID() == compareValue.GetInstanceID()) { EditorUtility.DisplayDialog("Warning", $"已经添加过该{objectCollector.collectorType.Name}啦!", "确定"); return true; } } return false; } private bool CheckForReName(string name, int index, UnityEngine.Object ob) { for (int i = 0; i < values.arraySize; i++) { if (i == index) continue; var value = values.GetArrayElementAtIndex(i); if (value.objectReferenceValue == null) continue; string compareName = value.objectReferenceValue.name.Replace(" ", ""); if (!string.IsNullOrEmpty(compareName) && name == compareName) { EditorUtility.DisplayDialog("Warning", $"该{objectCollector.collectorType.Name}名称{name}重名啦!请修改GameObject名称。", "确定"); EditorGUIUtility.PingObject(ob); return true; } } return false; } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return 0; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { keys = property.FindPropertyRelative("keys"); values = property.FindPropertyRelative("values"); bindTypes = property.FindPropertyRelative("bindTypes"); GUILayout.BeginVertical("HelpBox"); DrawInputTextField(); EditorGUILayout.Space(); if (collectList == null) { collectList = new ReorderableList(property.serializedObject, values, true, false, true, true); DrawsearchList = new ReorderableList(searchList, typeof(string), true, false, false, false); OnInit(property); } if (Application.isPlaying) EditorGUI.BeginDisabledGroup(true); if (string.IsNullOrEmpty(m_InputSearchText)) { GUI.contentColor = Color.white; var dragArea = GUILayoutUtility.GetRect(0f, 45f); GUI.color = Color.grey; UnityEngine.Object ob = DragAreaGUI.ObjectField(dragArea, objectCollector.collectorType, objectCollector.blackTypes); if (ob != null) AddObjectToList(ob); GUI.color = Color.white; GUILayout.Space(8); collectList.DoLayoutList(); } else { GetSearchList(); DrawsearchList.DoLayoutList(); } GUILayout.EndVertical(); if (Application.isPlaying) EditorGUI.EndDisabledGroup(); } private void AddObjectToList(UnityEngine.Object ob) { if (CheckForSameValue(ob, -1)) return; string name = ob.name.Replace(" ", ""); if (CheckForReName(name, -1, ob)) return; values.arraySize++; var size = values.arraySize; keys.arraySize = size; bindTypes.arraySize = size; values.GetArrayElementAtIndex(size - 1).objectReferenceValue = ob; keys.GetArrayElementAtIndex(size - 1).stringValue = ob.name; bindTypes.GetArrayElementAtIndex(size - 1).stringValue = GetTypeName(ob); } private void GetSearchList() { searchList.Clear(); for (int index = 0; index < keys.arraySize; index++) { var key = keys.GetArrayElementAtIndex(index); if (!string.IsNullOrEmpty(m_InputSearchText)) { var inputStr = m_InputSearchText.ToLower(); var compareStr = key.stringValue.ToLower(); Regex r = new Regex(inputStr); Match m = r.Match(compareStr); if (m.Success) { searchList.Add(key.stringValue); } } } } private void DrawInputTextField() { if (TextFieldRoundEdge == null) { TextFieldRoundEdge = new GUIStyle("SearchTextField"); TextFieldRoundEdgeCancelButton = new GUIStyle("SearchCancelButton"); TextFieldRoundEdgeCancelButtonEmpty = new GUIStyle("SearchCancelButtonEmpty"); TransparentTextField = new GUIStyle(EditorStyles.whiteLabel); TransparentTextField.normal.textColor = EditorStyles.textField.normal.textColor; } Rect position = EditorGUILayout.GetControlRect(); GUIStyle textFieldRoundEdge = TextFieldRoundEdge; GUIStyle transparentTextField = TransparentTextField; GUIStyle gUIStyle = (m_InputSearchText != "") ? TextFieldRoundEdgeCancelButton : TextFieldRoundEdgeCancelButtonEmpty; position.width -= gUIStyle.fixedWidth; if (Event.current.type == EventType.Repaint) { GUI.contentColor = (EditorGUIUtility.isProSkin ? Color.white : Color.black); if (string.IsNullOrEmpty(m_InputSearchText)) { textFieldRoundEdge.Draw(position, new GUIContent("输入查询,可模糊匹配"), 0); } else { textFieldRoundEdge.Draw(position, new GUIContent(""), 0); } GUI.contentColor = Color.white; } Rect rect = position; float num = textFieldRoundEdge.CalcSize(new GUIContent("")).x - 2f; rect.width -= num; rect.x += num; rect.y += 1f; m_InputSearchText = EditorGUI.TextField(rect, m_InputSearchText, transparentTextField); position.x += position.width; position.width = gUIStyle.fixedWidth; position.height = gUIStyle.fixedHeight; if (GUI.Button(position, GUIContent.none, gUIStyle) && m_InputSearchText != "") { m_InputSearchText = ""; GUI.changed = true; GUIUtility.keyboardControl = 0; } } } }