重构UXController

1.重新优化UXBinding设计
2.优化编辑器预览 更贴合FGUI效果。
This commit is contained in:
b4lie 2026-03-21 19:57:49 +08:00
parent f6cb7dde18
commit 2752fe9e1c
39 changed files with 1710 additions and 2702 deletions

View File

@ -1,86 +0,0 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using AlicizaX.UI;
using AlicizaX.UI.Runtime;
public class StateTypeSearchProvider : ScriptableObject, ISearchWindowProvider
{
private Action<Type> _onSelect;
private List<SearchTreeEntry> _entries;
public void Init(Action<Type> onSelect)
{
_onSelect = onSelect;
BuildEntries();
}
private void BuildEntries()
{
_entries = new List<SearchTreeEntry>();
_entries.Add(new SearchTreeGroupEntry(new GUIContent("States")));
var all = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a =>
{
try { return a.GetTypes(); }
catch { return new Type[0]; }
})
.Where(t => typeof(ControllerStateBase).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
.Select(t =>
{
var attr = t.GetCustomAttribute<ControlerStateNameAttribute>();
var tag = attr != null ? attr.StateName : t.Name;
return (type: t, tag);
})
.OrderBy(x => x.tag)
.ToArray();
var groups = new Dictionary<string, List<(Type type, string fullTag, string display)>>();
foreach (var item in all)
{
string[] parts = item.tag.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
string g = parts.Length > 0 ? parts[0] : "Other";
string child = parts.Length > 1 ? string.Join("/", parts.Skip(1)) : (parts.Length == 1 ? parts[0] : item.type.Name);
string display = $"{child}";
if (!groups.TryGetValue(g, out var list)) { list = new List<(Type, string, string)>(); groups[g] = list; }
list.Add((item.type, item.tag, display));
}
foreach (var kv in groups.OrderBy(k => k.Key))
{
_entries.Add(new SearchTreeGroupEntry(new GUIContent(kv.Key)) { level = 1 });
foreach (var child in kv.Value.OrderBy(c => c.fullTag))
{
var entry = new SearchTreeEntry(new GUIContent(child.display)) { level = 2, userData = child.type };
_entries.Add(entry);
}
}
}
public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
{
if (_entries == null) BuildEntries();
return _entries;
}
public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context)
{
var t = entry.userData as Type;
if (t != null)
{
_onSelect?.Invoke(t);
return true;
}
return false;
}
}
#endif

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 37183e7bcfe8411fba14fb9eb6bdfe9d
timeCreated: 1764136414

View File

