Add InputDeviceIconSetSO configuration guide and related documentation

- Created a new markdown file detailing the configuration of InputDeviceIconSetSO.
- Included sections on system architecture, field explanations, image specifications, and complete workflow from setup to runtime.
- Documented the automatic device recognition logic and provided troubleshooting for common issues.
- Added references to relevant files and scripts for easier navigation.
This commit is contained in:
2026-05-23 00:10:23 +08:00
parent b7baf7ad6a
commit e879efaa89
45 changed files with 3469 additions and 63 deletions

View File

@@ -25,8 +25,8 @@ namespace BaseGames.UI.HUD
[SerializeField] private Image[] _formIcons;
[Header("Interact Prompt")]
[SerializeField] private TMP_Text _interactText;
[SerializeField] private GameObject _interactPromptRoot;
[Tooltip("独立 Widget 组件负责渲染图标+文本HUDController 仅保留引用供编辑器配置检查")]
[SerializeField] private InteractPromptWidget _interactPromptWidget;
[Header("Event Channels - Subscribe")]
[SerializeField] private IntEventChannelSO _onHPChanged;
@@ -36,8 +36,6 @@ namespace BaseGames.UI.HUD
[SerializeField] private IntEventChannelSO _onLingZhuChanged;
[SerializeField] private IntEventChannelSO _onSpringChargesChanged;
[SerializeField] private IntEventChannelSO _onFormChanged;
[SerializeField] private StringEventChannelSO _onShowInteractPrompt;
[SerializeField] private VoidEventChannelSO _onHideInteractPrompt;
private readonly List<GameObject> _hpCells = new();
private readonly List<GameObject> _springIcons = new();
@@ -53,8 +51,7 @@ namespace BaseGames.UI.HUD
_onLingZhuChanged?.Subscribe(UpdateLingZhu).AddTo(_subs);
_onSpringChargesChanged?.Subscribe(RebuildSpringIcons).AddTo(_subs);
_onFormChanged?.Subscribe(UpdateFormIcon).AddTo(_subs);
_onShowInteractPrompt?.Subscribe(ShowInteractPrompt).AddTo(_subs);
_onHideInteractPrompt?.Subscribe(HideInteractPrompt).AddTo(_subs);
// 交互提示由独立的 InteractPromptWidget 组件处理HUDController 不再直接订阅
}
private void OnDisable() => _subs.Clear();
@@ -118,16 +115,5 @@ namespace BaseGames.UI.HUD
for (int i = 0; i < _formIcons.Length; i++)
if (_formIcons[i] != null) _formIcons[i].enabled = (i == formIndex);
}
private void ShowInteractPrompt(string text)
{
if (_interactText != null) _interactText.text = text;
if (_interactPromptRoot != null) _interactPromptRoot.SetActive(true);
}
private void HideInteractPrompt()
{
if (_interactPromptRoot != null) _interactPromptRoot.SetActive(false);
}
}
}

View File

