#if UNITY_EDITOR using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor; using UnityEditor.Experimental.GraphView; using UnityEditor.Experimental.SceneManagement; using UnityEngine; using AlicizaX.UI; using AlicizaX.UI.Runtime; using UnityEditor.SceneManagement; using Random = UnityEngine.Random; [CustomEditor(typeof(UXControllerStateRecorder))] public class UXControllerStateRecorderEditor : Editor { private SerializedProperty _stateEntriesProp; private SerializedProperty _controller; private SerializedProperty _id; private GUIContent _removeIcon; private UXController _cacheController; private void OnEnable() { _controller = serializedObject.FindProperty("_controller"); _id = serializedObject.FindProperty("_id"); _stateEntriesProp = serializedObject.FindProperty("_stateEntries"); _removeIcon = EditorGUIUtility.IconContent("d_winbtn_win_close"); } private void OnDestroy() { if (target == null) { UnRegisterSelf(); } } private void UnRegisterSelf() { UXController controller = (_cacheController != null) ? _cacheController : GetPrefabStageController(); if (controller == null) return; var so = new SerializedObject(controller); var recorders = so.FindProperty("_recorders"); for (int i = recorders.arraySize - 1; i >= 0; i--) { var el = recorders.GetArrayElementAtIndex(i); if (el.objectReferenceValue == null) { recorders.DeleteArrayElementAtIndex(i); break; } } so.ApplyModifiedProperties(); EditorUtility.SetDirty(controller); } private void RegisterSelfToController(UXController controller) { var so = new SerializedObject(controller); var recorders = so.FindProperty("_recorders"); int newIndex = recorders.arraySize; recorders.InsertArrayElementAtIndex(newIndex); var el = recorders.GetArrayElementAtIndex(newIndex); el.objectReferenceValue = target; so.ApplyModifiedProperties(); EditorUtility.SetDirty(controller); } public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.Space(); var recorder = target as UXControllerStateRecorder; UXController prefabCtrl = GetPrefabStageController(); if (_id.intValue <= 0) { _id.intValue = Random.Range(10000000, 99999999); } EditorGUILayout.LabelField($"ID:{_id.intValue}", EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(); if (prefabCtrl != null && prefabCtrl != _controller.objectReferenceValue) { _controller.objectReferenceValue = prefabCtrl; } if (!prefabCtrl.HasRecorder(recorder)) { RegisterSelfToController(prefabCtrl); } if (_controller.objectReferenceValue != null) { _cacheController = _controller.objectReferenceValue as UXController; } if (_controller.objectReferenceValue != null) { UXController ctl = _controller.objectReferenceValue as UXController; EditorGUILayout.LabelField($"{ctl.name} ({ctl.Controllers.Count} controllers)", EditorStyles.helpBox); } else EditorGUILayout.LabelField("(None)", EditorStyles.helpBox); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); Rect addRect = EditorGUILayout.GetControlRect(false, GUILayout.Height(22)); if (GUI.Button(addRect, "Add State")) { var provider = ScriptableObject.CreateInstance(); provider.Init(OnTypeSelected); var screenPos = GUIUtility.GUIToScreenPoint(new Vector2(addRect.x, addRect.yMax)); SearchWindow.Open(new SearchWindowContext(screenPos), provider); } EditorGUILayout.Space(); for (int i = 0; i < _stateEntriesProp.arraySize; i++) { var entryProp = _stateEntriesProp.GetArrayElementAtIndex(i); if (entryProp == null) continue; var stateProp = entryProp.FindPropertyRelative("State"); var controllerNameProp = entryProp.FindPropertyRelative("ControllerName"); var controllerIndexProp = entryProp.FindPropertyRelative("ControllerIndex"); if (stateProp == null) { EditorGUILayout.HelpBox($"Unable to find 'State' on entry {i}.", MessageType.Error); if (GUILayout.Button("Remove")) { _stateEntriesProp.DeleteArrayElementAtIndex(i); break; } continue; } EditorGUILayout.BeginVertical(EditorStyles.helpBox); var t = stateProp.managedReferenceValue.GetType(); var attr = t.GetCustomAttribute(); var tag = attr != null ? attr.StateName : t.Name; EditorGUILayout.LabelField($"State:{tag}", EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(); var controllerNames = GetControllerNamesForRecorder(recorder); int selIdx = -1; if (!string.IsNullOrEmpty(controllerNameProp.stringValue)) { selIdx = controllerNames.IndexOf(controllerNameProp.stringValue); } GUILayoutOption ctrlWidth = GUILayout.Width(140); if (controllerNames.Count > 0) { int newSel = EditorGUILayout.Popup(selIdx, controllerNames.ToArray(), ctrlWidth); if (newSel >= 0 && newSel < controllerNames.Count) controllerNameProp.stringValue = controllerNames[newSel]; } else { if (!string.IsNullOrEmpty(controllerNameProp.stringValue)) { EditorGUI.BeginDisabledGroup(true); EditorGUILayout.HelpBox($"绑定控制器 [{controllerNameProp.stringValue}] 不存在", MessageType.Error); EditorGUI.EndDisabledGroup(); } else { EditorGUILayout.HelpBox($"暂无控制器", MessageType.Warning); } } int ctrlLen = GetSelectedControllerLength(recorder, controllerNameProp.stringValue); if (ctrlLen <= 0) ctrlLen = 1; controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, ctrlLen - 1); string[] idxOptions = new string[ctrlLen]; for (int j = 0; j < ctrlLen; j++) idxOptions[j] = j.ToString(); int curIdx = Mathf.Clamp(controllerIndexProp.intValue, 0, ctrlLen - 1); if (controllerNames.Count > 0) { int newIdx = EditorGUILayout.Popup(curIdx, idxOptions, GUILayout.Width(60)); if (newIdx != curIdx) controllerIndexProp.intValue = newIdx; } GUILayout.FlexibleSpace(); if (GUILayout.Button(_removeIcon, GUILayout.Width(28), GUILayout.Height(20))) { _stateEntriesProp.DeleteArrayElementAtIndex(i); break; } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(4); if (stateProp.managedReferenceValue == null) { EditorGUILayout.HelpBox("State instance is null. Remove and re-add.", MessageType.Warning); } else { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField(new GUIContent("属性"), EditorStyles.boldLabel); SerializedClassDrawer.DrawSerializableProperty(stateProp); EditorGUILayout.EndVertical(); } bool valid = true; try { var inst = stateProp.managedReferenceValue as ControllerStateBase; if (inst != null) valid = inst.Valid(recorder); } catch (Exception ex) { valid = false; Debug.LogException(ex); } if (!valid) { EditorGUILayout.HelpBox($"当前状态核验失败 请查看{stateProp.managedReferenceValue.GetType().FullName}脚本Valid", MessageType.Error); } EditorGUILayout.EndVertical(); EditorGUILayout.Space(); } serializedObject.ApplyModifiedProperties(); } private void OnTypeSelected(Type type) { if (type == null) return; // 撤销支持 Undo.RecordObject(serializedObject.targetObject, "Add Controller State"); serializedObject.Update(); int idx = _stateEntriesProp.arraySize; _stateEntriesProp.InsertArrayElementAtIndex(idx); var entryProp = _stateEntriesProp.GetArrayElementAtIndex(idx); var stateProp = entryProp.FindPropertyRelative("State"); var controllerNameProp = entryProp.FindPropertyRelative("ControllerName"); var controllerIndexProp = entryProp.FindPropertyRelative("ControllerIndex"); var defaultValueProp = entryProp.FindPropertyRelative("DefaultValue"); ControllerStateBase instance = null; try { instance = (ControllerStateBase)Activator.CreateInstance(type); } catch (Exception ex) { Debug.LogException(ex); } if (instance != null) { var recorder = serializedObject.targetObject as UXControllerStateRecorder; instance.Init(recorder); } // 把实例写进 SerializedProperty(需保证你的 State 字段用了 [SerializeReference]) if (stateProp != null) stateProp.managedReferenceValue = instance; if (stateProp != null) stateProp.isExpanded = true; controllerNameProp.stringValue = ""; controllerIndexProp.intValue = 0; serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(serializedObject.targetObject); } private UXController GetPrefabStageController() { var stage = PrefabStageUtility.GetCurrentPrefabStage(); if (stage == null) return null; var root = stage.prefabContentsRoot; if (root == null) return null; return root.GetComponentInChildren(); } private List GetControllerNamesForRecorder(UXControllerStateRecorder recorder) { var names = new List(); var prefabCtrl = GetPrefabStageController(); if (prefabCtrl != null) { foreach (var cd in prefabCtrl.Controllers) names.Add(cd.Name); return names; } var assigned = recorder.Controller; if (assigned != null) { foreach (var cd in assigned.Controllers) names.Add(cd.Name); return names; } var inScene = recorder.gameObject.GetComponentInParent(); if (inScene != null) { foreach (var cd in inScene.Controllers) names.Add(cd.Name); return names; } return names; } private int GetSelectedControllerLength(UXControllerStateRecorder recorder, string controllerName) { var prefabCtrl = GetPrefabStageController(); if (prefabCtrl != null) { var ch = prefabCtrl.Controllers.ToList().Find(x => x.Name == controllerName); if (ch != null) return ch.Length; } var assigned = recorder.Controller; if (assigned != null) { var ch = assigned.Controllers.ToList().Find(x => x.Name == controllerName); if (ch != null) return ch.Length; } var inScene = recorder.gameObject.GetComponentInParent(); if (inScene != null) { var ch = inScene.Controllers.ToList().Find(x => x.Name == controllerName); if (ch != null) return ch.Length; } return 1; } } #endif