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

1122 lines
41 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 TMPro;
2025-12-01 16:44:19 +08:00
using UnityEditor.SceneManagement;
using UnityEngine.UI;
2025-12-01 16:44:19 +08:00
namespace AlicizaX.UI
2025-12-01 16:44:19 +08:00
{
[CustomEditor(typeof(UXControllerStateRecorder))]
public class UXControllerStateRecorderEditor : UnityEditor.Editor
2025-12-01 16:44:19 +08:00
{
private SerializedProperty _stateEntriesProp;
private SerializedProperty _controller;
private SerializedProperty _id;
private UXController _cacheController;
2025-12-01 16:44:19 +08:00
private Dictionary<int, bool> _stateFoldouts = new Dictionary<int, bool>();
private bool _showControllerInfo = true;
private bool _compactMode = false;
private GUIStyle _headerStyle;
private GUIStyle _toolbarStyle;
private GUIStyle _stateHeaderStyle;
private void OnEnable()
2025-12-01 16:44:19 +08:00
{
_controller = serializedObject.FindProperty("_controller");
_id = serializedObject.FindProperty("_id");
_stateEntriesProp = serializedObject.FindProperty("_stateEntries");
2025-12-01 16:44:19 +08:00
}
private void OnDestroy()
2025-12-01 16:44:19 +08:00
{
if (target == null)
2025-12-01 16:44:19 +08:00
{
UnRegisterSelf();
2025-12-01 16:44:19 +08:00
}
}
private void InitStyles()
{
if (_headerStyle == null)
{
_headerStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13,
fontStyle = FontStyle.Bold
};
}
2025-12-01 16:44:19 +08:00
if (_toolbarStyle == null)
{
_toolbarStyle = new GUIStyle(EditorStyles.toolbar)
{
fixedHeight = 25
};
}
2025-12-01 16:44:19 +08:00
if (_stateHeaderStyle == null)
{
_stateHeaderStyle = new GUIStyle(EditorStyles.foldout)
{
fontStyle = FontStyle.Bold
};
}
}
2025-12-01 16:44:19 +08:00
public override void OnInspectorGUI()
{
InitStyles();
serializedObject.Update();
2025-12-01 16:44:19 +08:00
var recorder = target as UXControllerStateRecorder;
UXController prefabCtrl = GetPrefabStageController();
2025-12-01 16:44:19 +08:00
recorder.GenerateID();
2025-12-01 16:44:19 +08:00
// Header
DrawHeader(recorder);
EditorGUILayout.Space(5);
2025-12-01 16:44:19 +08:00
// Controller Reference
DrawControllerSection(recorder, prefabCtrl);
EditorGUILayout.Space(5);
2025-12-01 16:44:19 +08:00
// Toolbar
DrawToolbar();
EditorGUILayout.Space(5);
2025-12-01 16:44:19 +08:00
// States List
DrawStatesList(recorder);
2025-12-01 16:44:19 +08:00
serializedObject.ApplyModifiedProperties();
2025-12-01 16:44:19 +08:00
}
private void DrawHeader(UXControllerStateRecorder recorder)
2025-12-01 16:44:19 +08:00
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("UX 状态记录器", _headerStyle, GUILayout.Width(150));
EditorGUILayout.LabelField($"ID: {_id.intValue}", EditorStyles.miniLabel);
GUILayout.FlexibleSpace();
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("刷新", EditorStyles.miniButtonLeft, GUILayout.Width(60)))
{
recorder.Initialize();
EditorUtility.SetDirty(recorder);
}
Color oldColor = GUI.backgroundColor;
if (_compactMode) GUI.backgroundColor = Color.green;
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("紧凑", EditorStyles.miniButtonRight, GUILayout.Width(60)))
{
_compactMode = !_compactMode;
_stateFoldouts.Clear();
}
GUI.backgroundColor = oldColor;
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
2025-12-01 16:44:19 +08:00
}
private void DrawControllerSection(UXControllerStateRecorder recorder, UXController prefabCtrl)
2025-12-01 16:44:19 +08:00
{
if (prefabCtrl != null && prefabCtrl != _controller.objectReferenceValue)
{
_controller.objectReferenceValue = prefabCtrl;
recorder.SetController(prefabCtrl);
}
if (prefabCtrl != null && !prefabCtrl.HasRecorder(recorder))
{
RegisterSelfToController(prefabCtrl);
}
2025-12-01 16:44:19 +08:00
if (_controller.objectReferenceValue != null)
{
_cacheController = _controller.objectReferenceValue as UXController;
}
2025-12-01 16:44:19 +08:00
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
2025-12-01 16:44:19 +08:00
EditorGUILayout.BeginHorizontal();
2026-03-11 15:23:44 +08:00
_showControllerInfo = EditorGUILayout.Foldout(_showControllerInfo, "控制器引用", true);
EditorGUILayout.EndHorizontal();
// Controller dropdown
var availableControllers = GetAvailableControllers(recorder);
EditorGUILayout.BeginHorizontal();
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("控制器", GUILayout.Width(70));
if (availableControllers.Count > 0)
{
int currentIndex = -1;
var currentController = _controller.objectReferenceValue as UXController;
if (currentController != null)
{
currentIndex = availableControllers.IndexOf(currentController);
}
string[] options = new string[availableControllers.Count];
for (int i = 0; i < availableControllers.Count; i++)
{
var ctrl = availableControllers[i];
string path = GetGameObjectPath(ctrl.gameObject, recorder.gameObject);
options[i] = $"{ctrl.gameObject.name} ({path})";
}
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup(currentIndex, options);
if (EditorGUI.EndChangeCheck() && newIndex >= 0 && newIndex < availableControllers.Count)
{
_controller.objectReferenceValue = availableControllers[newIndex];
recorder.SetController(availableControllers[newIndex]);
if (!availableControllers[newIndex].HasRecorder(recorder))
{
RegisterSelfToController(availableControllers[newIndex]);
}
}
2025-12-01 16:44:19 +08:00
2026-03-11 15:23:44 +08:00
if (currentController != null && GUILayout.Button("选择", GUILayout.Width(60)))
{
Selection.activeGameObject = currentController.gameObject;
}
}
else
{
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("(在父对象中未找到控制器)", EditorStyles.miniLabel);
}
EditorGUILayout.EndHorizontal();
2025-12-01 16:44:19 +08:00
// Controller info
if (_showControllerInfo && _controller.objectReferenceValue != null)
{
UXController ctl = _controller.objectReferenceValue as UXController;
EditorGUI.indentLevel++;
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField($"控制器数量: {ctl.ControllerCount}", EditorStyles.miniLabel);
2025-12-01 16:44:19 +08:00
for (int i = 0; i < ctl.ControllerCount; i++)
{
var handle = ctl.GetControllerAt(i);
if (handle != null)
{
string desc = string.IsNullOrEmpty(handle.Description) ? "" : $" - {handle.Description}";
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField($" [{i}] {handle.Name} (长度: {handle.Length}){desc}", EditorStyles.miniLabel);
}
}
EditorGUI.indentLevel--;
}
if (_controller.objectReferenceValue == null)
{
2026-03-11 15:23:44 +08:00
EditorGUILayout.HelpBox("未分配控制器。请在父对象上添加 UXController。", MessageType.Warning);
}
EditorGUILayout.EndVertical();
}
private void DrawToolbar()
2025-12-01 16:44:19 +08:00
{
EditorGUILayout.BeginHorizontal(_toolbarStyle);
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("+ 添加状态", EditorStyles.toolbarButton, GUILayout.Width(100)))
{
ShowAddStateMenu();
}
EditorGUI.BeginDisabledGroup(_stateEntriesProp.arraySize == 0);
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("清空全部", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
2026-03-11 15:23:44 +08:00
if (EditorUtility.DisplayDialog("清空所有状态",
"移除所有状态?", "清空", "取消"))
{
ClearAllStates();
}
}
EditorGUI.EndDisabledGroup();
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("全部展开", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
for (int i = 0; i < _stateEntriesProp.arraySize; i++)
{
_stateFoldouts[i] = true;
}
}
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("全部折叠", EditorStyles.toolbarButton, GUILayout.Width(75)))
{
_stateFoldouts.Clear();
}
GUILayout.FlexibleSpace();
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField($"状态数: {_stateEntriesProp.arraySize}", EditorStyles.miniLabel, GUILayout.Width(70));
EditorGUILayout.EndHorizontal();
2025-12-01 16:44:19 +08:00
}
private void DrawStatesList(UXControllerStateRecorder recorder)
{
if (_stateEntriesProp.arraySize == 0)
{
2026-03-11 15:23:44 +08:00
EditorGUILayout.HelpBox("未定义状态。点击 '+ 添加状态' 开始。", MessageType.Info);
return;
}
for (int i = 0; i < _stateEntriesProp.arraySize; i++)
{
DrawStateEntry(recorder, i);
EditorGUILayout.Space(3);
}
}
2025-12-01 16:44:19 +08:00
private void DrawStateEntry(UXControllerStateRecorder recorder, int index)
2025-12-01 16:44:19 +08:00
{
if (index < 0 || index >= _stateEntriesProp.arraySize)
{
return;
}
var entryProp = _stateEntriesProp.GetArrayElementAtIndex(index);
if (entryProp == null) return;
2025-12-01 16:44:19 +08:00
var stateProp = entryProp.FindPropertyRelative("State");
var controllerNameProp = entryProp.FindPropertyRelative("ControllerName");
var controllerIndexProp = entryProp.FindPropertyRelative("ControllerIndex");
// Validate all properties
if (controllerNameProp == null || controllerIndexProp == null)
{
return;
}
if (stateProp == null || stateProp.managedReferenceValue == null)
2025-12-01 16:44:19 +08:00
{
DrawInvalidStateEntry(index);
return;
}
var stateInstance = stateProp.managedReferenceValue as ControllerStateBase;
var stateType = stateProp.managedReferenceValue.GetType();
var attr = stateType.GetCustomAttribute<ControlerStateNameAttribute>();
var stateName = attr != null ? attr.StateName : stateType.Name;
bool isValid = ValidateState(recorder, stateInstance);
bool isFolded = _stateFoldouts.ContainsKey(index) && _stateFoldouts[index];
// State box with validation color
Color boxColor = GUI.backgroundColor;
if (!isValid) GUI.backgroundColor = new Color(1f, 0.5f, 0.5f);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUI.backgroundColor = boxColor;
// Header
DrawStateHeader(index, stateName, ref isFolded);
_stateFoldouts[index] = isFolded;
// Compact mode: show binding info
if (_compactMode && !isFolded)
{
EditorGUI.indentLevel++;
try
2025-12-01 16:44:19 +08:00
{
string bindingText = string.IsNullOrEmpty(controllerNameProp.stringValue)
2026-03-11 15:23:44 +08:00
? "未绑定"
: $"绑定到: {controllerNameProp.stringValue}[{controllerIndexProp.intValue}]";
EditorGUILayout.LabelField(bindingText, EditorStyles.miniLabel);
2026-03-11 15:04:33 +08:00
// 在紧凑模式下也允许快速修改索引
if (!string.IsNullOrEmpty(controllerNameProp.stringValue))
{
EditorGUILayout.BeginHorizontal();
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("索引", GUILayout.Width(50));
2026-03-11 15:04:33 +08:00
int ctrlLen = GetSelectedControllerLength(recorder, controllerNameProp.stringValue);
if (ctrlLen <= 0) ctrlLen = 1;
string[] idxOptions = new string[ctrlLen];
for (int j = 0; j < ctrlLen; j++) idxOptions[j] = j.ToString();
EditorGUI.BeginChangeCheck();
int newIdx = EditorGUILayout.Popup(controllerIndexProp.intValue, idxOptions, GUILayout.Width(60));
if (EditorGUI.EndChangeCheck())
{
controllerIndexProp.intValue = newIdx;
}
EditorGUILayout.EndHorizontal();
}
2025-12-01 16:44:19 +08:00
}
catch (ObjectDisposedException)
{
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("绑定: (刷新中...)", EditorStyles.miniLabel);
}
EditorGUI.indentLevel--;
}
// Expanded content
if (isFolded && !_compactMode)
{
EditorGUI.indentLevel++;
// Binding
DrawStateBinding(recorder, controllerNameProp, controllerIndexProp);
EditorGUILayout.Space(3);
// Properties
if (stateInstance != null)
{
EditorGUILayout.BeginHorizontal();
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("属性", EditorStyles.boldLabel);
// Set to Current Value button
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("设为当前值", GUILayout.Width(110)))
{
SetStateToCurrentValue(recorder, stateInstance, stateProp);
}
2025-12-01 16:44:19 +08:00
EditorGUILayout.EndHorizontal();
SerializedClassDrawer.DrawSerializableProperty(stateProp);
// Description
EditorGUILayout.Space(2);
var description = stateInstance.GetDescription();
if (!string.IsNullOrEmpty(description))
{
EditorGUILayout.LabelField($" {description}", EditorStyles.miniLabel);
}
}
// Validation warning
if (!isValid)
{
2026-03-11 15:23:44 +08:00
EditorGUILayout.HelpBox("状态验证失败。请检查所需组件。", MessageType.Error);
}
EditorGUI.indentLevel--;
2025-12-01 16:44:19 +08:00
}
EditorGUILayout.EndVertical();
}
private void DrawStateHeader(int index, string stateName, ref bool isFolded)
{
2025-12-01 16:44:19 +08:00
EditorGUILayout.BeginHorizontal();
// Foldout with name
isFolded = EditorGUILayout.Foldout(isFolded, $"[{index}] {stateName}", true, _stateHeaderStyle);
2025-12-01 16:44:19 +08:00
GUILayout.FlexibleSpace();
// Buttons
if (GUILayout.Button("▲", EditorStyles.miniButtonLeft, GUILayout.Width(25)) && index > 0)
{
MoveStateUp(index);
}
if (GUILayout.Button("▼", EditorStyles.miniButtonMid, GUILayout.Width(25)) && index < _stateEntriesProp.arraySize - 1)
{
MoveStateDown(index);
}
if (GUILayout.Button("⎘", EditorStyles.miniButtonMid, GUILayout.Width(25)))
{
DuplicateState(index);
}
if (GUILayout.Button("×", EditorStyles.miniButtonRight, GUILayout.Width(25)))
{
DeleteStateAtIndex(index);
}
EditorGUILayout.EndHorizontal();
}
private void DrawStateBinding(UXControllerStateRecorder recorder,
SerializedProperty controllerNameProp, SerializedProperty controllerIndexProp)
{
// Safety check: ensure properties are valid
if (controllerNameProp == null || controllerIndexProp == null)
2025-12-01 16:44:19 +08:00
{
2026-03-11 15:23:44 +08:00
EditorGUILayout.HelpBox("绑定属性无效。请尝试刷新。", MessageType.Warning);
return;
2025-12-01 16:44:19 +08:00
}
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("绑定", EditorStyles.boldLabel);
var controllerNames = GetControllerNamesForRecorder(recorder);
// Controller dropdown
EditorGUILayout.BeginHorizontal();
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("控制器", GUILayout.Width(70));
2025-12-01 16:44:19 +08:00
if (controllerNames.Count > 0)
{
string currentName = "";
try
{
currentName = controllerNameProp.stringValue;
}
catch (ObjectDisposedException)
{
// Property has been disposed, exit gracefully
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
return;
}
int selIdx = controllerNames.IndexOf(currentName);
if (selIdx < 0) selIdx = 0;
EditorGUI.BeginChangeCheck();
int newSel = EditorGUILayout.Popup(selIdx, controllerNames.ToArray());
if (EditorGUI.EndChangeCheck() && newSel >= 0 && newSel < controllerNames.Count)
{
2025-12-01 16:44:19 +08:00
controllerNameProp.stringValue = controllerNames[newSel];
}
2025-12-01 16:44:19 +08:00
}
else
{
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("(无可用控制器)", EditorStyles.miniLabel);
}
EditorGUILayout.EndHorizontal();
// Index dropdown
if (controllerNames.Count > 0 && !string.IsNullOrEmpty(controllerNameProp.stringValue))
{
EditorGUILayout.BeginHorizontal();
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField("索引", GUILayout.Width(70));
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 = controllerIndexProp.intValue;
EditorGUI.BeginChangeCheck();
int newIdx = EditorGUILayout.Popup(curIdx, idxOptions, GUILayout.Width(100));
if (EditorGUI.EndChangeCheck())
2025-12-01 16:44:19 +08:00
{
controllerIndexProp.intValue = newIdx;
2025-12-01 16:44:19 +08:00
}
// Preview button
if (_controller.objectReferenceValue != null && !Application.isPlaying)
2025-12-01 16:44:19 +08:00
{
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("预览", GUILayout.Width(60)))
{
PreviewState(controllerNameProp.stringValue, controllerIndexProp.intValue);
}
2025-12-01 16:44:19 +08:00
}
EditorGUILayout.EndHorizontal();
2025-12-01 16:44:19 +08:00
}
EditorGUILayout.EndVertical();
}
private void DrawInvalidStateEntry(int index)
{
Color oldColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(1f, 0.5f, 0.5f);
2025-12-01 16:44:19 +08:00
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUI.backgroundColor = oldColor;
2025-12-01 16:44:19 +08:00
EditorGUILayout.BeginHorizontal();
2026-03-11 15:23:44 +08:00
EditorGUILayout.LabelField($"[{index}] 无效状态", EditorStyles.boldLabel);
2026-03-11 15:23:44 +08:00
if (GUILayout.Button("移除", GUILayout.Width(60)))
2025-12-01 16:44:19 +08:00
{
DeleteStateAtIndex(index);
2025-12-01 16:44:19 +08:00
}
EditorGUILayout.EndHorizontal();
2025-12-01 16:44:19 +08:00
2026-03-11 15:23:44 +08:00
EditorGUILayout.HelpBox("状态数据已损坏。请移除并重新添加。", MessageType.Error);
2025-12-01 16:44:19 +08:00
EditorGUILayout.EndVertical();
}
2025-12-01 16:44:19 +08:00
private void DeleteStateAtIndex(int index)
{
if (index < 0 || index >= _stateEntriesProp.arraySize)
2025-12-01 16:44:19 +08:00
{
Debug.LogWarning($"Invalid state index: {index}");
return;
2025-12-01 16:44:19 +08:00
}
2026-03-11 15:23:44 +08:00
Undo.RecordObject(serializedObject.targetObject, "删除状态");
2025-12-01 16:44:19 +08:00
// Get the entry before deleting
var entryProp = _stateEntriesProp.GetArrayElementAtIndex(index);
var stateProp = entryProp?.FindPropertyRelative("State");
2025-12-01 16:44:19 +08:00
// If the state reference is not null, we need to delete twice
// First deletion clears the reference, second removes the array element
if (stateProp != null && stateProp.managedReferenceValue != null)
2025-12-01 16:44:19 +08:00
{
stateProp.managedReferenceValue = null;
2025-12-01 16:44:19 +08:00
}
// Now delete the array element
_stateEntriesProp.DeleteArrayElementAtIndex(index);
// Clean up foldout dictionary
_stateFoldouts.Remove(index);
// Rebuild foldout dictionary with corrected indices
var newFoldouts = new Dictionary<int, bool>();
foreach (var kvp in _stateFoldouts)
2025-12-01 16:44:19 +08:00
{
if (kvp.Key < index)
{
newFoldouts[kvp.Key] = kvp.Value;
}
else if (kvp.Key > index)
{
newFoldouts[kvp.Key - 1] = kvp.Value;
}
2025-12-01 16:44:19 +08:00
}
_stateFoldouts = newFoldouts;
2025-12-01 16:44:19 +08:00
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(serializedObject.targetObject);
// Exit GUI to prevent using disposed SerializedProperty
GUIUtility.ExitGUI();
}
private void ClearAllStates()
{
2026-03-11 15:23:44 +08:00
Undo.RecordObject(serializedObject.targetObject, "清空所有状态");
// Clear all managed references first
for (int i = _stateEntriesProp.arraySize - 1; i >= 0; i--)
2025-12-01 16:44:19 +08:00
{
var entryProp = _stateEntriesProp.GetArrayElementAtIndex(i);
var stateProp = entryProp?.FindPropertyRelative("State");
if (stateProp != null && stateProp.managedReferenceValue != null)
{
stateProp.managedReferenceValue = null;
}
2025-12-01 16:44:19 +08:00
}
// Clear the array
_stateEntriesProp.ClearArray();
_stateFoldouts.Clear();
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(serializedObject.targetObject);
// Exit GUI to prevent using disposed SerializedProperty
GUIUtility.ExitGUI();
}
private void PreviewState(string controllerName, int index)
{
if (_controller.objectReferenceValue != null)
2025-12-01 16:44:19 +08:00
{
var ctl = _controller.objectReferenceValue as UXController;
ctl.SetControllerIndex(controllerName, index);
EditorUtility.SetDirty(ctl);
SceneView.RepaintAll();
2025-12-01 16:44:19 +08:00
}
}
2025-12-01 16:44:19 +08:00
private void DuplicateState(int index)
{
if (index < 0 || index >= _stateEntriesProp.arraySize)
2025-12-01 16:44:19 +08:00
{
Debug.LogWarning($"Invalid state index: {index}");
return;
2025-12-01 16:44:19 +08:00
}
2026-03-11 15:23:44 +08:00
Undo.RecordObject(serializedObject.targetObject, "复制状态");
_stateEntriesProp.InsertArrayElementAtIndex(index);
// Update foldout dictionary
var newFoldouts = new Dictionary<int, bool>();
foreach (var kvp in _stateFoldouts)
{
if (kvp.Key <= index)
{
newFoldouts[kvp.Key] = kvp.Value;
}
else
{
newFoldouts[kvp.Key + 1] = kvp.Value;
}
}
_stateFoldouts = newFoldouts;
_stateFoldouts[index + 1] = true;
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(serializedObject.targetObject);
// Exit GUI to prevent using disposed SerializedProperty
GUIUtility.ExitGUI();
2025-12-01 16:44:19 +08:00
}
private void MoveStateUp(int index)
{
if (index <= 0 || index >= _stateEntriesProp.arraySize)
{
return;
}
2025-12-01 16:44:19 +08:00
2026-03-11 15:23:44 +08:00
Undo.RecordObject(serializedObject.targetObject, "上移状态");
2025-12-01 16:44:19 +08:00
_stateEntriesProp.MoveArrayElement(index, index - 1);
// Update foldout dictionary
if (_stateFoldouts.ContainsKey(index))
{
bool wasFolded = _stateFoldouts[index];
_stateFoldouts.Remove(index);
_stateFoldouts[index - 1] = wasFolded;
}
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(serializedObject.targetObject);
// Exit GUI to prevent using disposed SerializedProperty
GUIUtility.ExitGUI();
}
private void MoveStateDown(int index)
2025-12-01 16:44:19 +08:00
{
if (index < 0 || index >= _stateEntriesProp.arraySize - 1)
{
return;
}
2026-03-11 15:23:44 +08:00
Undo.RecordObject(serializedObject.targetObject, "下移状态");
_stateEntriesProp.MoveArrayElement(index, index + 1);
// Update foldout dictionary
if (_stateFoldouts.ContainsKey(index))
{
bool wasFolded = _stateFoldouts[index];
_stateFoldouts.Remove(index);
_stateFoldouts[index + 1] = wasFolded;
}
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(serializedObject.targetObject);
// Exit GUI to prevent using disposed SerializedProperty
GUIUtility.ExitGUI();
2025-12-01 16:44:19 +08:00
}
private bool ValidateState(UXControllerStateRecorder recorder, ControllerStateBase stateInstance)
2025-12-01 16:44:19 +08:00
{
if (stateInstance == null) return false;
try
{
return stateInstance.Valid(recorder);
}
catch
{
return false;
}
2025-12-01 16:44:19 +08:00
}
private void ShowAddStateMenu()
2025-12-01 16:44:19 +08:00
{
var provider = ScriptableObject.CreateInstance<StateTypeSearchProvider>();
provider.Init(OnTypeSelected);
SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(Event.current.mousePosition)), provider);
2025-12-01 16:44:19 +08:00
}
private void OnTypeSelected(Type type)
{
if (type == null) return;
2025-12-01 16:44:19 +08:00
2026-03-11 15:23:44 +08:00
Undo.RecordObject(serializedObject.targetObject, "添加控制器状态");
serializedObject.Update();
2025-12-01 16:44:19 +08:00
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");
2025-12-01 16:44:19 +08:00
ControllerStateBase instance = null;
try
{
instance = (ControllerStateBase)Activator.CreateInstance(type);
}
catch (Exception ex)
{
Debug.LogException(ex);
return;
}
2026-03-11 15:04:33 +08:00
var recorder = serializedObject.targetObject as UXControllerStateRecorder;
if (instance != null)
{
instance.Init(recorder);
}
if (stateProp != null)
{
stateProp.managedReferenceValue = instance;
stateProp.isExpanded = true;
}
2026-03-11 15:04:33 +08:00
var controllerNames = GetControllerNamesForRecorder(recorder);
if (controllerNames.Count > 0)
{
controllerNameProp.stringValue = controllerNames[0];
controllerIndexProp.intValue = 0;
}
else
{
controllerNameProp.stringValue = "";
controllerIndexProp.intValue = 0;
}
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(serializedObject.targetObject);
_stateFoldouts[idx] = true;
}
private void UnRegisterSelf()
2025-12-01 16:44:19 +08:00
{
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);
}
}
so.ApplyModifiedProperties();
EditorUtility.SetDirty(controller);
2025-12-01 16:44:19 +08:00
}
private void RegisterSelfToController(UXController controller)
2025-12-01 16:44:19 +08:00
{
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);
2025-12-01 16:44:19 +08:00
}
private UXController GetPrefabStageController()
2025-12-01 16:44:19 +08:00
{
var stage = PrefabStageUtility.GetCurrentPrefabStage();
if (stage == null) return null;
var root = stage.prefabContentsRoot;
if (root == null) return null;
return root.GetComponentInChildren<UXController>();
2025-12-01 16:44:19 +08:00
}
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;
}
2025-12-01 16:44:19 +08:00
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)
2025-12-01 16:44:19 +08:00
{
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;
2025-12-01 16:44:19 +08:00
}
private List<UXController> GetAvailableControllers(UXControllerStateRecorder recorder)
2025-12-01 16:44:19 +08:00
{
var controllers = new List<UXController>();
// Search upwards through all parent transforms
Transform current = recorder.transform.parent;
while (current != null)
{
var ctrl = current.GetComponent<UXController>();
if (ctrl != null && !controllers.Contains(ctrl))
{
controllers.Add(ctrl);
}
current = current.parent;
}
return controllers;
2025-12-01 16:44:19 +08:00
}
private string GetGameObjectPath(GameObject target, GameObject from)
2025-12-01 16:44:19 +08:00
{
if (target == null || from == null) return "";
List<string> path = new List<string>();
Transform current = from.transform.parent;
while (current != null)
{
if (current.gameObject == target)
{
return path.Count == 0 ? "Parent" : string.Join("/", path);
}
path.Insert(0, "..");
current = current.parent;
}
return "Unknown";
2025-12-01 16:44:19 +08:00
}
private void SetStateToCurrentValue(UXControllerStateRecorder recorder, ControllerStateBase state, SerializedProperty stateProp)
{
if (recorder == null || state == null) return;
2026-03-11 15:23:44 +08:00
Undo.RecordObject(recorder, "设置状态为当前值");
// Use reflection to set current values based on state type
var stateType = state.GetType();
if (state is TransformPositionState)
{
if (recorder.TryGetComponent<RectTransform>(out var rect))
{
var posField = stateType.GetField("_position", BindingFlags.NonPublic | BindingFlags.Instance);
if (posField != null)
{
posField.SetValue(state, rect.anchoredPosition);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
}
else if (state is TransformScaleState)
{
if (recorder.transform != null)
{
var scaleField = stateType.GetField("_scale", BindingFlags.NonPublic | BindingFlags.Instance);
if (scaleField != null)
{
scaleField.SetValue(state, recorder.transform.localScale);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
}
else if (state is TransformRotationState)
{
if (recorder.transform != null)
{
var rotField = stateType.GetField("_rotation", BindingFlags.NonPublic | BindingFlags.Instance);
if (rotField != null)
{
rotField.SetValue(state, recorder.transform.localEulerAngles);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
}
else if (state is GraphicColorState)
{
if (recorder.TryGetComponent<Graphic>(out var graphic))
{
var colorField = stateType.GetField("_color", BindingFlags.NonPublic | BindingFlags.Instance);
if (colorField != null)
{
colorField.SetValue(state, graphic.color);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
}
else if (state is GraphicMaterialState)
{
if (recorder.TryGetComponent<Graphic>(out var graphic))
{
var matField = stateType.GetField("_material", BindingFlags.NonPublic | BindingFlags.Instance);
if (matField != null)
{
matField.SetValue(state, graphic.material);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
}
else if (state is ImageSpriteState)
{
if (recorder.TryGetComponent<Image>(out var image))
{
var spriteField = stateType.GetField("_sprite", BindingFlags.NonPublic | BindingFlags.Instance);
if (spriteField != null)
{
spriteField.SetValue(state, image.sprite);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
}
else if (state is TextContentState)
{
string currentText = "";
if (recorder.TryGetComponent<Text>(out var text))
{
currentText = text.text;
}
else if (recorder.TryGetComponent<TextMeshProUGUI>(out var tmp))
{
currentText = tmp.text;
}
var textField = stateType.GetField("_text", BindingFlags.NonPublic | BindingFlags.Instance);
if (textField != null)
{
textField.SetValue(state, currentText);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
else if (state is TextColorState)
{
Color currentColor = Color.white;
if (recorder.TryGetComponent<Text>(out var text))
{
currentColor = text.color;
}
else if (recorder.TryGetComponent<TextMeshProUGUI>(out var tmp))
{
currentColor = tmp.color;
}
var colorField = stateType.GetField("_color", BindingFlags.NonPublic | BindingFlags.Instance);
if (colorField != null)
{
colorField.SetValue(state, currentColor);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
else if (state is CanvasGroupAlphaState)
{
if (recorder.TryGetComponent<CanvasGroup>(out var canvasGroup))
{
var alphaField = stateType.GetField("_alpha", BindingFlags.NonPublic | BindingFlags.Instance);
if (alphaField != null)
{
alphaField.SetValue(state, canvasGroup.alpha);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
}
else if (state is CanvasGroupInteractableState)
{
if (recorder.TryGetComponent<CanvasGroup>(out var canvasGroup))
{
var interField = stateType.GetField("_interactable", BindingFlags.NonPublic | BindingFlags.Instance);
var blockField = stateType.GetField("_blocksRaycasts", BindingFlags.NonPublic | BindingFlags.Instance);
if (interField != null && blockField != null)
{
interField.SetValue(state, canvasGroup.interactable);
blockField.SetValue(state, canvasGroup.blocksRaycasts);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
}
else if (state is GameObjectActiveState)
{
var activeField = stateType.GetField("_active", BindingFlags.NonPublic | BindingFlags.Instance);
if (activeField != null)
{
activeField.SetValue(state, recorder.gameObject.activeSelf);
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
EditorUtility.SetDirty(recorder);
}
}
Debug.Log($"Set {stateType.Name} to current value");
}
2025-12-01 16:44:19 +08:00
}
}
#endif