com.alicizax.unity.ui.exten.../Editor/UX/Controller/UXControllerStateRecorderEditor.cs

359 lines
12 KiB
C#
Raw Normal View History

2025-12-01 16:44:19 +08:00
#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<StateTypeSearchProvider>();
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<ControlerStateNameAttribute>();
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<UXController>();
}
private List<string> GetControllerNamesForRecorder(UXControllerStateRecorder recorder)
{
var names = new List<string>();
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<UXController>();
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<UXController>();
if (inScene != null)
{
var ch = inScene.Controllers.ToList().Find(x => x.Name == controllerName);
if (ch != null) return ch.Length;
}
return 1;
}
}
#endif