From 99446198cf8f70a8c0f42736ffc0ec3cf3571f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Fri, 5 Dec 2025 19:03:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=B4=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/Input.meta | 3 + Runtime/Input/InputDeviceWatcher.cs | 224 ++++++++++++++++++ Runtime/Input/InputDeviceWatcher.cs.meta | 11 + .../UXComponent/Selectable/UXNavigation.cs | 3 +- .../UXComponent/Selectable/UXSelectable.cs | 45 +++- Runtime/UXComponent/TestUI.cs | 36 +++ Runtime/UXComponent/TestUI.cs.meta | 3 + 7 files changed, 315 insertions(+), 10 deletions(-) create mode 100644 Runtime/Input.meta create mode 100644 Runtime/Input/InputDeviceWatcher.cs create mode 100644 Runtime/Input/InputDeviceWatcher.cs.meta create mode 100644 Runtime/UXComponent/TestUI.cs create mode 100644 Runtime/UXComponent/TestUI.cs.meta diff --git a/Runtime/Input.meta b/Runtime/Input.meta new file mode 100644 index 0000000..946e48f --- /dev/null +++ b/Runtime/Input.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7f9ad1ce05574ba886146f9982e01142 +timeCreated: 1764839428 \ No newline at end of file diff --git a/Runtime/Input/InputDeviceWatcher.cs b/Runtime/Input/InputDeviceWatcher.cs new file mode 100644 index 0000000..3df12fb --- /dev/null +++ b/Runtime/Input/InputDeviceWatcher.cs @@ -0,0 +1,224 @@ +#if INPUTSYSTEM_SUPPORT +using System; +using System.Globalization; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; +using UnityEngine.InputSystem; + +public static class InputDeviceWatcher +{ + public enum InputDeviceCategory + { + Keyboard, + Xbox, + PlayStation, + Other + } + + static readonly float DebounceWindow = 1f; + public static InputDeviceCategory CurrentCategory = InputDeviceCategory.Keyboard; + public static string CurrentDeviceName = ""; + + private static InputAction _anyInputAction; + private static int _lastDeviceId = -1; + private static float _lastInputTime = -Mathf.Infinity; + + + public static event Action OnDeviceChanged; + + private static bool initialized = false; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + public static void Initialize() + { + if (initialized) return; + initialized = true; + + CurrentCategory = InputDeviceCategory.Keyboard; + CurrentDeviceName = ""; + + _anyInputAction = new InputAction("AnyDevice", InputActionType.PassThrough); + _anyInputAction.AddBinding("/anyKey"); + _anyInputAction.AddBinding("/*"); + _anyInputAction.AddBinding("/*"); + _anyInputAction.AddBinding("/*"); + + _anyInputAction.performed += OnAnyInputPerformed; + _anyInputAction.Enable(); + + InputSystem.onDeviceChange += OnDeviceChange; +#if UNITY_EDITOR + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; +#endif + } + +#if UNITY_EDITOR + static void OnPlayModeStateChanged(PlayModeStateChange state) + { + if (state == PlayModeStateChange.ExitingPlayMode) + { + Dispose(); + } + + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + } +#endif + + public static void Dispose() + { + if (!initialized) return; + CurrentCategory = InputDeviceCategory.Keyboard; + _anyInputAction.performed -= OnAnyInputPerformed; + _anyInputAction.Disable(); + _anyInputAction.Dispose(); + + InputSystem.onDeviceChange -= OnDeviceChange; + + OnDeviceChanged = null; + initialized = false; + } + + // ------------------ 监听输入 -------------------- + private static void OnAnyInputPerformed(InputAction.CallbackContext ctx) + { + if (ctx.control == null || ctx.control.device == null) return; + + var device = ctx.control.device; + int curId = device.deviceId; + float now = Time.realtimeSinceStartup; + + if (curId == _lastDeviceId) return; + if (DebounceWindow > 0f && (now - _lastInputTime) < DebounceWindow) return; + + _lastInputTime = now; + _lastDeviceId = curId; + + CurrentCategory = DetermineCategoryFromDevice(device); + CurrentDeviceName = device.displayName ?? $"Device_{curId}"; + + EmitChangeLog(); + } + + // ------------------ 监听设备变更 -------------------- + private static void OnDeviceChange(InputDevice device, InputDeviceChange change) + { + if (change == InputDeviceChange.Removed || change == InputDeviceChange.Disconnected) + { + if (device.deviceId == _lastDeviceId) + { + _lastDeviceId = -1; + _lastInputTime = -Mathf.Infinity; + CurrentDeviceName = ""; + CurrentCategory = InputDeviceCategory.Keyboard; + EmitChangeLog(); + } + } + } + + // ------------------ 分类逻辑 -------------------- + private static InputDeviceCategory DetermineCategoryFromDevice(InputDevice device) + { + if (device == null) return InputDeviceCategory.Keyboard; + if (device is Keyboard || device is Mouse) return InputDeviceCategory.Keyboard; + if (IsGamepadLike(device)) return GetGamepadCategory(device); + + string combined = $"{device.description.interfaceName} {device.layout} {device.description.product} {device.description.manufacturer} {device.displayName}".ToLower(); + + if (combined.Contains("xbox") || combined.Contains("xinput")) return InputDeviceCategory.Xbox; + if (combined.Contains("dualshock") || combined.Contains("dualsense") || combined.Contains("playstation")) return InputDeviceCategory.PlayStation; + + return InputDeviceCategory.Other; + } + + private static bool IsGamepadLike(InputDevice device) + { + if (device is Gamepad) return true; + if (device is Joystick) return true; + + var layout = (device.layout ?? "").ToLower(); + return layout.Contains("gamepad") || layout.Contains("controller") || layout.Contains("joystick"); + } + + private static InputDeviceCategory GetGamepadCategory(InputDevice device) + { + if (device == null) return InputDeviceCategory.Other; + + var iface = (device.description.interfaceName ?? "").ToLower(); + if (iface.Contains("xinput")) return InputDeviceCategory.Xbox; + + if (TryParseVidPidFromCapabilities(device.description.capabilities, out int vendorId, out int _)) + { + if (vendorId == 0x045E || vendorId == 1118) return InputDeviceCategory.Xbox; + if (vendorId == 0x054C || vendorId == 1356) return InputDeviceCategory.PlayStation; + } + + string combined = $"{device.description.interfaceName} {device.layout} {device.description.product} {device.description.manufacturer} {device.displayName}".ToLower(); + if (combined.Contains("xbox")) return InputDeviceCategory.Xbox; + if (combined.Contains("dualshock") || combined.Contains("playstation")) return InputDeviceCategory.PlayStation; + + return InputDeviceCategory.Other; + } + + // ------------------ VID/PID 解析 -------------------- + private static bool TryParseVidPidFromCapabilities(string capabilities, out int vendorId, out int productId) + { + vendorId = 0; + productId = 0; + if (string.IsNullOrEmpty(capabilities)) return false; + + try + { + var decVendor = Regex.Match(capabilities, "\"vendorId\"\\s*:\\s*(\\d+)", RegexOptions.IgnoreCase); + var decProduct = Regex.Match(capabilities, "\"productId\"\\s*:\\s*(\\d+)", RegexOptions.IgnoreCase); + + if (decVendor.Success) int.TryParse(decVendor.Groups[1].Value, out vendorId); + if (decProduct.Success) int.TryParse(decProduct.Groups[1].Value, out productId); + + return vendorId != 0 || productId != 0; + } + catch + { + return false; + } + } + + // ------------------ 输出 -------------------- + private static void EmitChangeLog() + { + int vid = GetVendorId(); + int pid = GetProductId(); + +#if UNITY_EDITOR + Debug.Log($"输入设备变更 -> {CurrentCategory} 触发设备: {CurrentDeviceName} vid=0x{vid:X} pid=0x{pid:X}"); +#endif + + OnDeviceChanged?.Invoke(CurrentCategory); + } + + private static int GetVendorId() + { + foreach (var d in InputSystem.devices) + { + if ((d.displayName ?? "") == CurrentDeviceName && + TryParseVidPidFromCapabilities(d.description.capabilities, out int v, out int _)) + return v; + } + + return 0; + } + + private static int GetProductId() + { + foreach (var d in InputSystem.devices) + { + if ((d.displayName ?? "") == CurrentDeviceName && + TryParseVidPidFromCapabilities(d.description.capabilities, out int _, out int p)) + return p; + } + + return 0; + } +} + +#endif diff --git a/Runtime/Input/InputDeviceWatcher.cs.meta b/Runtime/Input/InputDeviceWatcher.cs.meta new file mode 100644 index 0000000..ac22b49 --- /dev/null +++ b/Runtime/Input/InputDeviceWatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e78f6224467e13742a70115f1942d941 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UXComponent/Selectable/UXNavigation.cs b/Runtime/UXComponent/Selectable/UXNavigation.cs index 861f286..d0b9fe2 100644 --- a/Runtime/UXComponent/Selectable/UXNavigation.cs +++ b/Runtime/UXComponent/Selectable/UXNavigation.cs @@ -19,8 +19,7 @@ namespace AlicizaX.UI.Extension [SerializeField] private Mode m_Mode; - [Tooltip("Enables navigation to wrap around from last to first or first to last element. Does not work for automatic grid navigation")] [HideInInspector] [SerializeField] - private bool m_WrapAround; + [HideInInspector] [SerializeField] private bool m_WrapAround; [HideInInspector] [SerializeField] private UXSelectable m_SelectOnUp; [HideInInspector] [SerializeField] private UXSelectable m_SelectOnDown; diff --git a/Runtime/UXComponent/Selectable/UXSelectable.cs b/Runtime/UXComponent/Selectable/UXSelectable.cs index 25f58f9..10ddb3f 100644 --- a/Runtime/UXComponent/Selectable/UXSelectable.cs +++ b/Runtime/UXComponent/Selectable/UXSelectable.cs @@ -25,11 +25,16 @@ namespace AlicizaX.UI.Extension private readonly List m_CanvasGroupCache = new List(); [SerializeField] private UXNavigation m_Navigation = UXNavigation.defaultNavigation; + public UXNavigation navigation { get { return m_Navigation; } - set { if (SetPropertyUtility.SetStruct(ref m_Navigation, value)) OnSetProperty(); } + set + { + if (SetPropertyUtility.SetStruct(ref m_Navigation, value)) OnSetProperty(); + } } + public static UXSelectable[] allSelectablesArray { get @@ -167,6 +172,7 @@ namespace AlicizaX.UI.Extension maxFurthestScore = score; bestFurthestPick = sel; } + continue; } @@ -256,12 +262,35 @@ namespace AlicizaX.UI.Extension } } - // default handlers to be overridden - public virtual void OnPointerDown(PointerEventData eventData) { } - public virtual void OnPointerUp(PointerEventData eventData) { } - public virtual void OnPointerEnter(PointerEventData eventData) { } - public virtual void OnPointerExit(PointerEventData eventData) { } - public virtual void OnSelect(BaseEventData eventData) { hasSelection = true; } - public virtual void OnDeselect(BaseEventData eventData) { hasSelection = false; } + public virtual void OnPointerDown(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + if (IsInteractable() && navigation.mode != UXNavigation.Mode.None && EventSystem.current != null) + EventSystem.current.SetSelectedGameObject(gameObject, eventData); + } + + public virtual void OnPointerUp(PointerEventData eventData) + { + } + + public virtual void OnPointerEnter(PointerEventData eventData) + { + } + + public virtual void OnPointerExit(PointerEventData eventData) + { + } + + public virtual void OnSelect(BaseEventData eventData) + { + hasSelection = true; + } + + public virtual void OnDeselect(BaseEventData eventData) + { + hasSelection = false; + } } } diff --git a/Runtime/UXComponent/TestUI.cs b/Runtime/UXComponent/TestUI.cs new file mode 100644 index 0000000..6c5286f --- /dev/null +++ b/Runtime/UXComponent/TestUI.cs @@ -0,0 +1,36 @@ +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace AlicizaX.UI.Extension.UXComponent +{ + public class TestUI : UXSelectable + { + private float maxSliderValue; + private float minSliderValue; + [SerializeField] private Slider _slider; + + private float sliderRange; + + [SerializeField] private float SLIDERSTEP = 100.0f; //used to + + protected override void Awake() + { + base.Awake(); + maxSliderValue = _slider.maxValue; + minSliderValue = _slider.minValue; + sliderRange = maxSliderValue - minSliderValue; + } + + public override void OnMove(AxisEventData eventData) + { + if (eventData.moveDir == MoveDirection.Left || eventData.moveDir == MoveDirection.Right) + { + float sliderChange = eventData.moveDir == MoveDirection.Right ? 1 * (sliderRange / SLIDERSTEP) : -1 * (sliderRange / SLIDERSTEP); + float tempValue = _slider.value + sliderChange; + _slider.value = Mathf.Clamp(tempValue, minSliderValue, maxSliderValue); + Debug.Log(_slider.value ); + } + } + } +} diff --git a/Runtime/UXComponent/TestUI.cs.meta b/Runtime/UXComponent/TestUI.cs.meta new file mode 100644 index 0000000..3edca92 --- /dev/null +++ b/Runtime/UXComponent/TestUI.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 67a18ee6ebd4470b9e936543b01af7ca +timeCreated: 1764765598 \ No newline at end of file