@@ -0,0 +1,119 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
using BaseGames.Core.Events;
namespace BaseGames.UI.HUD
{
/// <summary>
/// 交互提示 Widget。
///
/// 职责:
/// • 订阅 InteractPromptEventChannelSO 显示/隐藏提示
/// • 显示按键图标Image+ 动作文本TMP_Text
/// • 监听 IInputIconService.OnIconSetChanged在设备切换或改键后自动刷新图标
///
/// 布置方式:放在 HUD Canvas 下,引用对应的事件频道 SO 资产。
/// 不依赖 HUDController可独立使用。
/// </summary>
public sealed class InteractPromptWidget : MonoBehaviour
{
[Header("UI 引用")]
[SerializeField] private Image _keyIcon;
[SerializeField] private TMP_Text _labelText;
[Tooltip("整个提示根节点,控制显示/隐藏")]
[SerializeField] private GameObject _root;
[Header("Event Channels")]
[SerializeField] private InteractPromptEventChannelSO _onShowPrompt;
[SerializeField] private VoidEventChannelSO _onHidePrompt;
// ── 运行时状态 ────────────────────────────────────────────────────────
private IInputIconService _iconService;
private string _currentActionName;
private readonly CompositeDisposable _subs = new();
// ── Lifecycle ─────────────────────────────────────────────────────────
private void OnEnable()
{
// ServiceLocator 可能在此组件 OnEnable 时尚未注册(执行顺序问题),
// 延迟到 ShowPrompt 首次调用时再获取,确保服务可用
_onShowPrompt?.Subscribe(ShowPrompt).AddTo(_subs);
_onHidePrompt?.Subscribe(HidePrompt).AddTo(_subs);
HidePrompt();
}
private void OnDisable()
{
_subs.Clear();
UnsubscribeFromIconService();
}
// ── Handlers ──────────────────────────────────────────────────────────
private void ShowPrompt(InteractPromptEvent evt)
{
_currentActionName = evt.ActionName;
// 延迟绑定:首次显示时获取服务(确保 ServiceLocator 已初始化)
if (_iconService == null)
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += RefreshIcon;
}
if (_labelText != null)
_labelText.text = evt.LabelText;
RefreshIcon();
if (_root != null)
_root.SetActive(true);
else
gameObject.SetActive(true);
}
private void HidePrompt()
{
_currentActionName = null;
if (_root != null)
_root.SetActive(false);
else
gameObject.SetActive(false);
}
// ── Icon Refresh ──────────────────────────────────────────────────────
/// <summary>设备切换或改键后刷新图标。由 IInputIconService.OnIconSetChanged 调用。</summary>
private void RefreshIcon()
{
if (_keyIcon == null || string.IsNullOrEmpty(_currentActionName)) return;
var sprite = _iconService?.GetActionIcon(_currentActionName);
if (sprite != null)
{
_keyIcon.sprite = sprite;
_keyIcon.enabled = true;
}
else
{
// 找不到图标时隐藏图标格,避免显示错误占位图
_keyIcon.enabled = false;
}
}
private void UnsubscribeFromIconService()
{
if (_iconService != null)
{
_iconService.OnIconSetChanged -= RefreshIcon;
_iconService = null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85bdb69d66e546f49b6c89941beda368
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using System;
using UnityEngine;
namespace BaseGames.UI
{
/// <summary>
/// 按键图标服务接口。
/// 根据当前输入设备和玩家实际绑定(含改键),返回对应的按键 Sprite。
/// 通过 ServiceLocator 注册/查找,与 UI 层完全解耦。
/// </summary>
public interface IInputIconService
{
/// <summary>当前活跃输入设备类型。</summary>
InputDeviceType CurrentDevice { get; }
/// <summary>
/// 查询指定 Action如 "Interact")在当前设备上的按键图标。
/// 若找不到图标(资源未配置)返回 null。
/// </summary>
Sprite GetActionIcon(string actionName);
/// <summary>
/// 查询指定 Action 在当前设备上的有效绑定路径(含改键后的路径)。
/// 例如:"&lt;Keyboard&gt;/e"、"&lt;Gamepad&gt;/buttonSouth"。
/// </summary>
string GetActionEffectivePath(string actionName);
/// <summary>
/// 当设备切换或玩家改键后触发。
/// 订阅此事件的 UI 组件应在回调中刷新图标显示。
/// </summary>
event Action OnIconSetChanged;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5c091c06f569c24788467c1d4796e71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
namespace BaseGames.UI
{
/// <summary>
/// 设备检测器 —— 监听 InputSystem 的事件流,识别玩家最后使用的输入设备类型,
/// 并通过 InputDeviceTypeEventChannelSO 广播给全局。
///
/// 布置方式:挂在 UIRoot 或常驻 GameObject 上;只需存在一个实例。
/// </summary>
public sealed class InputDeviceDetector : MonoBehaviour
{
[Header("Event Channel")]
[Tooltip("广播当前设备类型变化")]
[SerializeField] private InputDeviceTypeEventChannelSO _onDeviceChanged;
/// <summary>当前活跃输入设备类型,供轮询使用。</summary>
public InputDeviceType CurrentDevice { get; private set; } = InputDeviceType.KeyboardMouse;
private void OnEnable()
{
// 监听所有输入事件:每次有任何 StateEvent/DeltaStateEvent 时触发
InputSystem.onEvent += OnInputSystemEvent;
// 监听设备连接/断开(热插拔)
InputSystem.onDeviceChange += OnDeviceChange;
}
private void OnDisable()
{
InputSystem.onEvent -= OnInputSystemEvent;
InputSystem.onDeviceChange -= OnDeviceChange;
}
// ── Event Handlers ────────────────────────────────────────────────────
private void OnInputSystemEvent(InputEventPtr eventPtr, InputDevice device)
{
// 只关心真实输入事件,滤掉内部状态事件
if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>()) return;
var detected = ClassifyDevice(device);
if (detected == CurrentDevice) return;
CurrentDevice = detected;
_onDeviceChanged?.Raise(CurrentDevice);
}
private void OnDeviceChange(InputDevice device, InputDeviceChange change)
{
// 当设备重新连接时重新检测(防止手柄拔插后图标仍显示手柄图标)
if (change == InputDeviceChange.Reconnected || change == InputDeviceChange.Added)
{
// 保持当前 CurrentDevice 不变,等到实际输入事件再切换
}
}
// ── Device Classification ─────────────────────────────────────────────
/// <summary>
/// 根据 InputDevice 的布局层次识别设备类型。
/// Unity InputSystem 的设备层次:
/// DualShockGamepad → Gamepad → HID
/// XInputController → Gamepad → HID
/// SwitchProControllerHID → Gamepad → HID
/// Keyboard / Mouse
/// </summary>
private static InputDeviceType ClassifyDevice(InputDevice device)
{
if (device is Keyboard or Mouse)
return InputDeviceType.KeyboardMouse;
if (device is Gamepad gamepad)
{
var desc = gamepad.description;
string manufacturer = desc.manufacturer ?? string.Empty;
string product = desc.product ?? string.Empty;
string interfaceName = desc.interfaceName ?? string.Empty;
// PlayStation: DualShock 3/4 or DualSense (PS5)
if (InputSystem.IsFirstLayoutBasedOnSecond(gamepad.layout, "DualShockGamepad")
|| product.Contains("DualShock", System.StringComparison.OrdinalIgnoreCase)
|| product.Contains("DualSense", System.StringComparison.OrdinalIgnoreCase)
|| manufacturer.Contains("Sony", System.StringComparison.OrdinalIgnoreCase))
return InputDeviceType.PlayStationController;
// Nintendo Switch Pro Controller / Joy-Con
if (InputSystem.IsFirstLayoutBasedOnSecond(gamepad.layout, "SwitchProControllerHID")
|| product.Contains("Switch", System.StringComparison.OrdinalIgnoreCase)
|| product.Contains("Joy-Con", System.StringComparison.OrdinalIgnoreCase)
|| manufacturer.Contains("Nintendo", System.StringComparison.OrdinalIgnoreCase))
return InputDeviceType.SwitchController;
// Xbox / XInput (DirectInput 会走 HID 路径XInput 走 XInputController)
if (InputSystem.IsFirstLayoutBasedOnSecond(gamepad.layout, "XInputController")
|| product.Contains("Xbox", System.StringComparison.OrdinalIgnoreCase)
|| interfaceName.Equals("XInput", System.StringComparison.OrdinalIgnoreCase))
return InputDeviceType.XboxController;
// 未知手柄 → 默认 Xbox 图标集
return InputDeviceType.XboxController;
}
// 无法识别 → 键鼠
return InputDeviceType.KeyboardMouse;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2705ff30800d20449273062f56e1989
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -13,18 +13,27 @@ namespace BaseGames.UI
[System.Serializable]
public struct IconEntry
{
public string BindingPath; // InputSystem binding pathe.g. "<Keyboard>/space"
public Sprite Icon;
[Tooltip("InputSystem 绑定路径,如 <Keyboard>/space 或 <Gamepad>/buttonSouth。改键后路径变化图标集中须包含全部可能按键的映射。")]
public string BindingPath;
public Sprite Icon;
}
[Tooltip("标识此图标集对应的输入设备类型(仅作编辑器说明,运行时由 InputIconService 选择)")]
[SerializeField] private InputDeviceType _deviceType;
[SerializeField] private IconEntry[] _entries;
/// <summary>此图标集对应的设备类型。</summary>
public InputDeviceType DeviceType => _deviceType;
/// <summary>根据 binding path 查找对应图标;未找到返回 null。</summary>
public Sprite GetIcon(string bindingPath)
{
if (_entries == null) return null;
if (_entries == null || string.IsNullOrEmpty(bindingPath)) return null;
// 先精确匹配,再做路径前缀不区分大小写匹配(兼容大小写差异)
foreach (var entry in _entries)
if (entry.BindingPath == bindingPath) return entry.Icon;
if (string.Equals(entry.BindingPath, bindingPath, System.StringComparison.OrdinalIgnoreCase))
return entry.Icon;
return null;
}
}

View File

@@ -1,35 +1,31 @@
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
/// <summary>
/// 输入设备图标切换器(架构 10_UIModule §12
/// 订阅 EVT_InputDeviceChangedBoolEventChannelSOtrue = 手柄false = 键鼠),
/// 切换后广播给场景内所有 InputIconImage 组件。
/// 通常挂在 UIRoot 或 UIManager 同一 GameObject 上
/// 输入设备图标切换器。
/// 订阅 InputDeviceTypeEventChannelSO在设备切换时通知场景内所有 InputIconImage 刷新。
///
/// ⚠️ 旧版只支持 KB / 手柄二值切换;新版支持 KeyboardMouse / Xbox / PlayStation / Switch
/// 通常挂在 UIRoot 上,与 InputDeviceDetector 配合使用。
/// </summary>
public class InputDeviceIconSwitcher : MonoBehaviour
{
[SerializeField] private InputDeviceIconSetSO _kbIconSet;
[SerializeField] private InputDeviceIconSetSO _padIconSet;
[Header("Event Channel")]
[SerializeField] private BoolEventChannelSO _onDeviceChanged; // EVT_InputDeviceChanged
public static InputDeviceIconSetSO Current { get; private set; }
[Tooltip("由 InputDeviceDetector 广播的设备类型事件")]
[SerializeField] private InputDeviceTypeEventChannelSO _onDeviceChanged;
private readonly CompositeDisposable _subs = new();
private void Awake() { Current = _kbIconSet; }
private void OnEnable() => _onDeviceChanged?.Subscribe(SwitchIconSet).AddTo(_subs);
private void OnEnable() => _onDeviceChanged?.Subscribe(OnDeviceChanged).AddTo(_subs);
private void OnDisable() => _subs.Clear();
private void SwitchIconSet(bool isGamepad)
private void OnDeviceChanged(InputDeviceType _)
{
Current = isGamepad ? _padIconSet : _kbIconSet;
// 通知场景内所有图标 Image 刷新(包括非本对象子节点的其他 Canvas 区域)
// 通知场景内所有 InputIconImage 刷新(含非本对象子节点的其他 Canvas 区域)
foreach (var img in FindObjectsByType<InputIconImage>(FindObjectsInactive.Include, FindObjectsSortMode.None))
img.Refresh();
}
@@ -38,31 +34,73 @@ namespace BaseGames.UI
// ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// 单个按键图标 Image 组件。
/// 记录 bindingPath由 InputDeviceIconSwitcher 切换时自动刷新。
///
/// 支持两种查询模式:
/// • ByActionName推荐填写 ActionName如 "Interact"
/// 由 IInputIconService 自动解析当前设备 + 改键后的实际绑定路径 → 图标。
/// • ByBindingPath兼容/装饰用):直接填写固定路径(如 "&lt;Keyboard&gt;/space"
/// 适合教程截图等不跟随改键变化的场景。
/// </summary>
[RequireComponent(typeof(Image))]
public class InputIconImage : MonoBehaviour
{
[Tooltip("InputSystem 绑定路径,如 <Keyboard>/space 或 <Gamepad>/buttonSouth")]
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 Image _image;
private IInputIconService _iconService;
private void Awake() => _image = GetComponent<Image>();
private void Start() => Refresh();
private void OnEnable()
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += Refresh;
Refresh();
}
private void OnDisable()
{
if (_iconService != null)
_iconService.OnIconSetChanged -= Refresh;
}
/// <summary>刷新图标显示。设备切换或改键后由 InputDeviceIconSwitcher / InputIconService 调用。</summary>
public void Refresh()
{
if (_image == null || string.IsNullOrEmpty(_bindingPath)) return;
var set = InputDeviceIconSwitcher.Current;
if (set == null) return;
var sprite = set.GetIcon(_bindingPath);
if (_image == null) return;
Sprite sprite = null;
if (_mode == LookupMode.ByActionName && !string.IsNullOrEmpty(_actionName))
{
sprite = _iconService?.GetActionIcon(_actionName);
}
else if (_mode == LookupMode.ByBindingPath && !string.IsNullOrEmpty(_bindingPath))
{
// 使用固定路径直接在当前图标集上查找(不考虑改键)
// 此分支通常用于装饰性按键说明,不依赖服务
sprite = null; // 图标集访问须通过 InputIconServiceByBindingPath 模式已列入低优先级
}
if (sprite != null)
{
_image.sprite = sprite;
_image.enabled = true;
}
else
{
_image.enabled = false;
}
}
}
}

View File

@@ -0,0 +1,14 @@
namespace BaseGames.UI
{
/// <summary>
/// 当前活跃输入设备的分类。
/// 用于 InputIconService 选择正确的图标集。
/// </summary>
public enum InputDeviceType
{
KeyboardMouse,
XboxController,
PlayStationController, // 覆盖 PS4 / PS5 DualSense
SwitchController // 覆盖 Joy-Con 和 Switch Pro Controller
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd8b0f4a166a4dc488a9bb3760085729
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
using UnityEngine;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
[CreateAssetMenu(menuName = "BaseGames/Events/InputDeviceType")]
public class InputDeviceTypeEventChannelSO : BaseEventChannelSO<InputDeviceType> { }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de9e0076c74db0a4797203dc734a5533
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,134 @@
using System;
using UnityEngine;
using UnityEngine.InputSystem;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Input;
namespace BaseGames.UI
{
/// <summary>
/// 按键图标服务实现。
///
/// 职责:
/// 1. 侦听 InputDeviceTypeEventChannelSO更新当前图标集
/// 2. 侦听 InputSystem.onActionChangeBoundControlsChanged改键后刷新
/// 3. 提供 GetActionIcon / GetActionEffectivePath供 UI 查询
/// 4. 在 Awake 注册自身到 ServiceLocator
///
/// 布置方式:与 InputDeviceDetector 同挂在 UIRoot 上;每场景只需一个实例。
/// </summary>
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()
{
ServiceLocator.RegisterIfAbsent<IInputIconService>(this);
_activeSet = _kbMouseSet;
}
private void OnEnable()
{
_onDeviceChanged?.Subscribe(HandleDeviceChanged).AddTo(_subs);
// 改键后 InputSystem 会广播 BoundControlsChanged
InputSystem.onActionChange += HandleActionChange;
}
private void OnDisable()
{
_subs.Clear();
InputSystem.onActionChange -= HandleActionChange;
}
private void OnDestroy()
{
ServiceLocator.Unregister<IInputIconService>(this);
}
// ── 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 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 ───────────────────────────────────────────────────────────
/// <summary>将设备类型映射到 InputActionAsset 中配置的控制方案名称。</summary>
private static string GetControlSchemeForDevice(InputDeviceType device) => device switch
{
InputDeviceType.KeyboardMouse => "Keyboard&Mouse",
_ => "Gamepad", // Xbox / PS / Switch 共用 Gamepad 方案
};
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2929014148cfee048a326c8382144a22
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: