com.alicizax.unity.ui.exten.../Runtime/Input/InputDeviceWatcher.cs

235 lines
7.8 KiB
C#
Raw Normal View History

2025-12-05 19:03:35 +08:00
#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;
2025-12-16 14:00:30 +08:00
private static InputDeviceCategory _lastEmittedCategory = InputDeviceCategory.Keyboard;
2025-12-05 19:03:35 +08:00
public static event Action<InputDeviceCategory> OnDeviceChanged;
private static bool initialized = false;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void Initialize()
{
if (initialized) return;
initialized = true;
CurrentCategory = InputDeviceCategory.Keyboard;
CurrentDeviceName = "";
2025-12-16 14:00:30 +08:00
_lastEmittedCategory = CurrentCategory; // 初始化同步
2025-12-05 19:03:35 +08:00
_anyInputAction = new InputAction("AnyDevice", InputActionType.PassThrough);
_anyInputAction.AddBinding("<Keyboard>/anyKey");
_anyInputAction.AddBinding("<Mouse>/*");
_anyInputAction.AddBinding("<Gamepad>/*");
_anyInputAction.AddBinding("<Joystick>/*");
_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;
2025-12-16 14:00:30 +08:00
_lastEmittedCategory = InputDeviceCategory.Keyboard;
2025-12-05 19:03:35 +08:00
}
2025-12-05 20:57:19 +08:00
2025-12-05 19:03:35 +08:00
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}";
2025-12-16 14:00:30 +08:00
EmitChange();
2025-12-05 19:03:35 +08:00
}
// ------------------ 监听设备变更 --------------------
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;
2025-12-16 14:00:30 +08:00
EmitChange();
2025-12-05 19:03:35 +08:00
}
}
}
// ------------------ 分类逻辑 --------------------
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;
}
}
// ------------------ 输出 --------------------
2025-12-16 14:00:30 +08:00
private static void EmitChange()
2025-12-05 19:03:35 +08:00
{
2025-12-16 14:00:30 +08:00
if (CurrentCategory == _lastEmittedCategory)
{
return;
}
2025-12-05 19:03:35 +08:00
int vid = GetVendorId();
int pid = GetProductId();
#if UNITY_EDITOR
Debug.Log($"输入设备变更 -> {CurrentCategory} 触发设备: {CurrentDeviceName} vid=0x{vid:X} pid=0x{pid:X}");
#endif
2025-12-16 14:00:30 +08:00
// 触发事件并记录已发射的分类
2025-12-05 19:03:35 +08:00
OnDeviceChanged?.Invoke(CurrentCategory);
2025-12-16 14:00:30 +08:00
_lastEmittedCategory = CurrentCategory;
2025-12-05 19:03:35 +08:00
}
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