Files
zeling_v2/Assets/_Game/Scripts/UI/InputDeviceIconSwitcher.cs
2026-06-05 18:41:33 +08:00

130 lines
5.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
/// <summary>
/// 输入设备图标切换器。
/// 订阅 InputDeviceTypeEventChannelSO在设备切换时通知场景内所有 InputIconImage 刷新。
///
/// ⚠️ 旧版只支持 KB / 手柄二值切换;新版支持 KeyboardMouse / Xbox / PlayStation / Switch。
/// 通常挂在 UIRoot 上,与 InputDeviceDetector 配合使用。
/// </summary>
public class InputDeviceIconSwitcher : MonoBehaviour
{
[Header("Event Channel")]
[Tooltip("由 InputDeviceDetector 广播的设备类型事件")]
[SerializeField] private InputDeviceTypeEventChannelSO _onDeviceChanged;
private readonly CompositeDisposable _subs = new();
private void OnEnable() => _onDeviceChanged?.Subscribe(OnDeviceChanged).AddTo(_subs);
private void OnDisable() => _subs.Clear();
private void OnDeviceChanged(InputDeviceType _)
{
// InputIconImage 已通过订阅 IInputIconService.OnIconSetChanged 自主刷新,
// 无需重复调用 RefreshAll()——否则每次设备切换每个 InputIconImage 会执行两次 Refresh。
// 此处保留供将来添加设备切换时的其他 UI 响应(提示动画、音效反馈等)。
}
}
// ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// 单个按键图标 Image 组件。
///
/// 支持两种查询模式:
/// • ByActionName推荐填写 ActionName如 "Interact"
/// 由 IInputIconService 自动解析当前设备 + 改键后的实际绑定路径 → 图标。
/// • ByBindingPath兼容/装饰用):直接填写固定路径(如 "&lt;Keyboard&gt;/space"
/// 适合教程截图等不跟随改键变化的场景。
/// </summary>
[RequireComponent(typeof(Image))]
public class InputIconImage : MonoBehaviour
{
public enum LookupMode { ByActionName, ByBindingPath }
[SerializeField] private LookupMode _mode = LookupMode.ByActionName;
[Tooltip("Action 名称,如 Interact / Jump / Attack仅 ByActionName 模式使用)")]
[SerializeField] private string _actionName;
[Tooltip("固定绑定路径,如 <Keyboard>/space仅 ByBindingPath 模式使用)")]
[SerializeField] private string _bindingPath;
private Image _image;
private IInputIconService _iconService;
// ── 静态注册表:替换 FindObjectsByTypeO(1) 注册/注销O(n) 广播 ────────
private static readonly List<InputIconImage> _registry = new();
/// <summary>通知注册表内所有已启用实例刷新图标(设备切换时调用)。</summary>
internal static void RefreshAll()
{
for (int i = _registry.Count - 1; i >= 0; i--)
{
if (_registry[i] != null) _registry[i].Refresh();
else _registry.RemoveAt(i); // 清理已销毁的残留引用
}
}
private void Awake() => _image = GetComponent<Image>();
private void OnEnable()
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += Refresh;
_registry.Add(this);
Refresh();
}
private void OnDisable()
{
if (_iconService != null)
_iconService.OnIconSetChanged -= Refresh;
_registry.Remove(this);
}
/// <summary>刷新图标显示。设备切换或改键后由 InputDeviceIconSwitcher / InputIconService 调用。</summary>
public void Refresh()
{
if (_image == null) return;
// 若组件在 IInputIconService 注册前 Enable此处补重试并补订阅
if (_iconService == null)
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += Refresh;
}
Sprite sprite = null;
if (_mode == LookupMode.ByActionName && !string.IsNullOrEmpty(_actionName))
{
sprite = _iconService?.GetActionIcon(_actionName);
}
else if (_mode == LookupMode.ByBindingPath && !string.IsNullOrEmpty(_bindingPath))
{
// 使用固定路径在当前图标集查找(不随改键变化),适合装饰性按键说明
sprite = _iconService?.GetPathIcon(_bindingPath);
}
if (sprite != null)
{
_image.sprite = sprite;
_image.enabled = true;
}
else
{
_image.enabled = false;
}
}
}
}