using System; using System.Collections.Generic; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace UnityEngine.UI { [DisallowMultipleComponent] [AddComponentMenu("UX/UX Controller")] public sealed class UXController : MonoBehaviour { [Serializable] public sealed class ControllerDefinition { [SerializeField] private string _id = string.Empty; [SerializeField] private string _name = "Controller"; [SerializeField] private int _length = 2; [SerializeField] private string _description = string.Empty; [NonSerialized] private int _selectedIndex = -1; [NonSerialized] private UXController _owner; public string Id => _id; public string Name { get => _name; set => _name = value; } public int Length { get => Mathf.Max(1, _length); set => _length = Mathf.Max(1, value); } public string Description { get => _description; set => _description = value; } public int SelectedIndex { get => _selectedIndex; set { if (_owner == null) { SetSelectedIndexSilently(Mathf.Clamp(value, 0, Length - 1)); return; } _owner.SetControllerIndexInternal(this, value); } } internal void EnsureId() { if (string.IsNullOrWhiteSpace(_id)) { _id = Guid.NewGuid().ToString("N"); } } internal void SetOwner(UXController owner) { _owner = owner; } internal void SetSelectedIndexSilently(int selectedIndex) { _selectedIndex = selectedIndex; } } [SerializeField] private List _controllers = new List(); [SerializeField] private List _bindings = new List(); private readonly Dictionary _controllerIdMap = new Dictionary(); private readonly Dictionary _controllerNameMap = new Dictionary(); public IReadOnlyList Controllers { get { EnsureInitialized(); return _controllers; } } public IReadOnlyList Bindings => _bindings; public int ControllerCount => _controllers.Count; 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 GetControllerByName(string controllerName) { if (string.IsNullOrWhiteSpace(controllerName)) { return null; } EnsureInitialized(); if (_controllerNameMap.TryGetValue(controllerName, out int index)) { return _controllers[index]; } return null; } public ControllerDefinition GetControllerAt(int index) { EnsureInitialized(); 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(); for (int i = 0; i < _controllers.Count; i++) { SetControllerIndexInternal(_controllers[i], 0, true); } } internal bool HasBinding(UXBinding binding) { return binding != null && _bindings.Contains(binding); } internal void RegisterBinding(UXBinding binding) { if (binding == null) { return; } if (!_bindings.Contains(binding)) { _bindings.Add(binding); #if UNITY_EDITOR if (!Application.isPlaying) { EditorUtility.SetDirty(this); } #endif } } internal void UnregisterBinding(UXBinding binding) { if (binding == null) { return; } _bindings.Remove(binding); #if UNITY_EDITOR if (!Application.isPlaying) { EditorUtility.SetDirty(this); } #endif } private void Reset() { if (_controllers.Count == 0) { _controllers.Add(new ControllerDefinition()); } RebuildMaps(); } private void Awake() { EnsureInitialized(); for (int i = 0; i < _bindings.Count; i++) { if (_bindings[i] != null) { _bindings[i].Initialize(); } } ResetAllControllers(); } private void OnValidate() { RebuildMaps(); CleanupBindings(); } private void EnsureInitialized() { if (_controllerIdMap.Count == 0 && _controllerNameMap.Count == 0) { RebuildMaps(); } } private void RebuildMaps() { _controllerIdMap.Clear(); _controllerNameMap.Clear(); var usedNames = new HashSet(StringComparer.Ordinal); for (int i = 0; i < _controllers.Count; i++) { ControllerDefinition controller = _controllers[i]; if (controller == null) { continue; } controller.EnsureId(); controller.SetOwner(this); controller.SetSelectedIndexSilently(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 CleanupBindings() { for (int i = _bindings.Count - 1; i >= 0; i--) { if (_bindings[i] == null) { _bindings.RemoveAt(i); } } } private bool SetControllerIndexInternal(ControllerDefinition controller, int selectedIndex, bool force = false) { if (controller == null) { return false; } selectedIndex = Mathf.Clamp(selectedIndex, 0, controller.Length - 1); if (!force && controller.SelectedIndex == selectedIndex) { return false; } controller.SetSelectedIndexSilently(selectedIndex); NotifyBindings(controller.Id, selectedIndex); return true; } private void NotifyBindings(string controllerId, int selectedIndex) { for (int i = 0; i < _bindings.Count; i++) { UXBinding binding = _bindings[i]; if (binding != null) { binding.OnControllerChanged(controllerId, selectedIndex); } } } } }