1122 lines
41 KiB
C#
1122 lines
41 KiB
C#
#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;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEngine.UI;
|
||
|
||
namespace AlicizaX.UI
|
||
{
|
||
[CustomEditor(typeof(UXControllerStateRecorder))]
|
||
public class UXControllerStateRecorderEditor : UnityEditor.Editor
|
||
{
|
||
private SerializedProperty _stateEntriesProp;
|
||
private SerializedProperty _controller;
|
||
private SerializedProperty _id;
|
||
private UXController _cacheController;
|
||
|
||
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()
|
||
{
|
||
_controller = serializedObject.FindProperty("_controller");
|
||
_id = serializedObject.FindProperty("_id");
|
||
_stateEntriesProp = serializedObject.FindProperty("_stateEntries");
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (target == null)
|
||
{
|
||
UnRegisterSelf();
|
||
}
|
||
}
|
||
|
||
private void InitStyles()
|
||
{
|
||
if (_headerStyle == null)
|
||
{
|
||
_headerStyle = new GUIStyle(EditorStyles.boldLabel)
|
||
{
|
||
fontSize = 13,
|
||
fontStyle = FontStyle.Bold
|
||
};
|
||
}
|
||
|
||
if (_toolbarStyle == null)
|
||
{
|
||
_toolbarStyle = new GUIStyle(EditorStyles.toolbar)
|
||
{
|
||
fixedHeight = 25
|
||
};
|
||
}
|
||
|
||
if (_stateHeaderStyle == null)
|
||
{
|
||
_stateHeaderStyle = new GUIStyle(EditorStyles.foldout)
|
||
{
|
||
fontStyle = FontStyle.Bold
|
||
};
|
||
}
|
||
}
|
||
|
||
public override void OnInspectorGUI()
|
||
{
|
||
InitStyles();
|
||
serializedObject.Update();
|
||
|
||
var recorder = target as UXControllerStateRecorder;
|
||
UXController prefabCtrl = GetPrefabStageController();
|
||
|
||
recorder.GenerateID();
|
||
|
||
// Header
|
||
DrawHeader(recorder);
|
||
EditorGUILayout.Space(5);
|
||
|
||
// Controller Reference
|
||
DrawControllerSection(recorder, prefabCtrl);
|
||
EditorGUILayout.Space(5);
|
||
|
||
// Toolbar
|
||
DrawToolbar();
|
||
EditorGUILayout.Space(5);
|
||
|
||
// States List
|
||
DrawStatesList(recorder);
|
||
|
||
serializedObject.ApplyModifiedProperties();
|
||
}
|
||
|
||
private void DrawHeader(UXControllerStateRecorder recorder)
|
||
{
|
||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
EditorGUILayout.LabelField("UX 状态记录器", _headerStyle, GUILayout.Width(150));
|
||
EditorGUILayout.LabelField($"ID: {_id.intValue}", EditorStyles.miniLabel);
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
if (GUILayout.Button("刷新", EditorStyles.miniButtonLeft, GUILayout.Width(60)))
|
||
{
|
||
recorder.Initialize();
|
||
EditorUtility.SetDirty(recorder);
|
||
}
|
||
|
||
Color oldColor = GUI.backgroundColor;
|
||
if (_compactMode) GUI.backgroundColor = Color.green;
|
||
if (GUILayout.Button("紧凑", EditorStyles.miniButtonRight, GUILayout.Width(60)))
|
||
{
|
||
_compactMode = !_compactMode;
|
||
_stateFoldouts.Clear();
|
||
}
|
||
GUI.backgroundColor = oldColor;
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawControllerSection(UXControllerStateRecorder recorder, UXController prefabCtrl)
|
||
{
|
||
if (prefabCtrl != null && prefabCtrl != _controller.objectReferenceValue)
|
||
{
|
||
_controller.objectReferenceValue = prefabCtrl;
|
||
recorder.SetController(prefabCtrl);
|
||
}
|
||
|
||
if (prefabCtrl != null && !prefabCtrl.HasRecorder(recorder))
|
||
{
|
||
RegisterSelfToController(prefabCtrl);
|
||
}
|
||
|
||
if (_controller.objectReferenceValue != null)
|
||
{
|
||
_cacheController = _controller.objectReferenceValue as UXController;
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
_showControllerInfo = EditorGUILayout.Foldout(_showControllerInfo, "控制器引用", true);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// Controller dropdown
|
||
var availableControllers = GetAvailableControllers(recorder);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
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]);
|
||
}
|
||
}
|
||
|
||
if (currentController != null && GUILayout.Button("选择", GUILayout.Width(60)))
|
||
{
|
||
Selection.activeGameObject = currentController.gameObject;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.LabelField("(在父对象中未找到控制器)", EditorStyles.miniLabel);
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// Controller info
|
||
if (_showControllerInfo && _controller.objectReferenceValue != null)
|
||
{
|
||
UXController ctl = _controller.objectReferenceValue as UXController;
|
||
|
||
EditorGUI.indentLevel++;
|
||
EditorGUILayout.LabelField($"控制器数量: {ctl.ControllerCount}", EditorStyles.miniLabel);
|
||
|
||
for (int i = 0; i < ctl.ControllerCount; i++)
|
||
{
|
||
var handle = ctl.GetControllerAt(i);
|
||
if (handle != null)
|
||
{
|
||
string desc = string.IsNullOrEmpty(handle.Description) ? "" : $" - {handle.Description}";
|
||
EditorGUILayout.LabelField($" [{i}] {handle.Name} (长度: {handle.Length}){desc}", EditorStyles.miniLabel);
|
||
}
|
||
}
|
||
EditorGUI.indentLevel--;
|
||
}
|
||
|
||
if (_controller.objectReferenceValue == null)
|
||
{
|
||
EditorGUILayout.HelpBox("未分配控制器。请在父对象上添加 UXController。", MessageType.Warning);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawToolbar()
|
||
{
|
||
EditorGUILayout.BeginHorizontal(_toolbarStyle);
|
||
|
||
if (GUILayout.Button("+ 添加状态", EditorStyles.toolbarButton, GUILayout.Width(100)))
|
||
{
|
||
ShowAddStateMenu();
|
||
}
|
||
|
||
EditorGUI.BeginDisabledGroup(_stateEntriesProp.arraySize == 0);
|
||
if (GUILayout.Button("清空全部", EditorStyles.toolbarButton, GUILayout.Width(70)))
|
||
{
|
||
if (EditorUtility.DisplayDialog("清空所有状态",
|
||
"移除所有状态?", "清空", "取消"))
|
||
{
|
||
ClearAllStates();
|
||
}
|
||
}
|
||
EditorGUI.EndDisabledGroup();
|
||
|
||
if (GUILayout.Button("全部展开", EditorStyles.toolbarButton, GUILayout.Width(70)))
|
||
{
|
||
for (int i = 0; i < _stateEntriesProp.arraySize; i++)
|
||
{
|
||
_stateFoldouts[i] = true;
|
||
}
|
||
}
|
||
|
||
if (GUILayout.Button("全部折叠", EditorStyles.toolbarButton, GUILayout.Width(75)))
|
||
{
|
||
_stateFoldouts.Clear();
|
||
}
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
EditorGUILayout.LabelField($"状态数: {_stateEntriesProp.arraySize}", EditorStyles.miniLabel, GUILayout.Width(70));
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void DrawStatesList(UXControllerStateRecorder recorder)
|
||
{
|
||
if (_stateEntriesProp.arraySize == 0)
|
||
{
|
||
EditorGUILayout.HelpBox("未定义状态。点击 '+ 添加状态' 开始。", MessageType.Info);
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < _stateEntriesProp.arraySize; i++)
|
||
{
|
||
DrawStateEntry(recorder, i);
|
||
EditorGUILayout.Space(3);
|
||
}
|
||
}
|
||
|
||
private void DrawStateEntry(UXControllerStateRecorder recorder, int index)
|
||
{
|
||
if (index < 0 || index >= _stateEntriesProp.arraySize)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var entryProp = _stateEntriesProp.GetArrayElementAtIndex(index);
|
||
if (entryProp == null) return;
|
||
|
||
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)
|
||
{
|
||
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
|
||
{
|
||
string bindingText = string.IsNullOrEmpty(controllerNameProp.stringValue)
|
||
? "未绑定"
|
||
: $"绑定到: {controllerNameProp.stringValue}[{controllerIndexProp.intValue}]";
|
||
EditorGUILayout.LabelField(bindingText, EditorStyles.miniLabel);
|
||
|
||
// 在紧凑模式下也允许快速修改索引
|
||
if (!string.IsNullOrEmpty(controllerNameProp.stringValue))
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("索引", GUILayout.Width(50));
|
||
|
||
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();
|
||
}
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
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();
|
||
EditorGUILayout.LabelField("属性", EditorStyles.boldLabel);
|
||
|
||
// Set to Current Value button
|
||
if (GUILayout.Button("设为当前值", GUILayout.Width(110)))
|
||
{
|
||
SetStateToCurrentValue(recorder, stateInstance, stateProp);
|
||
}
|
||
|
||
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)
|
||
{
|
||
EditorGUILayout.HelpBox("状态验证失败。请检查所需组件。", MessageType.Error);
|
||
}
|
||
|
||
EditorGUI.indentLevel--;
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawStateHeader(int index, string stateName, ref bool isFolded)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
// Foldout with name
|
||
isFolded = EditorGUILayout.Foldout(isFolded, $"[{index}] {stateName}", true, _stateHeaderStyle);
|
||
|
||
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)
|
||
{
|
||
EditorGUILayout.HelpBox("绑定属性无效。请尝试刷新。", MessageType.Warning);
|
||
return;
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||
EditorGUILayout.LabelField("绑定", EditorStyles.boldLabel);
|
||
|
||
var controllerNames = GetControllerNamesForRecorder(recorder);
|
||
|
||
// Controller dropdown
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("控制器", GUILayout.Width(70));
|
||
|
||
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)
|
||
{
|
||
controllerNameProp.stringValue = controllerNames[newSel];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.LabelField("(无可用控制器)", EditorStyles.miniLabel);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// Index dropdown
|
||
if (controllerNames.Count > 0 && !string.IsNullOrEmpty(controllerNameProp.stringValue))
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
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())
|
||
{
|
||
controllerIndexProp.intValue = newIdx;
|
||
}
|
||
|
||
// Preview button
|
||
if (_controller.objectReferenceValue != null && !Application.isPlaying)
|
||
{
|
||
if (GUILayout.Button("预览", GUILayout.Width(60)))
|
||
{
|
||
PreviewState(controllerNameProp.stringValue, controllerIndexProp.intValue);
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawInvalidStateEntry(int index)
|
||
{
|
||
Color oldColor = GUI.backgroundColor;
|
||
GUI.backgroundColor = new Color(1f, 0.5f, 0.5f);
|
||
|
||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||
GUI.backgroundColor = oldColor;
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField($"[{index}] 无效状态", EditorStyles.boldLabel);
|
||
|
||
if (GUILayout.Button("移除", GUILayout.Width(60)))
|
||
{
|
||
DeleteStateAtIndex(index);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.HelpBox("状态数据已损坏。请移除并重新添加。", MessageType.Error);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DeleteStateAtIndex(int index)
|
||
{
|
||
if (index < 0 || index >= _stateEntriesProp.arraySize)
|
||
{
|
||
Debug.LogWarning($"Invalid state index: {index}");
|
||
return;
|
||
}
|
||
|
||
Undo.RecordObject(serializedObject.targetObject, "删除状态");
|
||
|
||
// Get the entry before deleting
|
||
var entryProp = _stateEntriesProp.GetArrayElementAtIndex(index);
|
||
var stateProp = entryProp?.FindPropertyRelative("State");
|
||
|
||
// 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)
|
||
{
|
||
stateProp.managedReferenceValue = null;
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
if (kvp.Key < index)
|
||
{
|
||
newFoldouts[kvp.Key] = kvp.Value;
|
||
}
|
||
else if (kvp.Key > index)
|
||
{
|
||
newFoldouts[kvp.Key - 1] = kvp.Value;
|
||
}
|
||
}
|
||
_stateFoldouts = newFoldouts;
|
||
|
||
serializedObject.ApplyModifiedProperties();
|
||
EditorUtility.SetDirty(serializedObject.targetObject);
|
||
|
||
// Exit GUI to prevent using disposed SerializedProperty
|
||
GUIUtility.ExitGUI();
|
||
}
|
||
|
||
private void ClearAllStates()
|
||
{
|
||
Undo.RecordObject(serializedObject.targetObject, "清空所有状态");
|
||
|
||
// Clear all managed references first
|
||
for (int i = _stateEntriesProp.arraySize - 1; i >= 0; i--)
|
||
{
|
||
var entryProp = _stateEntriesProp.GetArrayElementAtIndex(i);
|
||
var stateProp = entryProp?.FindPropertyRelative("State");
|
||
if (stateProp != null && stateProp.managedReferenceValue != null)
|
||
{
|
||
stateProp.managedReferenceValue = null;
|
||
}
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
var ctl = _controller.objectReferenceValue as UXController;
|
||
ctl.SetControllerIndex(controllerName, index);
|
||
EditorUtility.SetDirty(ctl);
|
||
SceneView.RepaintAll();
|
||
}
|
||
}
|
||
|
||
private void DuplicateState(int index)
|
||
{
|
||
if (index < 0 || index >= _stateEntriesProp.arraySize)
|
||
{
|
||
Debug.LogWarning($"Invalid state index: {index}");
|
||
return;
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
private void MoveStateUp(int index)
|
||
{
|
||
if (index <= 0 || index >= _stateEntriesProp.arraySize)
|
||
{
|
||
return;
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
private void MoveStateDown(int index)
|
||
{
|
||
if (index < 0 || index >= _stateEntriesProp.arraySize - 1)
|
||
{
|
||
return;
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
private bool ValidateState(UXControllerStateRecorder recorder, ControllerStateBase stateInstance)
|
||
{
|
||
if (stateInstance == null) return false;
|
||
try
|
||
{
|
||
return stateInstance.Valid(recorder);
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private void ShowAddStateMenu()
|
||
{
|
||
var provider = ScriptableObject.CreateInstance<StateTypeSearchProvider>();
|
||
provider.Init(OnTypeSelected);
|
||
SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(Event.current.mousePosition)), provider);
|
||
}
|
||
|
||
private void OnTypeSelected(Type type)
|
||
{
|
||
if (type == null) return;
|
||
|
||
Undo.RecordObject(serializedObject.targetObject, "添加控制器状态");
|
||
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");
|
||
|
||
ControllerStateBase instance = null;
|
||
try
|
||
{
|
||
instance = (ControllerStateBase)Activator.CreateInstance(type);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogException(ex);
|
||
return;
|
||
}
|
||
var recorder = serializedObject.targetObject as UXControllerStateRecorder;
|
||
if (instance != null)
|
||
{
|
||
instance.Init(recorder);
|
||
}
|
||
|
||
if (stateProp != null)
|
||
{
|
||
stateProp.managedReferenceValue = instance;
|
||
stateProp.isExpanded = true;
|
||
}
|
||
|
||
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()
|
||
{
|
||
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);
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
private List<UXController> GetAvailableControllers(UXControllerStateRecorder recorder)
|
||
{
|
||
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;
|
||
}
|
||
|
||
private string GetGameObjectPath(GameObject target, GameObject from)
|
||
{
|
||
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";
|
||
}
|
||
|
||
private void SetStateToCurrentValue(UXControllerStateRecorder recorder, ControllerStateBase state, SerializedProperty stateProp)
|
||
{
|
||
if (recorder == null || state == null) return;
|
||
|
||
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");
|
||
}
|
||
}
|
||
}
|
||
#endif
|