using System; using UnityEngine; using UnityEngine.InputSystem; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Input; namespace BaseGames.UI { /// /// 按键图标服务实现。 /// /// 职责: /// 1. 侦听 InputDeviceTypeEventChannelSO,更新当前图标集 /// 2. 侦听 InputSystem.onActionChange(BoundControlsChanged),改键后刷新 /// 3. 提供 GetActionIcon / GetActionEffectivePath,供 UI 查询 /// 4. 在 Awake 注册自身到 ServiceLocator /// /// 布置方式:与 InputDeviceDetector 同挂在 UIRoot 上;每场景只需一个实例。 /// public sealed class InputIconService : MonoBehaviour, IInputIconService { [Header("Input")] [SerializeField] private InputReaderSO _inputReader; [Header("Icon Sets — 按设备类型配置")] [SerializeField] private InputDeviceIconSetSO _kbMouseSet; [SerializeField] private InputDeviceIconSetSO _xboxSet; [SerializeField] private InputDeviceIconSetSO _playStationSet; [SerializeField] private InputDeviceIconSetSO _switchSet; [Header("Event Channels")] [SerializeField] private InputDeviceTypeEventChannelSO _onDeviceChanged; // ── IInputIconService ───────────────────────────────────────────────── public InputDeviceType CurrentDevice { get; private set; } = InputDeviceType.KeyboardMouse; public event Action OnIconSetChanged; private InputDeviceIconSetSO _activeSet; private readonly CompositeDisposable _subs = new(); // ── Lifecycle ───────────────────────────────────────────────────────── private void Awake() { _activeSet = _kbMouseSet; } private void OnEnable() { // OnEnable/OnDisable 对称注册:支持多场景加载时 GameObject 的启用/禁用周期 ServiceLocator.RegisterIfAbsent(this); _onDeviceChanged?.Subscribe(HandleDeviceChanged).AddTo(_subs); InputSystem.onActionChange += HandleActionChange; } private void OnDisable() { ServiceLocator.Unregister(this); // 仅注销自身(ReferenceEquals 保护) _subs.Clear(); InputSystem.onActionChange -= HandleActionChange; } // ── Event Handlers ──────────────────────────────────────────────────── private void HandleDeviceChanged(InputDeviceType deviceType) { CurrentDevice = deviceType; _activeSet = deviceType switch { InputDeviceType.XboxController => _xboxSet ?? _kbMouseSet, InputDeviceType.PlayStationController => _playStationSet ?? _kbMouseSet, InputDeviceType.SwitchController => _switchSet ?? _kbMouseSet, _ => _kbMouseSet, }; OnIconSetChanged?.Invoke(); } private void HandleActionChange(object obj, InputActionChange change) { if (change == InputActionChange.BoundControlsChanged) OnIconSetChanged?.Invoke(); } // ── IInputIconService impl ──────────────────────────────────────────── public Sprite GetActionIcon(string actionName) { var path = GetActionEffectivePath(actionName); if (path == null || _activeSet == null) return null; return _activeSet.GetIcon(path); } public Sprite GetPathIcon(string bindingPath) { if (_activeSet == null || string.IsNullOrEmpty(bindingPath)) return null; return _activeSet.GetIcon(bindingPath); } public string GetActionEffectivePath(string actionName) { if (_inputReader == null) return null; var action = _inputReader.FindAction(actionName); if (action == null) return null; // 通过 binding.groups 过滤,只返回匹配当前设备控制方案的绑定路径 string schemeFilter = GetControlSchemeForDevice(CurrentDevice); foreach (var binding in action.bindings) { // 跳过复合绑定的父条目(无实际路径) if (binding.isComposite) continue; // 若 binding.groups 不含当前方案,则跳过(允许空 groups 的绑定匹配所有设备) if (!string.IsNullOrEmpty(schemeFilter) && !string.IsNullOrEmpty(binding.groups) && !binding.groups.Contains(schemeFilter, StringComparison.OrdinalIgnoreCase)) continue; // effectivePath 已自动合并 overridePath(改键后的路径) var path = binding.effectivePath; if (!string.IsNullOrEmpty(path)) return path; } return null; } // ── Helpers ─────────────────────────────────────────────────────────── /// 将设备类型映射到 InputActionAsset 中配置的控制方案名称。 private static string GetControlSchemeForDevice(InputDeviceType device) => device switch { InputDeviceType.KeyboardMouse => "Keyboard&Mouse", _ => "Gamepad", // Xbox / PS / Switch 共用 Gamepad 方案 }; } }