130 lines
5.1 KiB
C#
130 lines
5.1 KiB
C#
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(兼容/装饰用):直接填写固定路径(如 "<Keyboard>/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;
|
||
|
||
// ── 静态注册表:替换 FindObjectsByType,O(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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|