@ -0,0 +1,416 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace AlicizaX.UI
{
[CustomEditor(typeof(UXBinding))]
public sealed class UXBindingEditor : UnityEditor.Editor
{
private SerializedProperty _controllerProp;
private SerializedProperty _entriesProp;
private readonly Dictionary<int, bool> _foldouts = new Dictionary<int, bool>();
private readonly List<UXBindingProperty> _supportedProperties = new List<UXBindingProperty>();
private void OnEnable()
{
_controllerProp = serializedObject.FindProperty("_controller");
_entriesProp = serializedObject.FindProperty("_entries");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
var binding = (UXBinding)target;
DrawHeader(binding);
EditorGUILayout.Space(6f);
DrawControllerField(binding);
EditorGUILayout.Space(6f);
DrawEntries(binding);
serializedObject.ApplyModifiedProperties();
}
private void DrawHeader(UXBinding binding)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("UX Binding", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"Target: {binding.gameObject.name}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"Rules: {_entriesProp.arraySize}", EditorStyles.miniLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Add Rule"))
{
AddEntry(binding);
}
if (GUILayout.Button("Capture Defaults"))
{
binding.CaptureDefaults();
EditorUtility.SetDirty(binding);
}
if (GUILayout.Button("Reset To Defaults"))
{
binding.ResetToDefaults();
EditorUtility.SetDirty(binding);
SceneView.RepaintAll();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawControllerField(UXBinding binding)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Controller", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
UXController newController = (UXController)EditorGUILayout.ObjectField("Reference", binding.Controller, typeof(UXController), true);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(binding, "Change UX Binding Controller");
binding.SetController(newController);
_controllerProp.objectReferenceValue = newController;
EditorUtility.SetDirty(binding);
}
if (binding.Controller == null)
{
EditorGUILayout.HelpBox("Assign a UXController on this object or one of its parents.", MessageType.Warning);
}
else
{
EditorGUILayout.LabelField($"Bound To: {binding.Controller.name}", EditorStyles.miniLabel);
}
EditorGUILayout.EndVertical();
}
private void DrawEntries(UXBinding binding)
{
if (_entriesProp.arraySize == 0)
{
EditorGUILayout.HelpBox("No rules yet. Add one and choose a controller + property pair.", MessageType.Info);
return;
}
UXBindingPropertyUtility.GetSupportedProperties(binding.gameObject, _supportedProperties);
for (int i = 0; i < _entriesProp.arraySize; i++)
{
DrawEntry(binding, i);
EditorGUILayout.Space(4f);
}
}
private void DrawEntry(UXBinding binding, int index)
{
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
UXBindingProperty property = (UXBindingProperty)propertyProp.enumValueIndex;
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
string label = UXBindingPropertyUtility.GetMetadata(property).DisplayName;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {label}", true);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Preview", EditorStyles.miniButtonLeft, GUILayout.Width(60f)))
{
binding.PreviewEntry(index);
SceneView.RepaintAll();
}
if (GUILayout.Button("X", EditorStyles.miniButtonRight, GUILayout.Width(24f)))
{
DeleteEntry(binding, index);
}
EditorGUILayout.EndHorizontal();
_foldouts[index] = expanded;
if (expanded)
{
DrawControllerSelector(binding, controllerIdProp, controllerIndexProp);
DrawPropertySelector(entryProp, binding.gameObject, propertyProp);
property = (UXBindingProperty)propertyProp.enumValueIndex;
UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(property);
EditorGUILayout.Space(2f);
EditorGUILayout.LabelField("Value", EditorStyles.boldLabel);
DrawValueField(valueProp, metadata, "Matched Value");
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Use Current"))
{
binding.CaptureEntryValue(index);
EditorUtility.SetDirty(binding);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(2f);
EditorGUILayout.PropertyField(fallbackModeProp, new GUIContent("Fallback"));
UXBindingFallbackMode fallbackMode = (UXBindingFallbackMode)fallbackModeProp.enumValueIndex;
if (fallbackMode == UXBindingFallbackMode.UseCustomValue)
{
DrawValueField(fallbackValueProp, metadata, "Fallback Value");
if (GUILayout.Button("Use Current As Fallback"))
{
binding.CaptureEntryFallbackValue(index);
EditorUtility.SetDirty(binding);
}
}
if (!_supportedProperties.Contains(property))
{
EditorGUILayout.HelpBox("This property is not supported by the components on the current GameObject.", MessageType.Error);
}
}
EditorGUILayout.EndVertical();
}
private void DrawControllerSelector(UXBinding binding, SerializedProperty controllerIdProp, SerializedProperty controllerIndexProp)
{
UXController controller = binding.Controller;
if (controller == null || controller.Controllers.Count == 0)
{
EditorGUILayout.HelpBox("Create a controller definition first.", MessageType.Info);
return;
}
string[] names = new string[controller.Controllers.Count];
int selectedController = 0;
for (int i = 0; i < controller.Controllers.Count; i++)
{
UXController.ControllerDefinition definition = controller.Controllers[i];
names[i] = definition.Name;
if (definition.Id == controllerIdProp.stringValue)
{
selectedController = i;
}
}
EditorGUI.BeginChangeCheck();
selectedController = EditorGUILayout.Popup("Controller", selectedController, names);
if (EditorGUI.EndChangeCheck())
{
controllerIdProp.stringValue = controller.Controllers[selectedController].Id;
controllerIndexProp.intValue = 0;
}
UXController.ControllerDefinition selectedDefinition = controller.Controllers[selectedController];
int maxIndex = Mathf.Max(1, selectedDefinition.Length);
controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, maxIndex - 1);
string[] indexNames = new string[maxIndex];
for (int i = 0; i < maxIndex; i++)
{
indexNames[i] = i.ToString();
}
controllerIndexProp.intValue = EditorGUILayout.Popup("Index", controllerIndexProp.intValue, indexNames);
}
private void DrawPropertySelector(SerializedProperty entryProp, GameObject targetObject, SerializedProperty propertyProp)
{
var options = new List<UXBindingProperty>(_supportedProperties);
UXBindingProperty current = (UXBindingProperty)propertyProp.enumValueIndex;
if (!options.Contains(current))
{
options.Add(current);
}
string[] displayNames = new string[options.Count];
int selectedIndex = 0;
for (int i = 0; i < options.Count; i++)
{
UXBindingProperty option = options[i];
UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(option);
bool supported = UXBindingPropertyUtility.IsSupported(targetObject, option);
displayNames[i] = supported ? metadata.DisplayName : $"{metadata.DisplayName} (Unsupported)";
if (option == current)
{
selectedIndex = i;
}
}
EditorGUI.BeginChangeCheck();
selectedIndex = EditorGUILayout.Popup("Property", selectedIndex, displayNames);
if (EditorGUI.EndChangeCheck())
{
propertyProp.enumValueIndex = (int)options[selectedIndex];
entryProp.FindPropertyRelative("_hasCapturedDefault").boolValue = false;
entryProp.FindPropertyRelative("_capturedProperty").enumValueIndex = propertyProp.enumValueIndex;
ResetValue(entryProp.FindPropertyRelative("_capturedDefault"));
ApplyDefaultFallbackForProperty(entryProp, (UXBindingProperty)propertyProp.enumValueIndex);
}
}
private void DrawValueField(SerializedProperty valueProp, UXBindingPropertyMetadata metadata, string label)
{
switch (metadata.ValueKind)
{
case UXBindingValueKind.Boolean:
{
SerializedProperty boolProp = valueProp.FindPropertyRelative("_boolValue");
boolProp.boolValue = EditorGUILayout.Toggle(label, boolProp.boolValue);
break;
}
case UXBindingValueKind.Float:
{
SerializedProperty floatProp = valueProp.FindPropertyRelative("_floatValue");
floatProp.floatValue = EditorGUILayout.FloatField(label, floatProp.floatValue);
break;
}
case UXBindingValueKind.String:
{
SerializedProperty stringProp = valueProp.FindPropertyRelative("_stringValue");
EditorGUILayout.LabelField(label);
stringProp.stringValue = EditorGUILayout.TextArea(stringProp.stringValue, GUILayout.MinHeight(54f));
break;
}
case UXBindingValueKind.Color:
{
SerializedProperty colorProp = valueProp.FindPropertyRelative("_colorValue");
colorProp.colorValue = EditorGUILayout.ColorField(label, colorProp.colorValue);
break;
}
case UXBindingValueKind.Vector2:
{
SerializedProperty vector2Prop = valueProp.FindPropertyRelative("_vector2Value");
vector2Prop.vector2Value = EditorGUILayout.Vector2Field(label, vector2Prop.vector2Value);
break;
}
case UXBindingValueKind.Vector3:
{
SerializedProperty vector3Prop = valueProp.FindPropertyRelative("_vector3Value");
vector3Prop.vector3Value = EditorGUILayout.Vector3Field(label, vector3Prop.vector3Value);
break;
}
case UXBindingValueKind.ObjectReference:
{
SerializedProperty objectProp = valueProp.FindPropertyRelative("_objectValue");
objectProp.objectReferenceValue = EditorGUILayout.ObjectField(
label,
objectProp.objectReferenceValue,
metadata.ObjectReferenceType,
false);
break;
}
}
}
private void AddEntry(UXBinding binding)
{
int index = _entriesProp.arraySize;
_entriesProp.InsertArrayElementAtIndex(index);
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
SerializedProperty capturedDefaultProp = entryProp.FindPropertyRelative("_capturedDefault");
SerializedProperty hasCapturedDefaultProp = entryProp.FindPropertyRelative("_hasCapturedDefault");
SerializedProperty capturedPropertyProp = entryProp.FindPropertyRelative("_capturedProperty");
controllerIdProp.stringValue = string.Empty;
controllerIndexProp.intValue = 0;
propertyProp.enumValueIndex = (int)UXBindingProperty.GameObjectActive;
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault;
ResetValue(valueProp);
ResetValue(fallbackValueProp);
ResetValue(capturedDefaultProp);
hasCapturedDefaultProp.boolValue = false;
capturedPropertyProp.enumValueIndex = (int)UXBindingProperty.GameObjectActive;
ApplyDefaultFallbackForProperty(entryProp, UXBindingProperty.GameObjectActive);
if (binding.Controller != null && binding.Controller.Controllers.Count > 0)
{
controllerIdProp.stringValue = binding.Controller.Controllers[0].Id;
}
_foldouts[index] = true;
}
private static void ResetValue(SerializedProperty valueProp)
{
valueProp.FindPropertyRelative("_boolValue").boolValue = false;
valueProp.FindPropertyRelative("_floatValue").floatValue = 0f;
valueProp.FindPropertyRelative("_stringValue").stringValue = string.Empty;
valueProp.FindPropertyRelative("_colorValue").colorValue = Color.white;
valueProp.FindPropertyRelative("_vector2Value").vector2Value = Vector2.zero;
valueProp.FindPropertyRelative("_vector3Value").vector3Value = Vector3.zero;
valueProp.FindPropertyRelative("_objectValue").objectReferenceValue = null;
}
private void DeleteEntry(UXBinding binding, int index)
{
if (index < 0 || index >= _entriesProp.arraySize)
{
return;
}
Undo.RecordObject(binding, "Delete UX Binding Rule");
_entriesProp.DeleteArrayElementAtIndex(index);
CleanupFoldouts(index);
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(binding);
GUIUtility.ExitGUI();
}
private void CleanupFoldouts(int removedIndex)
{
_foldouts.Remove(removedIndex);
var remapped = new Dictionary<int, bool>();
foreach (var pair in _foldouts)
{
int nextIndex = pair.Key > removedIndex ? pair.Key - 1 : pair.Key;
remapped[nextIndex] = pair.Value;
}
_foldouts.Clear();
foreach (var pair in remapped)
{
_foldouts[pair.Key] = pair.Value;
}
}
private static void ApplyDefaultFallbackForProperty(SerializedProperty entryProp, UXBindingProperty property)
{
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
if (property == UXBindingProperty.GameObjectActive)
{
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.UseCustomValue;
fallbackValueProp.FindPropertyRelative("_boolValue").boolValue = false;
return;
}
if ((UXBindingFallbackMode)fallbackModeProp.enumValueIndex == UXBindingFallbackMode.UseCustomValue)
{
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault;
ResetValue(fallbackValueProp);
}
}
}
}
#endif

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 39cce3f21841b1f4aa459d3909001607
guid: 034c6da8785dcdb47a6f4350865dec51
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,424 +1,266 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using AlicizaX.UI;
using System.Collections.Generic;
namespace AlicizaX.UI
{
[CustomEditor(typeof(UXController))]
public class UXControllerEditor : UnityEditor.Editor
public sealed class UXControllerEditor : UnityEditor.Editor
{
private SerializedProperty _controllersProp;
private bool _showRecorders = true;
private bool _previewMode = false;
private Dictionary<int, bool> _controllerFoldouts = new Dictionary<int, bool>();
private GUIStyle _headerStyle;
private GUIStyle _toolbarStyle;
private GUIStyle _controllerHeaderStyle;
private SerializedProperty _bindingsProp;
private readonly Dictionary<int, bool> _foldouts = new Dictionary<int, bool>();
private void OnEnable()
{
_controllersProp = serializedObject.FindProperty("_controllers");
}
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 (_controllerHeaderStyle == null)
{
_controllerHeaderStyle = new GUIStyle(EditorStyles.foldout)
{
fontStyle = FontStyle.Bold
};
}
_bindingsProp = serializedObject.FindProperty("_bindings");
}
public override void OnInspectorGUI()
{
InitStyles();
serializedObject.Update();
var controller = target as UXController;
var controller = (UXController)target;
// Header
DrawHeader();
EditorGUILayout.Space(5);
// Preview Mode
DrawPreviewMode(controller);
EditorGUILayout.Space(5);
// Toolbar
DrawToolbar();
EditorGUILayout.Space(5);
// Controllers List
DrawControllersList(controller);
EditorGUILayout.Space(5);
// Recorders Section
DrawRecorders(controller);
DrawHeader(controller);
EditorGUILayout.Space(6f);
DrawControllers(controller);
EditorGUILayout.Space(6f);
DrawBindings();
serializedObject.ApplyModifiedProperties();
}
private void DrawHeader()
private void DrawHeader(UXController controller)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("UX Controller", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"Controllers: {_controllersProp.arraySize}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"Bindings: {_bindingsProp.arraySize}", EditorStyles.miniLabel);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("UX 控制器", _headerStyle, GUILayout.Width(130));
EditorGUILayout.LabelField($"控制器数量: {_controllersProp.arraySize}", EditorStyles.miniLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("+ 添加控制器", GUILayout.Width(110)))
if (GUILayout.Button("Add Controller"))
{
AddNewController();
AddController();
}
if (GUILayout.Button("Reset Preview"))
{
controller.ResetAllControllers();
EditorUtility.SetDirty(controller);
SceneView.RepaintAll();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawPreviewMode(UXController controller)
{
Color oldColor = GUI.backgroundColor;
if (_previewMode) GUI.backgroundColor = new Color(0.5f, 1f, 0.5f);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUI.backgroundColor = oldColor;
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
_previewMode = EditorGUILayout.Toggle("预览模式", _previewMode);
if (EditorGUI.EndChangeCheck())
{
if (!_previewMode)
{
ResetAllControllers(controller);
}
}
if (_previewMode)
{
GUILayout.FlexibleSpace();
if (GUILayout.Button("重置全部", GUILayout.Width(80)))
{
ResetAllControllers(controller);
}
}
EditorGUILayout.EndHorizontal();
if (_previewMode)
{
EditorGUILayout.HelpBox("预览模式已激活。点击状态按钮进行预览。", MessageType.Info);
}
EditorGUILayout.EndVertical();
}
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(_toolbarStyle);
if (GUILayout.Button("全部展开", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
for (int i = 0; i < _controllersProp.arraySize; i++)
{
_controllerFoldouts[i] = true;
}
}
if (GUILayout.Button("全部折叠", EditorStyles.toolbarButton, GUILayout.Width(75)))
{
_controllerFoldouts.Clear();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
private void DrawControllersList(UXController controller)
private void DrawControllers(UXController controller)
{
if (_controllersProp.arraySize == 0)
{
EditorGUILayout.HelpBox("未定义控制器。点击 '+ 添加控制器' 创建一个。", MessageType.Info);
EditorGUILayout.HelpBox("No controllers yet. Add one to start driving bindings.", MessageType.Info);
return;
}
for (int i = 0; i < _controllersProp.arraySize; i++)
{
DrawControllerEntry(controller, i);
EditorGUILayout.Space(3);
DrawController(controller, i);
EditorGUILayout.Space(4f);
}
}
private void DrawControllerEntry(UXController controller, int index)
private void DrawController(UXController controller, int index)
{
var element = _controllersProp.GetArrayElementAtIndex(index);
var nameProp = element.FindPropertyRelative("_name");
var lengthProp = element.FindPropertyRelative("_length");
var descProp = element.FindPropertyRelative("_description");
SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index);
SerializedProperty idProp = entryProp.FindPropertyRelative("_id");
SerializedProperty nameProp = entryProp.FindPropertyRelative("_name");
SerializedProperty lengthProp = entryProp.FindPropertyRelative("_length");
SerializedProperty descriptionProp = entryProp.FindPropertyRelative("_description");
if (!_controllerFoldouts.ContainsKey(index))
{
_controllerFoldouts[index] = false;
}
bool isFolded = _controllerFoldouts[index];
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
// Header
DrawControllerHeader(index, nameProp, ref isFolded);
_controllerFoldouts[index] = isFolded;
// Expanded Content
if (isFolded)
{
EditorGUI.indentLevel++;
// Name
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("名称", GUILayout.Width(70));
nameProp.stringValue = EditorGUILayout.TextField(nameProp.stringValue);
EditorGUILayout.EndHorizontal();
// Length
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("长度", GUILayout.Width(70));
lengthProp.intValue = Mathf.Max(1, EditorGUILayout.IntField(lengthProp.intValue));
EditorGUILayout.EndHorizontal();
// Description
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("描述", GUILayout.Width(70));
descProp.stringValue = EditorGUILayout.TextField(descProp.stringValue);
EditorGUILayout.EndHorizontal();
// Preview
if (_previewMode && !Application.isPlaying)
{
EditorGUILayout.Space(5);
DrawControllerPreview(controller, index, lengthProp.intValue);
}
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
}
private void DrawControllerHeader(int index, SerializedProperty nameProp, ref bool isFolded)
{
EditorGUILayout.BeginHorizontal();
// Foldout
isFolded = EditorGUILayout.Foldout(isFolded, $"[{index}] {nameProp.stringValue}", true, _controllerHeaderStyle);
expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {nameProp.stringValue}", true);
GUILayout.FlexibleSpace();
// Buttons
if (GUILayout.Button("▲", EditorStyles.miniButtonLeft, GUILayout.Width(25)) && index > 0)
if (GUILayout.Button("Up", EditorStyles.miniButtonLeft, GUILayout.Width(34f)) && index > 0)
{
_controllersProp.MoveArrayElement(index, index - 1);
GUIUtility.ExitGUI();
}
if (GUILayout.Button("", EditorStyles.miniButtonMid, GUILayout.Width(25)) && index < _controllersProp.arraySize - 1)
if (GUILayout.Button("Down", EditorStyles.miniButtonMid, GUILayout.Width(44f)) && index < _controllersProp.arraySize - 1)
{
_controllersProp.MoveArrayElement(index, index + 1);
GUIUtility.ExitGUI();
}
if (GUILayout.Button("⎘", EditorStyles.miniButtonMid, GUILayout.Width(25)))
if (GUILayout.Button("X", EditorStyles.miniButtonRight, GUILayout.Width(24f)))
{
DuplicateController(index);
GUIUtility.ExitGUI();
DeleteController(controller, index);
}
if (GUILayout.Button("×", EditorStyles.miniButtonRight, GUILayout.Width(25)))
{
if (EditorUtility.DisplayDialog("删除控制器",
$"删除控制器 '{nameProp.stringValue}'?", "删除", "取消"))
{
DeleteController(index);
}
}
EditorGUILayout.EndHorizontal();
}
private void DrawControllerPreview(UXController controller, int controllerIndex, int length)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("预览", EditorStyles.boldLabel);
var handle = controller.GetControllerAt(controllerIndex);
if (handle != null)
{
int currentIndex = handle.SelectedIndex;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"状态: {currentIndex}", GUILayout.Width(60));
string[] options = new string[length];
for (int i = 0; i < length; i++)
{
options[i] = i.ToString();
}
EditorGUI.BeginChangeCheck();
int newIndex = GUILayout.SelectionGrid(currentIndex, options, Mathf.Min(length, 6));
if (EditorGUI.EndChangeCheck())
{
controller.SetControllerIndexInternal(controllerIndex, newIndex);
EditorUtility.SetDirty(controller);
SceneView.RepaintAll();
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
}
private void DrawRecorders(UXController controller)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
_showRecorders = EditorGUILayout.Foldout(_showRecorders, $"状态记录器 ({controller.Recorders.Count})", true);
EditorGUILayout.EndHorizontal();
if (_showRecorders)
_foldouts[index] = expanded;
if (expanded)
{
if (controller.Recorders.Count == 0)
{
EditorGUILayout.HelpBox("未附加状态记录器。", MessageType.Info);
}
else
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(nameProp, new GUIContent("Name"));
lengthProp.intValue = Mathf.Max(1, EditorGUILayout.IntField("Length", Mathf.Max(1, lengthProp.intValue)));
EditorGUILayout.PropertyField(descriptionProp, new GUIContent("Description"));
for (int i = 0; i < controller.Recorders.Count; i++)
{
DrawRecorderEntry(controller.Recorders[i], i);
}
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField("Id", idProp.stringValue);
EditorGUI.EndDisabledGroup();
EditorGUI.indentLevel--;
UXController.ControllerDefinition definition = controller.GetControllerAt(index);
if (definition != null)
{
DrawPreview(controller, definition);
}
}
EditorGUILayout.EndVertical();
}
private void DrawRecorderEntry(UXControllerStateRecorder recorder, int index)
private void DrawPreview(UXController controller, UXController.ControllerDefinition definition)
{
if (recorder == null) return;
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
EditorGUILayout.LabelField($"[{index}]", GUILayout.Width(30));
EditorGUILayout.LabelField($"ID: {recorder.ID}", GUILayout.Width(100));
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(recorder, typeof(UXControllerStateRecorder), true);
EditorGUI.EndDisabledGroup();
EditorGUILayout.LabelField($"状态数: {recorder.StateEntries.Count}", GUILayout.Width(60));
if (GUILayout.Button("选择", GUILayout.Width(60)))
int currentIndex = Mathf.Max(0, definition.SelectedIndex);
int length = Mathf.Max(1, definition.Length);
string[] options = new string[length];
for (int i = 0; i < length; i++)
{
Selection.activeGameObject = recorder.gameObject;
options[i] = i.ToString();
}
EditorGUILayout.EndHorizontal();
EditorGUI.BeginChangeCheck();
int newIndex = GUILayout.SelectionGrid(currentIndex, options, Mathf.Min(length, 6));
if (EditorGUI.EndChangeCheck())
{
controller.SetControllerIndex(definition.Id, newIndex);
EditorUtility.SetDirty(controller);
SceneView.RepaintAll();
}
}
private void AddNewController()
private void DrawBindings()
{
_controllersProp.arraySize++;
var newElement = _controllersProp.GetArrayElementAtIndex(_controllersProp.arraySize - 1);
newElement.FindPropertyRelative("_name").stringValue = $"Controller{_controllersProp.arraySize}";
newElement.FindPropertyRelative("_length").intValue = 2;
newElement.FindPropertyRelative("_description").stringValue = "";
_controllerFoldouts[_controllersProp.arraySize - 1] = true;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Registered Bindings", EditorStyles.boldLabel);
if (_bindingsProp.arraySize == 0)
{
EditorGUILayout.HelpBox("No UXBinding components are registered under this controller.", MessageType.Info);
}
else
{
for (int i = 0; i < _bindingsProp.arraySize; i++)
{
SerializedProperty bindingProp = _bindingsProp.GetArrayElementAtIndex(i);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField($"Binding {i}", bindingProp.objectReferenceValue, typeof(UXBinding), true);
EditorGUI.EndDisabledGroup();
}
}
EditorGUILayout.EndVertical();
}
private void DuplicateController(int index)
private void AddController()
{
int index = _controllersProp.arraySize;
_controllersProp.InsertArrayElementAtIndex(index);
var newProp = _controllersProp.GetArrayElementAtIndex(index + 1);
var nameProp = newProp.FindPropertyRelative("_name");
nameProp.stringValue += " 副本";
SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index);
entryProp.FindPropertyRelative("_id").stringValue = string.Empty;
entryProp.FindPropertyRelative("_name").stringValue = $"Controller {index + 1}";
entryProp.FindPropertyRelative("_length").intValue = 2;
entryProp.FindPropertyRelative("_description").stringValue = string.Empty;
_controllerFoldouts[index + 1] = true;
_foldouts[index] = true;
}
private void DeleteController(int index)
private void DeleteController(UXController controller, int index)
{
if (index < 0 || index >= _controllersProp.arraySize)
{
return;
}
Undo.RecordObject(serializedObject.targetObject, "删除控制器");
SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index);
string deletedControllerId = entryProp.FindPropertyRelative("_id").stringValue;
Undo.RecordObject(controller, "Delete UX Controller");
_controllersProp.DeleteArrayElementAtIndex(index);
_controllerFoldouts.Remove(index);
// Rebuild foldout dictionary with corrected indices
var newFoldouts = new Dictionary<int, bool>();
foreach (var kvp in _controllerFoldouts)
{
if (kvp.Key < index)
{
newFoldouts[kvp.Key] = kvp.Value;
}
else if (kvp.Key > index)
{
newFoldouts[kvp.Key - 1] = kvp.Value;
}
}
_controllerFoldouts = newFoldouts;
CleanupFoldouts(index);
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(serializedObject.targetObject);
ClearBindingReferences(controller, deletedControllerId);
EditorUtility.SetDirty(controller);
GUIUtility.ExitGUI();
}
private void ResetAllControllers(UXController controller)
private void CleanupFoldouts(int removedIndex)
{
for (int i = 0; i < controller.ControllerCount; i++)
_foldouts.Remove(removedIndex);
var remapped = new Dictionary<int, bool>();
foreach (var pair in _foldouts)
{
controller.SetControllerIndexInternal(i, 0);
int nextIndex = pair.Key > removedIndex ? pair.Key - 1 : pair.Key;
remapped[nextIndex] = pair.Value;
}
_foldouts.Clear();
foreach (var pair in remapped)
{
_foldouts[pair.Key] = pair.Value;
}
}
private static void ClearBindingReferences(UXController controller, string deletedControllerId)
{
if (controller == null || string.IsNullOrEmpty(deletedControllerId))
{
return;
}
IReadOnlyList<UXBinding> bindings = controller.Bindings;
for (int i = 0; i < bindings.Count; i++)
{
UXBinding binding = bindings[i];
if (binding == null)
{
continue;
}
var so = new SerializedObject(binding);
SerializedProperty entriesProp = so.FindProperty("_entries");
bool changed = false;
for (int entryIndex = 0; entryIndex < entriesProp.arraySize; entryIndex++)
{
SerializedProperty bindingEntry = entriesProp.GetArrayElementAtIndex(entryIndex);
SerializedProperty controllerIdProp = bindingEntry.FindPropertyRelative("_controllerId");
if (controllerIdProp.stringValue == deletedControllerId)
{
controllerIdProp.stringValue = string.Empty;
changed = true;
}
}
if (changed)
{
so.ApplyModifiedProperties();
EditorUtility.SetDirty(binding);
}
}
EditorUtility.SetDirty(controller);
SceneView.RepaintAll();
}
}
}
#endif

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2
guid: 627b7a51244f46ce8bcbd31e6cedf4f8
timeCreated: 1764127335
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,268 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace AlicizaX.UI
{
public sealed class UXControllerSceneOverlayWindow : EditorWindow
{
private const string OverlayEnabledKey = "AlicizaX.UI.UXControllerSceneOverlay.Enabled";
private const string OverlayAutoFocusKey = "AlicizaX.UI.UXControllerSceneOverlay.AutoFocus";
private const float OverlayWidth = 360f;
private const float OverlayMargin = 12f;
private static bool s_overlayEnabled;
private static bool s_autoFocusSceneView;
private static Vector2 s_scrollPosition;
private static bool s_registered;
[MenuItem("Window/UX/Controller Scene Overlay")]
public static void ShowWindow()
{
var window = GetWindow<UXControllerSceneOverlayWindow>("UX Controller Overlay");
window.minSize = new Vector2(320f, 140f);
window.Show();
EnsureRegistered();
}
[InitializeOnLoadMethod]
private static void Initialize()
{
s_overlayEnabled = EditorPrefs.GetBool(OverlayEnabledKey, false);
s_autoFocusSceneView = EditorPrefs.GetBool(OverlayAutoFocusKey, false);
EnsureRegistered();
}
private static void EnsureRegistered()
{
if (s_registered)
{
return;
}
SceneView.duringSceneGui += OnSceneGui;
s_registered = true;
}
private void OnGUI()
{
EditorGUILayout.LabelField("Scene View Overlay", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"When enabled, the Scene View will display all UXController components in the current loaded scenes and let you preview them directly.",
MessageType.Info);
EditorGUI.BeginChangeCheck();
bool overlayEnabled = EditorGUILayout.Toggle("Enable Overlay", s_overlayEnabled);
bool autoFocus = EditorGUILayout.Toggle("Focus SceneView On Open", s_autoFocusSceneView);
if (EditorGUI.EndChangeCheck())
{
s_overlayEnabled = overlayEnabled;
s_autoFocusSceneView = autoFocus;
EditorPrefs.SetBool(OverlayEnabledKey, s_overlayEnabled);
EditorPrefs.SetBool(OverlayAutoFocusKey, s_autoFocusSceneView);
SceneView.RepaintAll();
}
EditorGUILayout.Space(8f);
if (GUILayout.Button("Repaint Scene View"))
{
SceneView.RepaintAll();
}
if (GUILayout.Button("Open Scene View"))
{
SceneView.FocusWindowIfItsOpen<SceneView>();
}
}
private static void OnSceneGui(SceneView sceneView)
{
if (!s_overlayEnabled)
{
return;
}
if (s_autoFocusSceneView && sceneView != null)
{
s_autoFocusSceneView = false;
EditorPrefs.SetBool(OverlayAutoFocusKey, false);
sceneView.Focus();
}
List<UXController> controllers = GetSceneControllers();
Handles.BeginGUI();
float height = Mathf.Max(160f, sceneView.position.height - OverlayMargin * 2f);
Rect area = new Rect(
sceneView.position.width - OverlayWidth - OverlayMargin,
OverlayMargin,
OverlayWidth,
height);
GUILayout.BeginArea(area, EditorStyles.helpBox);
DrawOverlayContent(controllers);
GUILayout.EndArea();
Handles.EndGUI();
}
private static void DrawOverlayContent(List<UXController> controllers)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("UX Controllers", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Refresh", GUILayout.Width(64f)))
{
SceneView.RepaintAll();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(4f);
if (controllers.Count == 0)
{
EditorGUILayout.HelpBox("No UXController found in the current loaded scenes.", MessageType.Info);
return;
}
s_scrollPosition = EditorGUILayout.BeginScrollView(s_scrollPosition);
for (int i = 0; i < controllers.Count; i++)
{
DrawControllerCard(controllers[i], i);
EditorGUILayout.Space(4f);
}
EditorGUILayout.EndScrollView();
}
private static void DrawControllerCard(UXController controller, int index)
{
if (controller == null)
{
return;
}
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"{index + 1}. {controller.gameObject.name}", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Select", GUILayout.Width(52f)))
{
Selection.activeGameObject = controller.gameObject;
EditorGUIUtility.PingObject(controller.gameObject);
}
if (GUILayout.Button("Reset", GUILayout.Width(52f)))
{
controller.ResetAllControllers();
EditorUtility.SetDirty(controller);
SceneView.RepaintAll();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.LabelField(GetHierarchyPath(controller.transform), EditorStyles.miniLabel);
EditorGUILayout.LabelField($"Bindings: {controller.Bindings.Count}", EditorStyles.miniLabel);
for (int controllerIndex = 0; controllerIndex < controller.ControllerCount; controllerIndex++)
{
UXController.ControllerDefinition definition = controller.GetControllerAt(controllerIndex);
if (definition == null)
{
continue;
}
DrawDefinitionPreview(controller, definition);
}
EditorGUILayout.EndVertical();
}
private static void DrawDefinitionPreview(UXController controller, UXController.ControllerDefinition definition)
{
EditorGUILayout.Space(3f);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField(definition.Name, EditorStyles.boldLabel);
if (!string.IsNullOrWhiteSpace(definition.Description))
{
EditorGUILayout.LabelField(definition.Description, EditorStyles.miniLabel);
}
int currentIndex = Mathf.Max(0, definition.SelectedIndex);
int length = Mathf.Max(1, definition.Length);
string[] options = new string[length];
for (int i = 0; i < length; i++)
{
options[i] = i.ToString();
}
EditorGUI.BeginChangeCheck();
int newIndex = GUILayout.SelectionGrid(currentIndex, options, Mathf.Min(length, 5));
if (EditorGUI.EndChangeCheck())
{
controller.SetControllerIndex(definition.Id, newIndex);
EditorUtility.SetDirty(controller);
SceneView.RepaintAll();
}
EditorGUILayout.EndVertical();
}
private static List<UXController> GetSceneControllers()
{
var results = new List<UXController>();
UXController[] allControllers = Resources.FindObjectsOfTypeAll<UXController>();
for (int i = 0; i < allControllers.Length; i++)
{
UXController controller = allControllers[i];
if (controller == null)
{
continue;
}
if (EditorUtility.IsPersistent(controller))
{
continue;
}
if (!controller.gameObject.scene.IsValid() || !controller.gameObject.scene.isLoaded)
{
continue;
}
if ((controller.hideFlags & HideFlags.HideInHierarchy) != 0)
{
continue;
}
results.Add(controller);
}
results.Sort((left, right) => string.CompareOrdinal(GetHierarchyPath(left.transform), GetHierarchyPath(right.transform)));
return results;
}
private static string GetHierarchyPath(Transform target)
{
if (target == null)
{
return string.Empty;
}
string path = target.name;
Transform current = target.parent;
while (current != null)
{
path = $"{current.name}/{path}";
current = current.parent;
}
return path;
}
}
}
#endif

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 671716e6e16c2da419a6a7a374e699e6
guid: 7c62b8151e77e4b428e4e79a17d89e3b
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,210 +0,0 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace AlicizaX.UI
{
public class UXControllerSceneViewWindow : EditorWindow
{
private Vector2 _scrollPosition;
private bool _autoRefresh = false;
private double _lastRefreshTime;
[MenuItem("Window/UX/Controller Scene View")]
public static void ShowWindow()
{
var window = GetWindow<UXControllerSceneViewWindow>("UX Controller Viewer");
window.minSize = new Vector2(350, 400);
window.Show();
}
private void OnEnable()
{
_lastRefreshTime = EditorApplication.timeSinceStartup;
}
private void Update()
{
if (_autoRefresh && EditorApplication.timeSinceStartup - _lastRefreshTime > 0.5)
{
_lastRefreshTime = EditorApplication.timeSinceStartup;
Repaint();
}
}
private void OnGUI()
{
DrawToolbar();
EditorGUILayout.Space(10);
DrawControllers();
}
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60)))
{
Repaint();
}
_autoRefresh = GUILayout.Toggle(_autoRefresh, "Auto Refresh", EditorStyles.toolbarButton, GUILayout.Width(100));
GUILayout.FlexibleSpace();
if (GUILayout.Button("Reset All", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
ResetAllControllers();
}
EditorGUILayout.EndHorizontal();
}
private void DrawControllers()
{
var controllers = GameObject.FindObjectsOfType<UXController>();
if (controllers.Length == 0)
{
EditorGUILayout.HelpBox("No UXControllers found in the current scene.", MessageType.Info);
return;
}
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
foreach (var controller in controllers)
{
DrawControllerPanel(controller);
EditorGUILayout.Space(5);
}
EditorGUILayout.EndScrollView();
}
private void DrawControllerPanel(UXController controller)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
var boldLabelStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13
};
EditorGUILayout.LabelField(controller.name, boldLabelStyle);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Select", GUILayout.Width(70)))
{
Selection.activeGameObject = controller.gameObject;
SceneView.FrameLastActiveSceneView();
}
if (GUILayout.Button("Reset", GUILayout.Width(70)))
{
ResetController(controller);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
EditorGUILayout.LabelField($"Path: {GetGameObjectPath(controller.gameObject)}", EditorStyles.miniLabel);
EditorGUILayout.Space(5);
for (int i = 0; i < controller.ControllerCount; i++)
{
DrawControllerStatePanel(controller, i);
}
EditorGUILayout.Space(3);
EditorGUILayout.LabelField($"State Recorders: {controller.Recorders.Count}", EditorStyles.miniLabel);
EditorGUILayout.EndVertical();
}
private void DrawControllerStatePanel(UXController controller, int controllerIndex)
{
var handle = controller.GetControllerAt(controllerIndex);
if (handle == null) return;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(handle.Name, EditorStyles.boldLabel, GUILayout.Width(120));
if (!string.IsNullOrEmpty(handle.Description))
{
EditorGUILayout.LabelField($"({handle.Description})", EditorStyles.miniLabel);
}
EditorGUILayout.EndHorizontal();
int currentIndex = handle.SelectedIndex;
int length = handle.Length;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("State:", GUILayout.Width(50));
string[] options = new string[length];
for (int i = 0; i < length; i++)
{
options[i] = $"Index {i}";
}
EditorGUI.BeginChangeCheck();
int newIndex = GUILayout.SelectionGrid(currentIndex, options, Mathf.Min(length, 5));
if (EditorGUI.EndChangeCheck() && newIndex != currentIndex)
{
Undo.RecordObject(controller, "Change Controller State");
controller.SetControllerIndexInternal(controllerIndex, newIndex);
EditorUtility.SetDirty(controller);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void ResetController(UXController controller)
{
Undo.RecordObject(controller, "Reset Controller");
for (int i = 0; i < controller.ControllerCount; i++)
{
controller.SetControllerIndexInternal(i, 0);
}
EditorUtility.SetDirty(controller);
Repaint();
}
private void ResetAllControllers()
{
var controllers = GameObject.FindObjectsOfType<UXController>();
foreach (var controller in controllers)
{
ResetController(controller);
}
}
private string GetGameObjectPath(GameObject obj)
{
string path = obj.name;
Transform current = obj.transform.parent;
while (current != null)
{
path = current.name + "/" + path;
current = current.parent;
}
return path;
}
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: a754ad6b8cb341ada13bb87279b12b78
timeCreated: 1764134852

View File

@ -1,65 +0,0 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// Base class for all controller states
/// </summary>
public abstract class ControllerStateBase
{
/// <summary>
/// Initialize the state when added to a recorder
/// </summary>
public abstract void Init(UXControllerStateRecorder recorder);
/// <summary>
/// Execute the state when controller index changes
/// </summary>
/// <param name="recorder">The state recorder</param>
/// <param name="entryIndex">The index this state entry is bound to</param>
/// <param name="selectedIndex">The currently selected controller index</param>
public abstract void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectedIndex);
/// <summary>
/// Validate if the state can be properly executed
/// </summary>
public abstract bool Valid(UXControllerStateRecorder recorder);
/// <summary>
/// Get a human-readable description of this state
/// </summary>
public virtual string GetDescription()
{
return GetType().Name;
}
}
/// <summary>
/// Attribute to define display name for a controller state type
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ControlerStateNameAttribute : Attribute
{
public string StateName { get; }
public ControlerStateNameAttribute(string stateName)
{
StateName = stateName;
}
}
/// <summary>
/// Attribute to define if a state can be attached multiple times to the same recorder
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ControlerStateAttachTypeAttribute : Attribute
{
public bool Repeat { get; }
public ControlerStateAttachTypeAttribute(bool repeat)
{
Repeat = repeat;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 774b49ad28de40fda18249bd9a24e38b
timeCreated: 1763705196

View File

@ -1,44 +0,0 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// CanvasGroup 透明度状态控制
/// 控制 CanvasGroup 的 Alpha 值
/// </summary>
[Serializable]
[ControlerStateName("CanvasGroup/Alpha")]
[ControlerStateAttachType(true)]
public class CanvasGroupAlphaState : ControllerStateBase
{
[SerializeField] [Range(0f, 1f)] private float _alpha = 1f;
[HideInInspector] [SerializeField] private float _defaultAlpha;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.TryGetComponent<CanvasGroup>(out var canvasGroup))
{
_defaultAlpha = canvasGroup.alpha;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.TryGetComponent<CanvasGroup>(out var canvasGroup))
{
canvasGroup.alpha = (entryIndex == selectionIndex) ? _alpha : _defaultAlpha;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.GetComponent<CanvasGroup>() != null;
}
public override string GetDescription()
{
return $"匹配时: 透明度={_alpha:F2}, 默认={_defaultAlpha:F2}";
}
}
}

View File

@ -1,49 +0,0 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// CanvasGroup 交互状态控制
/// 控制 CanvasGroup 的可交互性和射线检测
/// </summary>
[Serializable]
[ControlerStateName("CanvasGroup/Interactable")]
[ControlerStateAttachType(true)]
public class CanvasGroupInteractableState : ControllerStateBase
{
[SerializeField] private bool _interactable = true;
[SerializeField] private bool _blocksRaycasts = true;
[HideInInspector] [SerializeField] private bool _defaultInteractable = true;
[HideInInspector] [SerializeField] private bool _defaultBlocksRaycasts = true;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.TryGetComponent<CanvasGroup>(out var canvasGroup))
{
_defaultInteractable = canvasGroup.interactable;
_defaultBlocksRaycasts = canvasGroup.blocksRaycasts;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.TryGetComponent<CanvasGroup>(out var canvasGroup))
{
bool isActive = (entryIndex == selectionIndex);
canvasGroup.interactable = isActive ? _interactable : _defaultInteractable;
canvasGroup.blocksRaycasts = isActive ? _blocksRaycasts : _defaultBlocksRaycasts;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.GetComponent<CanvasGroup>() != null;
}
public override string GetDescription()
{
return $"匹配时: 可交互={_interactable}, 射线检测={_blocksRaycasts}";
}
}
}

View File

@ -1,45 +0,0 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// GameObject 激活状态控制
/// 控制对象的显示/隐藏
/// </summary>
[Serializable]
[ControlerStateName("GameObject/Active")]
[ControlerStateAttachType(true)]
public class GameObjectActiveState : ControllerStateBase
{
[SerializeField] private bool _active = true;
[HideInInspector] [SerializeField] private bool _defaultActive = true;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.gameObject != null)
{
_defaultActive = recorder.gameObject.activeSelf;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.gameObject != null)
{
bool shouldBeActive = (entryIndex == selectionIndex) ? _active : _defaultActive;
recorder.gameObject.SetActive(shouldBeActive);
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.gameObject != null;
}
public override string GetDescription()
{
return $"匹配时: {(_active ? "" : "")}, 默认: {(_defaultActive ? "" : "")}";
}
}
}

View File

@ -1,45 +0,0 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.UI
{
/// <summary>
/// Graphic 颜色状态控制
/// 控制 UI 图形组件的颜色
/// </summary>
[Serializable]
[ControlerStateName("Graphic/Color")]
[ControlerStateAttachType(true)]
public class GraphicColorState : ControllerStateBase
{
[SerializeField] private Color _color = Color.white;
[HideInInspector] [SerializeField] private Color _defaultColor;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.TryGetComponent<Graphic>(out var graphic))
{
_defaultColor = graphic.color;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.TryGetComponent<Graphic>(out var graphic))
{
graphic.color = (entryIndex == selectionIndex) ? _color : _defaultColor;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.GetComponent<Graphic>() != null;
}
public override string GetDescription()
{
return $"匹配时: 颜色={_color}, 默认={_defaultColor}";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 75807e24962b62547b1444c2078b77b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,47 +0,0 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.UI
{
/// <summary>
/// Graphic 材质状态控制
/// 控制 UI 图形组件的材质
/// </summary>
[Serializable]
[ControlerStateName("Graphic/Material")]
[ControlerStateAttachType(true)]
public class GraphicMaterialState : ControllerStateBase
{
[SerializeField] private Material _material;
[HideInInspector] [SerializeField] private Material _defaultMaterial;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.TryGetComponent<Graphic>(out var graphic))
{
_defaultMaterial = graphic.material;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.TryGetComponent<Graphic>(out var graphic))
{
graphic.material = (entryIndex == selectionIndex) ? _material : _defaultMaterial;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.GetComponent<Graphic>() != null;
}
public override string GetDescription()
{
string matched = _material != null ? _material.name : "无";
string defaultVal = _defaultMaterial != null ? _defaultMaterial.name : "无";
return $"匹配时: {matched}, 默认: {defaultVal}";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 1920e338ff84a074997b6d495fdf8641
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,47 +0,0 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.UI
{
/// <summary>
/// Image 精灵图状态控制
/// 控制 Image 组件的 Sprite
/// </summary>
[Serializable]
[ControlerStateName("Image/Sprite")]
[ControlerStateAttachType(true)]
public class ImageSpriteState : ControllerStateBase
{
[SerializeField] private Sprite _sprite;
[HideInInspector] [SerializeField] private Sprite _defaultSprite;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.TryGetComponent<Image>(out var image))
{
_defaultSprite = image.sprite;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.TryGetComponent<Image>(out var image))
{
image.sprite = (entryIndex == selectionIndex) ? _sprite : _defaultSprite;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.GetComponent<Image>() != null;
}
public override string GetDescription()
{
string matched = _sprite != null ? _sprite.name : "无";
string defaultVal = _defaultSprite != null ? _defaultSprite.name : "无";
return $"匹配时: {matched}, 默认: {defaultVal}";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 628055cb200855e4caccc19755692f8c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,61 +0,0 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace AlicizaX.UI
{
/// <summary>
/// Text 颜色状态控制
/// 控制 Text 或 TextMeshProUGUI 的文本颜色
/// </summary>
[Serializable]
[ControlerStateName("Text/Color")]
[ControlerStateAttachType(true)]
public class TextColorState : ControllerStateBase
{
[SerializeField] private Color _color = Color.white;
[HideInInspector] [SerializeField] private Color _defaultColor;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder == null) return;
if (recorder.TryGetComponent<Text>(out var text))
{
_defaultColor = text.color;
}
else if (recorder.TryGetComponent<TextMeshProUGUI>(out var tmp))
{
_defaultColor = tmp.color;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder == null) return;
Color targetColor = (entryIndex == selectionIndex) ? _color : _defaultColor;
if (recorder.TryGetComponent<Text>(out var text))
{
text.color = targetColor;
}
else if (recorder.TryGetComponent<TextMeshProUGUI>(out var tmp))
{
tmp.color = targetColor;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null &&
(recorder.GetComponent<Text>() != null || recorder.GetComponent<TextMeshProUGUI>() != null);
}
public override string GetDescription()
{
return $"匹配时: 颜色={_color}, 默认={_defaultColor}";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c6ac5efb857bb65439be28f7d81256c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,49 +0,0 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace AlicizaX.UI
{
/// <summary>
/// Text 内容状态控制
/// 控制 Text 或 TextMeshProUGUI 的文本内容
/// </summary>
[Serializable]
[ControlerStateName("Text/Content")]
[ControlerStateAttachType(true)]
public class TextContentState : ControllerStateBase
{
[SerializeField] [TextArea(3, 10)] private string _text = "";
public override void Init(UXControllerStateRecorder recorder)
{
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder == null || entryIndex != selectionIndex) return;
if (recorder.TryGetComponent<Text>(out var text))
{
text.text = _text;
}
else if (recorder.TryGetComponent<TextMeshProUGUI>(out var tmp))
{
tmp.text = _text;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null &&
(recorder.GetComponent<Text>() != null || recorder.GetComponent<TextMeshProUGUI>() != null);
}
public override string GetDescription()
{
string preview = _text.Length > 30 ? _text.Substring(0, 30) + "..." : _text;
return $"匹配时: \"{preview}\"";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 2601e2ab0d45e3d42b77ab4d5b808794
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,44 +0,0 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// Transform 位置状态控制
/// 控制 RectTransform 的锚点位置
/// </summary>
[Serializable]
[ControlerStateName("Transform/Position")]
[ControlerStateAttachType(true)]
public class TransformPositionState : ControllerStateBase
{
[SerializeField] private Vector2 _position;
[HideInInspector] [SerializeField] private Vector2 _defaultPosition;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.TryGetComponent<RectTransform>(out var rect))
{
_defaultPosition = rect.anchoredPosition;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.TryGetComponent<RectTransform>(out var rect))
{
rect.anchoredPosition = (entryIndex == selectionIndex) ? _position : _defaultPosition;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.GetComponent<RectTransform>() != null;
}
public override string GetDescription()
{
return $"匹配时: 位置={_position}, 默认={_defaultPosition}";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: fd5741ffd27fc8f489c01426672b1553
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,44 +0,0 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// Transform 旋转状态控制
/// 控制对象的本地欧拉角旋转
/// </summary>
[Serializable]
[ControlerStateName("Transform/Rotation")]
[ControlerStateAttachType(true)]
public class TransformRotationState : ControllerStateBase
{
[SerializeField] private Vector3 _rotation;
[HideInInspector] [SerializeField] private Vector3 _defaultRotation;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.transform != null)
{
_defaultRotation = recorder.transform.localEulerAngles;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.transform != null)
{
recorder.transform.localEulerAngles = (entryIndex == selectionIndex) ? _rotation : _defaultRotation;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.transform != null;
}
public override string GetDescription()
{
return $"匹配时: 旋转={_rotation}, 默认={_defaultRotation}";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 6928446dc2b05ae48b3a2b5547d086cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,44 +0,0 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// Transform 缩放状态控制
/// 控制对象的本地缩放
/// </summary>
[Serializable]
[ControlerStateName("Transform/Scale")]
[ControlerStateAttachType(true)]
public class TransformScaleState : ControllerStateBase
{
[SerializeField] private Vector3 _scale = Vector3.one;
[HideInInspector] [SerializeField] private Vector3 _defaultScale;
public override void Init(UXControllerStateRecorder recorder)
{
if (recorder != null && recorder.transform != null)
{
_defaultScale = recorder.transform.localScale;
}
}
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
{
if (recorder != null && recorder.transform != null)
{
recorder.transform.localScale = (entryIndex == selectionIndex) ? _scale : _defaultScale;
}
}
public override bool Valid(UXControllerStateRecorder recorder)
{
return recorder != null && recorder.transform != null;
}
public override string GetDescription()
{
return $"匹配时: 缩放={_scale}, 默认={_defaultScale}";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 8232f9df6a0c18649b35fc85e9935659
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,419 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace AlicizaX.UI
{
public enum UXBindingFallbackMode
{
KeepCurrent = 0,
RestoreCapturedDefault = 1,
UseCustomValue = 2
}
public enum UXBindingValueKind
{
Boolean = 0,
Float = 1,
String = 2,
Color = 3,
Vector2 = 4,
Vector3 = 5,
ObjectReference = 6
}
public enum UXBindingProperty
{
GameObjectActive = 0,
CanvasGroupAlpha = 1,
CanvasGroupInteractable = 2,
CanvasGroupBlocksRaycasts = 3,
GraphicColor = 4,
GraphicMaterial = 5,
ImageSprite = 6,
TextContent = 7,
TextColor = 8,
RectTransformAnchoredPosition = 9,
TransformLocalScale = 10,
TransformLocalEulerAngles = 11
}
[Serializable]
public sealed class UXBindingValue
{
[SerializeField] private bool _boolValue;
[SerializeField] private float _floatValue;
[SerializeField] private string _stringValue = string.Empty;
[SerializeField] private Color _colorValue = Color.white;
[SerializeField] private Vector2 _vector2Value;
[SerializeField] private Vector3 _vector3Value;
[SerializeField] private UnityEngine.Object _objectValue;
public bool BoolValue
{
get => _boolValue;
set => _boolValue = value;
}
public float FloatValue
{
get => _floatValue;
set => _floatValue = value;
}
public string StringValue
{
get => _stringValue;
set => _stringValue = value ?? string.Empty;
}
public Color ColorValue
{
get => _colorValue;
set => _colorValue = value;
}
public Vector2 Vector2Value
{
get => _vector2Value;
set => _vector2Value = value;
}
public Vector3 Vector3Value
{
get => _vector3Value;
set => _vector3Value = value;
}
public UnityEngine.Object ObjectValue
{
get => _objectValue;
set => _objectValue = value;
}
public void CopyFrom(UXBindingValue other)
{
if (other == null)
{
return;
}
_boolValue = other._boolValue;
_floatValue = other._floatValue;
_stringValue = other._stringValue;
_colorValue = other._colorValue;
_vector2Value = other._vector2Value;
_vector3Value = other._vector3Value;
_objectValue = other._objectValue;
}
}
[DisallowMultipleComponent]
[AddComponentMenu("UX/UX Binding")]
public sealed class UXBinding : MonoBehaviour
{
[Serializable]
public sealed class BindingEntry
{
[SerializeField] private string _controllerId = string.Empty;
[SerializeField] private int _controllerIndex;
[SerializeField] private UXBindingProperty _property = UXBindingProperty.GameObjectActive;
[SerializeField] private UXBindingValue _value = new UXBindingValue();
[SerializeField] private UXBindingFallbackMode _fallbackMode = UXBindingFallbackMode.RestoreCapturedDefault;
[SerializeField] private UXBindingValue _fallbackValue = new UXBindingValue();
[HideInInspector] [SerializeField] private UXBindingValue _capturedDefault = new UXBindingValue();
[HideInInspector] [SerializeField] private bool _hasCapturedDefault;
[HideInInspector] [SerializeField] private UXBindingProperty _capturedProperty = UXBindingProperty.GameObjectActive;
public string ControllerId
{
get => _controllerId;
set => _controllerId = value ?? string.Empty;
}
public int ControllerIndex
{
get => Mathf.Max(0, _controllerIndex);
set => _controllerIndex = Mathf.Max(0, value);
}
public UXBindingProperty Property
{
get => _property;
set => _property = value;
}
public UXBindingValue Value => _value;
public UXBindingFallbackMode FallbackMode
{
get => _fallbackMode;
set => _fallbackMode = value;
}
public UXBindingValue FallbackValue => _fallbackValue;
public bool HasCapturedDefault => _hasCapturedDefault;
internal void Normalize()
{
if (_property != UXBindingProperty.GameObjectActive)
{
return;
}
if (_fallbackMode == UXBindingFallbackMode.RestoreCapturedDefault)
{
_fallbackMode = UXBindingFallbackMode.UseCustomValue;
_fallbackValue.BoolValue = false;
}
}
internal void CaptureDefault(GameObject target)
{
if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property))
{
return;
}
UXBindingPropertyUtility.CaptureValue(target, _property, _capturedDefault);
_capturedProperty = _property;
_hasCapturedDefault = true;
}
internal void CaptureCurrentAsValue(GameObject target)
{
if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property))
{
return;
}
UXBindingPropertyUtility.CaptureValue(target, _property, _value);
}
internal void CaptureCurrentAsFallback(GameObject target)
{
if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property))
{
return;
}
UXBindingPropertyUtility.CaptureValue(target, _property, _fallbackValue);
}
internal void ResetToCapturedDefault(GameObject target)
{
if (target == null)
{
return;
}
if (!_hasCapturedDefault || _capturedProperty != _property)
{
CaptureDefault(target);
}
UXBindingPropertyUtility.ApplyValue(target, _property, _capturedDefault);
}
internal void Apply(GameObject target, string controllerId, int selectedIndex)
{
if (target == null || !string.Equals(_controllerId, controllerId, StringComparison.Ordinal))
{
return;
}
if (!_hasCapturedDefault || _capturedProperty != _property)
{
CaptureDefault(target);
}
if (!UXBindingPropertyUtility.IsSupported(target, _property))
{
return;
}
if (selectedIndex == _controllerIndex)
{
UXBindingPropertyUtility.ApplyValue(target, _property, _value);
return;
}
switch (_fallbackMode)
{
case UXBindingFallbackMode.KeepCurrent:
return;
case UXBindingFallbackMode.RestoreCapturedDefault:
if (_hasCapturedDefault)
{
UXBindingPropertyUtility.ApplyValue(target, _property, _capturedDefault);
}
return;
case UXBindingFallbackMode.UseCustomValue:
UXBindingPropertyUtility.ApplyValue(target, _property, _fallbackValue);
return;
}
}
}
[SerializeField] private UXController _controller;
[SerializeField] private List<BindingEntry> _entries = new List<BindingEntry>();
private bool _initialized;
public UXController Controller => _controller;
public List<BindingEntry> Entries => _entries;
public void Initialize()
{
if (_initialized)
{
return;
}
_initialized = true;
NormalizeEntries();
EnsureControllerReference();
RegisterToController();
CaptureDefaults();
}
public void SetController(UXController controller)
{
if (_controller == controller)
{
return;
}
if (_controller != null)
{
_controller.UnregisterBinding(this);
}
_controller = controller;
RegisterToController();
}
public void CaptureDefaults()
{
for (int i = 0; i < _entries.Count; i++)
{
if (_entries[i] != null)
{
_entries[i].CaptureDefault(gameObject);
}
}
}
public void ResetToDefaults()
{
for (int i = 0; i < _entries.Count; i++)
{
if (_entries[i] != null)
{
_entries[i].ResetToCapturedDefault(gameObject);
}
}
}
public void PreviewEntry(int entryIndex)
{
if (_controller == null || entryIndex < 0 || entryIndex >= _entries.Count)
{
return;
}
BindingEntry entry = _entries[entryIndex];
if (entry == null || string.IsNullOrWhiteSpace(entry.ControllerId))
{
return;
}
_controller.SetControllerIndex(entry.ControllerId, entry.ControllerIndex);
}
public void CaptureEntryValue(int entryIndex)
{
if (entryIndex < 0 || entryIndex >= _entries.Count || _entries[entryIndex] == null)
{
return;
}
_entries[entryIndex].CaptureCurrentAsValue(gameObject);
}
public void CaptureEntryFallbackValue(int entryIndex)
{
if (entryIndex < 0 || entryIndex >= _entries.Count || _entries[entryIndex] == null)
{
return;
}
_entries[entryIndex].CaptureCurrentAsFallback(gameObject);
}
internal void OnControllerChanged(string controllerId, int selectedIndex)
{
for (int i = 0; i < _entries.Count; i++)
{
BindingEntry entry = _entries[i];
if (entry != null)
{
entry.Apply(gameObject, controllerId, selectedIndex);
}
}
}
private void Reset()
{
EnsureControllerReference();
RegisterToController();
}
private void Awake()
{
Initialize();
}
private void OnValidate()
{
NormalizeEntries();
EnsureControllerReference();
RegisterToController();
}
private void OnDestroy()
{
if (_controller != null)
{
_controller.UnregisterBinding(this);
}
}
private void EnsureControllerReference()
{
if (_controller == null)
{
_controller = GetComponentInParent<UXController>();
}
}
private void RegisterToController()
{
if (_controller != null)
{
_controller.RegisterBinding(this);
}
}
private void NormalizeEntries()
{
for (int i = 0; i < _entries.Count; i++)
{
if (_entries[i] != null)
{
_entries[i].Normalize();
}
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b7bbaef07861f9249bca4035e2422181
guid: aacb6b471b8bfde4c9d3650f1b9b2b22
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.UI
{
public sealed class UXBindingPropertyMetadata
{
public UXBindingPropertyMetadata(
UXBindingProperty property,
string displayName,
UXBindingValueKind valueKind,
Type objectReferenceType)
{
Property = property;
DisplayName = displayName;
ValueKind = valueKind;
ObjectReferenceType = objectReferenceType;
}
public UXBindingProperty Property { get; }
public string DisplayName { get; }
public UXBindingValueKind ValueKind { get; }
public Type ObjectReferenceType { get; }
}
public static class UXBindingPropertyUtility
{
private static readonly UXBindingPropertyMetadata[] Metadata =
{
new UXBindingPropertyMetadata(UXBindingProperty.GameObjectActive, "GameObject/Active", UXBindingValueKind.Boolean, null),
new UXBindingPropertyMetadata(UXBindingProperty.CanvasGroupAlpha, "CanvasGroup/Alpha", UXBindingValueKind.Float, null),
new UXBindingPropertyMetadata(UXBindingProperty.CanvasGroupInteractable, "CanvasGroup/Interactable", UXBindingValueKind.Boolean, null),
new UXBindingPropertyMetadata(UXBindingProperty.CanvasGroupBlocksRaycasts, "CanvasGroup/Blocks Raycasts", UXBindingValueKind.Boolean, null),
new UXBindingPropertyMetadata(UXBindingProperty.GraphicColor, "Graphic/Color", UXBindingValueKind.Color, null),
new UXBindingPropertyMetadata(UXBindingProperty.GraphicMaterial, "Graphic/Material", UXBindingValueKind.ObjectReference, typeof(Material)),
new UXBindingPropertyMetadata(UXBindingProperty.ImageSprite, "Image/Sprite", UXBindingValueKind.ObjectReference, typeof(Sprite)),
new UXBindingPropertyMetadata(UXBindingProperty.TextContent, "Text/Content", UXBindingValueKind.String, null),
new UXBindingPropertyMetadata(UXBindingProperty.TextColor, "Text/Color", UXBindingValueKind.Color, null),
new UXBindingPropertyMetadata(UXBindingProperty.RectTransformAnchoredPosition, "RectTransform/Anchored Position", UXBindingValueKind.Vector2, null),
new UXBindingPropertyMetadata(UXBindingProperty.TransformLocalScale, "Transform/Local Scale", UXBindingValueKind.Vector3, null),
new UXBindingPropertyMetadata(UXBindingProperty.TransformLocalEulerAngles, "Transform/Local Rotation", UXBindingValueKind.Vector3, null)
};
public static IReadOnlyList<UXBindingPropertyMetadata> AllMetadata => Metadata;
public static UXBindingPropertyMetadata GetMetadata(UXBindingProperty property)
{
for (int i = 0; i < Metadata.Length; i++)
{
if (Metadata[i].Property == property)
{
return Metadata[i];
}
}
return Metadata[0];
}
public static bool IsSupported(GameObject target, UXBindingProperty property)
{
if (target == null)
{
return false;
}
switch (property)
{
case UXBindingProperty.GameObjectActive:
case UXBindingProperty.TransformLocalScale:
case UXBindingProperty.TransformLocalEulerAngles:
return true;
case UXBindingProperty.CanvasGroupAlpha:
case UXBindingProperty.CanvasGroupInteractable:
case UXBindingProperty.CanvasGroupBlocksRaycasts:
return target.GetComponent<CanvasGroup>() != null;
case UXBindingProperty.GraphicColor:
case UXBindingProperty.GraphicMaterial:
return target.GetComponent<Graphic>() != null;
case UXBindingProperty.ImageSprite:
return target.GetComponent<Image>() != null;
case UXBindingProperty.TextContent:
case UXBindingProperty.TextColor:
return target.GetComponent<Text>() != null || target.GetComponent<TextMeshProUGUI>() != null;
case UXBindingProperty.RectTransformAnchoredPosition:
return target.GetComponent<RectTransform>() != null;
default:
return false;
}
}
public static void GetSupportedProperties(GameObject target, List<UXBindingProperty> output)
{
output.Clear();
if (target == null)
{
return;
}
for (int i = 0; i < Metadata.Length; i++)
{
UXBindingProperty property = Metadata[i].Property;
if (IsSupported(target, property))
{
output.Add(property);
}
}
}
public static void CaptureValue(GameObject target, UXBindingProperty property, UXBindingValue destination)
{
if (target == null || destination == null)
{
return;
}
switch (property)
{
case UXBindingProperty.GameObjectActive:
destination.BoolValue = target.activeSelf;
return;
case UXBindingProperty.CanvasGroupAlpha:
destination.FloatValue = target.GetComponent<CanvasGroup>().alpha;
return;
case UXBindingProperty.CanvasGroupInteractable:
destination.BoolValue = target.GetComponent<CanvasGroup>().interactable;
return;
case UXBindingProperty.CanvasGroupBlocksRaycasts:
destination.BoolValue = target.GetComponent<CanvasGroup>().blocksRaycasts;
return;
case UXBindingProperty.GraphicColor:
destination.ColorValue = target.GetComponent<Graphic>().color;
return;
case UXBindingProperty.GraphicMaterial:
destination.ObjectValue = target.GetComponent<Graphic>().material;
return;
case UXBindingProperty.ImageSprite:
destination.ObjectValue = target.GetComponent<Image>().sprite;
return;
case UXBindingProperty.TextContent:
if (target.TryGetComponent<Text>(out Text text))
{
destination.StringValue = text.text;
}
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmp))
{
destination.StringValue = tmp.text;
}
return;
case UXBindingProperty.TextColor:
if (target.TryGetComponent<Text>(out Text legacyText))
{
destination.ColorValue = legacyText.color;
}
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmpText))
{
destination.ColorValue = tmpText.color;
}
return;
case UXBindingProperty.RectTransformAnchoredPosition:
destination.Vector2Value = target.GetComponent<RectTransform>().anchoredPosition;
return;
case UXBindingProperty.TransformLocalScale:
destination.Vector3Value = target.transform.localScale;
return;
case UXBindingProperty.TransformLocalEulerAngles:
destination.Vector3Value = target.transform.localEulerAngles;
return;
}
}
public static void ApplyValue(GameObject target, UXBindingProperty property, UXBindingValue value)
{
if (target == null || value == null)
{
return;
}
switch (property)
{
case UXBindingProperty.GameObjectActive:
target.SetActive(value.BoolValue);
return;
case UXBindingProperty.CanvasGroupAlpha:
target.GetComponent<CanvasGroup>().alpha = value.FloatValue;
return;
case UXBindingProperty.CanvasGroupInteractable:
target.GetComponent<CanvasGroup>().interactable = value.BoolValue;
return;
case UXBindingProperty.CanvasGroupBlocksRaycasts:
target.GetComponent<CanvasGroup>().blocksRaycasts = value.BoolValue;
return;
case UXBindingProperty.GraphicColor:
target.GetComponent<Graphic>().color = value.ColorValue;
return;
case UXBindingProperty.GraphicMaterial:
target.GetComponent<Graphic>().material = value.ObjectValue as Material;
return;
case UXBindingProperty.ImageSprite:
target.GetComponent<Image>().sprite = value.ObjectValue as Sprite;
return;
case UXBindingProperty.TextContent:
if (target.TryGetComponent<Text>(out Text text))
{
text.text = value.StringValue;
}
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmp))
{
tmp.text = value.StringValue;
}
return;
case UXBindingProperty.TextColor:
if (target.TryGetComponent<Text>(out Text legacyText))
{
legacyText.color = value.ColorValue;
}
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmpText))
{
tmpText.color = value.ColorValue;
}
return;
case UXBindingProperty.RectTransformAnchoredPosition:
target.GetComponent<RectTransform>().anchoredPosition = value.Vector2Value;
return;
case UXBindingProperty.TransformLocalScale:
target.transform.localScale = value.Vector3Value;
return;
case UXBindingProperty.TransformLocalEulerAngles:
target.transform.localEulerAngles = value.Vector3Value;
return;
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b698383c9842af646b8985aa1fc11a77
guid: cd767ac91b0e6d44fa13eee8daebc60f
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace AlicizaX.UI
{
@ -9,12 +12,15 @@ namespace AlicizaX.UI
public sealed class UXController : MonoBehaviour
{
[Serializable]
public class ControllerData
public sealed class ControllerDefinition
{
[SerializeField] private string _id = string.Empty;
[SerializeField] private string _name = "Controller";
[SerializeField] private int _length = 2;
[SerializeField] private string _description = "";
[NonSerialized] private int _currentIndex = 0;
[SerializeField] private string _description = string.Empty;
[NonSerialized] private int _selectedIndex = -1;
public string Id => _id;
public string Name
{
@ -24,7 +30,7 @@ namespace AlicizaX.UI
public int Length
{
get => _length;
get => Mathf.Max(1, _length);
set => _length = Mathf.Max(1, value);
}
@ -34,240 +40,266 @@ namespace AlicizaX.UI
set => _description = value;
}
public int CurrentIndex
public int SelectedIndex
{
get => _currentIndex;
set => _currentIndex = value;
get => _selectedIndex;
internal set => _selectedIndex = value;
}
internal void EnsureId()
{
if (string.IsNullOrWhiteSpace(_id))
{
_id = Guid.NewGuid().ToString("N");
}
}
}
[SerializeField] private List<ControllerData> _controllers = new List<ControllerData>();
[SerializeField] private List<UXControllerStateRecorder> _recorders = new List<UXControllerStateRecorder>();
[SerializeField] private List<ControllerDefinition> _controllers = new List<ControllerDefinition>();
[SerializeField] private List<UXBinding> _bindings = new List<UXBinding>();
private Dictionary<string, int> _controllerIndexMap;
private bool _initialized = false;
#region Public API
public IReadOnlyList<ControllerData> Controllers => _controllers;
private readonly Dictionary<string, int> _controllerIdMap = new Dictionary<string, int>();
private readonly Dictionary<string, int> _controllerNameMap = new Dictionary<string, int>();
public IReadOnlyList<ControllerDefinition> Controllers => _controllers;
public IReadOnlyList<UXBinding> Bindings => _bindings;
public int ControllerCount => _controllers.Count;
public ControllerHandle GetController(string name)
public bool TryGetControllerById(string controllerId, out ControllerDefinition controller)
{
controller = null;
if (string.IsNullOrWhiteSpace(controllerId))
{
return false;
}
EnsureInitialized();
if (_controllerIdMap.TryGetValue(controllerId, out int index))
{
controller = _controllers[index];
return true;
}
return false;
}
public bool TryGetControllerByName(string controllerName, out ControllerDefinition controller)
{
controller = null;
if (string.IsNullOrWhiteSpace(controllerName))
{
return false;
}
EnsureInitialized();
if (_controllerNameMap.TryGetValue(controllerName, out int index))
{
controller = _controllers[index];
return true;
}
return false;
}
public ControllerDefinition GetControllerAt(int index)
{
if (index < 0 || index >= _controllers.Count)
{
return null;
}
return _controllers[index];
}
public int GetControllerIndex(string controllerId)
{
return TryGetControllerById(controllerId, out ControllerDefinition controller)
? controller.SelectedIndex
: 0;
}
public bool SetControllerIndex(string controllerId, int selectedIndex)
{
if (!TryGetControllerById(controllerId, out ControllerDefinition controller))
{
return false;
}
return SetControllerIndexInternal(controller, selectedIndex);
}
public bool SetControllerIndexByName(string controllerName, int selectedIndex)
{
if (!TryGetControllerByName(controllerName, out ControllerDefinition controller))
{
return false;
}
return SetControllerIndexInternal(controller, selectedIndex);
}
public void ResetAllControllers()
{
EnsureInitialized();
if (_controllerIndexMap.TryGetValue(name, out int index))
{
return new ControllerHandle(this, index);
}
return null;
}
public ControllerHandle GetControllerAt(int index)
{
if (index < 0 || index >= _controllers.Count) return null;
return new ControllerHandle(this, index);
}
public void SetControllerIndex(string name, int selectedIndex)
{
EnsureInitialized();
if (_controllerIndexMap.TryGetValue(name, out int controllerIndex))
for (int i = 0; i < _controllers.Count; i++)
{
SetControllerIndexInternal(controllerIndex, selectedIndex);
SetControllerIndexInternal(_controllers[i], 0, true);
}
}
public int GetControllerIndex(string name)
internal bool HasBinding(UXBinding binding)
{
EnsureInitialized();
if (_controllerIndexMap.TryGetValue(name, out int controllerIndex))
return binding != null && _bindings.Contains(binding);
}
internal void RegisterBinding(UXBinding binding)
{
if (binding == null)
{
return _controllers[controllerIndex].CurrentIndex;
return;
}
return 0;
}
#endregion
#region Internal API
internal IReadOnlyList<UXControllerStateRecorder> Recorders => _recorders;
internal void SetControllerIndexInternal(int controllerIndex, int selectedIndex)
{
if (controllerIndex < 0 || controllerIndex >= _controllers.Count) return;
var cd = _controllers[controllerIndex];
selectedIndex = Mathf.Clamp(selectedIndex, 0, cd.Length - 1);
if (cd.CurrentIndex == selectedIndex) return;
cd.CurrentIndex = selectedIndex;
NotifyRecorders(cd.Name, selectedIndex);
}
internal bool HasRecorder(UXControllerStateRecorder recorder)
{
if (recorder == null) return false;
return _recorders.Exists(r => r != null && r.ID == recorder.ID);
}
internal void RegisterRecorder(UXControllerStateRecorder recorder)
{
if (recorder != null && !HasRecorder(recorder))
if (!_bindings.Contains(binding))
{
_recorders.Add(recorder);
_bindings.Add(binding);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
EditorUtility.SetDirty(this);
}
#endif
}
}
internal void UnregisterRecorder(UXControllerStateRecorder recorder)
internal void UnregisterBinding(UXBinding binding)
{
if (recorder != null)
if (binding == null)
{
_recorders.RemoveAll(r => r == null || r.ID == recorder.ID);
return;
}
_bindings.Remove(binding);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
EditorUtility.SetDirty(this);
}
#endif
}
#endregion
private void Reset()
{
if (_controllers.Count == 0)
{
_controllers.Add(new ControllerDefinition());
}
#region Unity Lifecycle
RebuildMaps();
}
private void Awake()
{
InitializeRuntime();
EnsureInitialized();
for (int i = 0; i < _bindings.Count; i++)
{
if (_bindings[i] != null)
{
_bindings[i].Initialize();
}
}
ResetAllControllers();
}
private void OnValidate()
{
BuildControllerIndexMap();
RebuildMaps();
CleanupBindings();
}
#endregion
#region Private Methods
private void EnsureInitialized()
{
if (!_initialized)
if (_controllerIdMap.Count == 0 && _controllerNameMap.Count == 0)
{
BuildControllerIndexMap();
RebuildMaps();
}
}
private void BuildControllerIndexMap()
private void RebuildMaps()
{
if (_controllerIndexMap == null)
{
_controllerIndexMap = new Dictionary<string, int>();
}
else
{
_controllerIndexMap.Clear();
}
_controllerIdMap.Clear();
_controllerNameMap.Clear();
var usedNames = new HashSet<string>(StringComparer.Ordinal);
for (int i = 0; i < _controllers.Count; i++)
{
var controller = _controllers[i];
if (!string.IsNullOrEmpty(controller.Name))
ControllerDefinition controller = _controllers[i];
if (controller == null)
{
_controllerIndexMap[controller.Name] = i;
continue;
}
}
_initialized = true;
controller.EnsureId();
controller.SelectedIndex = Mathf.Clamp(controller.SelectedIndex, -1, controller.Length - 1);
if (string.IsNullOrWhiteSpace(controller.Name))
{
controller.Name = $"Controller{i + 1}";
}
if (!usedNames.Add(controller.Name))
{
controller.Name = $"{controller.Name}_{i + 1}";
usedNames.Add(controller.Name);
}
_controllerIdMap[controller.Id] = i;
_controllerNameMap[controller.Name] = i;
}
}
private void InitializeRuntime()
private void CleanupBindings()
{
EnsureInitialized();
for (int i = 0; i < _recorders.Count; i++)
for (int i = _bindings.Count - 1; i >= 0; i--)
{
if (_recorders[i] != null)
if (_bindings[i] == null)
{
_recorders[i].Initialize();
_bindings.RemoveAt(i);
}
}
for (int i = 0; i < _controllers.Count; i++)
{
SetControllerIndexInternal(i, 0);
}
}
private void NotifyRecorders(string controllerName, int selectedIndex)
private bool SetControllerIndexInternal(ControllerDefinition controller, int selectedIndex, bool force = false)
{
for (int i = 0; i < _recorders.Count; i++)
if (controller == null)
{
if (_recorders[i] != null)
{
_recorders[i].OnControllerIndexChanged(controllerName, selectedIndex);
}
return false;
}
selectedIndex = Mathf.Clamp(selectedIndex, 0, controller.Length - 1);
if (!force && controller.SelectedIndex == selectedIndex)
{
return false;
}
controller.SelectedIndex = selectedIndex;
NotifyBindings(controller.Id, selectedIndex);
return true;
}
#endregion
#region Controller Handle
public class ControllerHandle
private void NotifyBindings(string controllerId, int selectedIndex)
{
private readonly UXController _owner;
private readonly int _controllerIndex;
public ControllerHandle(UXController owner, int controllerIndex)
for (int i = 0; i < _bindings.Count; i++)
{
_owner = owner;
_controllerIndex = controllerIndex;
}
public bool IsValid => _owner != null && _controllerIndex >= 0 && _controllerIndex < _owner._controllers.Count;
public int SelectedIndex
{
get
UXBinding binding = _bindings[i];
if (binding != null)
{
if (!IsValid) return 0;
return _owner._controllers[_controllerIndex].CurrentIndex;
}
set
{
if (IsValid)
{
_owner.SetControllerIndexInternal(_controllerIndex, value);
}
}
}
public int Length
{
get
{
if (!IsValid) return 0;
return _owner._controllers[_controllerIndex].Length;
}
}
public string Name
{
get
{
if (!IsValid) return null;
return _owner._controllers[_controllerIndex].Name;
}
}
public string Description
{
get
{
if (!IsValid) return null;
return _owner._controllers[_controllerIndex].Description;
binding.OnControllerChanged(controllerId, selectedIndex);
}
}
}
#endregion
}
}

View File

@ -1,112 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace AlicizaX.UI
{
[DisallowMultipleComponent]
[AddComponentMenu("UX/UX Controller State Recorder")]
public class UXControllerStateRecorder : MonoBehaviour
{
[SerializeField] private int _id;
[SerializeField] private UXController _controller;
internal int ID => _id;
internal UXController Controller => _controller;
[Serializable]
public class StateEntry
{
[SerializeReference] public ControllerStateBase State = null;
public string ControllerName = string.Empty;
public int ControllerIndex = 0;
}
[SerializeField] private List<StateEntry> _stateEntries = new List<StateEntry>();
internal List<StateEntry> StateEntries => _stateEntries;
private bool _initialized = false;
#region Unity Lifecycle
private void Awake()
{
Initialize();
}
private void OnDestroy()
{
if (_controller != null)
{
_controller.UnregisterRecorder(this);
}
}
#endregion
#region Public API
public void Initialize()
{
if (_initialized) return;
_initialized = true;
foreach (var entry in _stateEntries)
{
if (entry?.State != null)
{
entry.State.Init(this);
}
}
}
public void SetController(UXController controller)
{
if (_controller != null)
{
_controller.UnregisterRecorder(this);
}
_controller = controller;
if (_controller != null)
{
_controller.RegisterRecorder(this);
}
}
#endregion
#region Internal API
internal void OnControllerIndexChanged(string controllerName, int selectedIndex)
{
for (int i = 0; i < _stateEntries.Count; i++)
{
var entry = _stateEntries[i];
if (entry != null && entry.ControllerName == controllerName && entry.State != null)
{
try
{
entry.State.Execute(this, entry.ControllerIndex, selectedIndex);
}
catch (Exception ex)
{
Debug.LogError($"Error executing state {entry.State.GetType().Name}: {ex.Message}", this);
}
}
}
}
internal void GenerateID()
{
if (_id <= 0)
{
_id = UnityEngine.Random.Range(10000000, 99999999);
}
}
#endregion
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 35829408994740a08c32ac2f519442f0
timeCreated: 1763705324