Files
Cielonos/Assets/Scripts/SLSUtilities/UI/InputBindingResolver.cs
SoulliesOfficial 6d7ebc5825 Passion & UI
2026-06-12 17:11:39 -04:00

250 lines
9.8 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.InputSystem;
namespace SLSUtilities.UI
{
/// <summary>
/// 从 <see cref="InputActionAsset"/> 读取当前按键绑定,
/// 将 Input Action 名映射为 InputGlyphs SpriteAsset 中的 glyph token 名。
/// <para>
/// 初始化后,<see cref="InputGlyphParser"/> 自动使用本解析器,
/// 使得本地化文本可以使用 Action 名作为 Token如 <c>[Interact]</c>
/// 无需硬编码具体按键名(如 <c>[R]</c>)。
/// 当玩家重新绑定按键时,重新调用 <see cref="Initialize"/> 即可自动更新图标。
/// </para>
/// </summary>
public static class InputBindingResolver
{
private static readonly Dictionary<string, string> ActionGlyphMap = new();
private static bool isInitialized;
/// <summary>是否已初始化。</summary>
public static bool IsInitialized => isInitialized;
// ================================================================
// Control Path → Glyph Token 特殊映射
// ================================================================
/// <summary>
/// 无法通过通用规则推导的控制路径,手动映射到 glyph token。
/// </summary>
private static readonly Dictionary<string, string> ControlPathOverrides = new()
{
// 鼠标
{ "<Mouse>/leftButton", "LMB" },
{ "<Mouse>/rightButton", "RMB" },
{ "<Mouse>/middleButton", "MMB" },
{ "<Mouse>/forwardButton", "Mouse4" },
{ "<Mouse>/backButton", "Mouse5" },
// 修饰键(左右合并为同一图标)
{ "<Keyboard>/leftCtrl", "Ctrl" },
{ "<Keyboard>/rightCtrl", "Ctrl" },
{ "<Keyboard>/leftShift", "Shift" },
{ "<Keyboard>/rightShift", "Shift" },
{ "<Keyboard>/leftAlt", "Alt" },
{ "<Keyboard>/rightAlt", "Alt" },
// 名称差异较大的按键
{ "<Keyboard>/escape", "Esc" },
{ "<Keyboard>/delete", "Del" },
{ "<Keyboard>/insert", "Ins" },
{ "<Keyboard>/pageUp", "Pgup" },
{ "<Keyboard>/pageDown", "Pgdn" },
{ "<Keyboard>/printScreen","Prtsc" },
{ "<Keyboard>/scrollLock", "Scrlk" },
{ "<Keyboard>/capsLock", "Caps" },
{ "<Keyboard>/numpadEnter","Enter" },
{ "<Keyboard>/contextMenu","Context" },
{ "<Keyboard>/leftMeta", "Windows" },
{ "<Keyboard>/rightMeta", "Windows" },
{ "<Keyboard>/backquote", "Tilde" },
{ "<Keyboard>/minus", "Hyphen" },
{ "<Keyboard>/equals", "Equals" },
{ "<Keyboard>/leftBracket","BracketOpen" },
{ "<Keyboard>/rightBracket","BracketClose" },
{ "<Keyboard>/backslash", "BackwardSlash" },
{ "<Keyboard>/semicolon", "SemiColon" },
{ "<Keyboard>/quote", "Quote" },
{ "<Keyboard>/comma", "Comma" },
{ "<Keyboard>/period", "Dot" },
{ "<Keyboard>/slash", "ForwardSlash" },
{ "<Keyboard>/numpadPlus", "Plus" },
// 箭头键
{ "<Keyboard>/upArrow", "ArrowUp" },
{ "<Keyboard>/downArrow", "ArrowDown" },
{ "<Keyboard>/leftArrow", "ArrowLeft" },
{ "<Keyboard>/rightArrow", "ArrowRight" },
};
// ================================================================
// 初始化
// ================================================================
/// <summary>
/// 从 InputActionAsset 构建 Action 名 → Glyph Token 的映射表。
/// 仅处理指定 controlScheme 的非 composite 绑定。
/// </summary>
/// <param name="inputActions">包含所有 ActionMap 的 InputActionAsset。</param>
/// <param name="controlScheme">
/// 要匹配的 Control Scheme 名称(如 "KeyboardMouse")。
/// 为 null 时取第一个匹配的绑定。
/// </param>
public static void Initialize(InputActionAsset inputActions, string controlScheme = "KeyboardMouse")
{
ActionGlyphMap.Clear();
if (inputActions == null)
{
Debug.LogWarning("[InputBindingResolver] InputActionAsset 为 null无法初始化。");
isInitialized = false;
return;
}
foreach (InputActionMap map in inputActions.actionMaps)
{
foreach (InputAction action in map.actions)
{
string glyph = ResolveFirstBinding(action, controlScheme);
if (glyph != null)
{
ActionGlyphMap[action.name] = glyph;
}
}
}
ActionGlyphMap["M0"] = ActionGlyphMap["MainWeaponPrimary"];
ActionGlyphMap["M1"] = ActionGlyphMap["MainWeaponSecondary"];
ActionGlyphMap["MA"] = ActionGlyphMap["MainWeaponSpecialA"];
ActionGlyphMap["MB"] = ActionGlyphMap["MainWeaponSpecialB"];
ActionGlyphMap["MC"] = ActionGlyphMap["MainWeaponSpecialC"];
ActionGlyphMap["S0"] = ActionGlyphMap["SupportEquipment0"];
ActionGlyphMap["S1"] = ActionGlyphMap["SupportEquipment1"];
ActionGlyphMap["S2"] = ActionGlyphMap["SupportEquipment2"];
ActionGlyphMap["S3"] = ActionGlyphMap["SupportEquipment3"];
isInitialized = true;
}
/// <summary>
/// 将 Token 解析为 glyph token 名。
/// 如果 token 匹配已注册的 Action 名,则返回对应按键的 glyph token
/// 否则原样返回(可能是直接按键名,如 "LMB"、"Q")。
/// </summary>
public static string ResolveToken(string token)
{
if (isInitialized && ActionGlyphMap.TryGetValue(token, out string glyph))
{
return glyph;
}
return token;
}
/// <summary>
/// 获取指定 Action 名当前绑定的 glyph token。
/// 未找到时返回 null。
/// </summary>
public static string GetGlyphForAction(string actionName)
{
if (isInitialized && ActionGlyphMap.TryGetValue(actionName, out string glyph))
{
return glyph;
}
return null;
}
// ================================================================
// 内部实现
// ================================================================
/// <summary>
/// 取 Action 的第一个匹配 controlScheme 的非 composite 绑定,
/// 解析其控制路径为 glyph token。
/// </summary>
private static string ResolveFirstBinding(InputAction action, string controlScheme)
{
foreach (InputBinding binding in action.bindings)
{
if (binding.isComposite || binding.isPartOfComposite)
continue;
if (!IsBindingMatchingScheme(binding, controlScheme))
continue;
string path = binding.effectivePath;
string glyph = ControlPathToGlyphToken(path);
if (glyph != null)
return glyph;
}
return null;
}
/// <summary>
/// 检查绑定是否属于指定的 Control Scheme。
/// controlScheme 为 null 时始终匹配。
/// </summary>
private static bool IsBindingMatchingScheme(InputBinding binding, string controlScheme)
{
if (string.IsNullOrEmpty(controlScheme))
return true;
// binding.groups 格式为 "KeyboardMouse" 或 "KeyboardMouse;Gamepad" 等
if (string.IsNullOrEmpty(binding.groups))
return false;
return binding.groups.Contains(controlScheme);
}
/// <summary>
/// 将 Input System 控制路径转换为 InputGlyphs SpriteAsset 中的 token 名。
/// </summary>
private static string ControlPathToGlyphToken(string controlPath)
{
if (string.IsNullOrEmpty(controlPath))
return null;
// 优先查找特殊映射
if (ControlPathOverrides.TryGetValue(controlPath, out string token))
return token;
// 通用键盘规则
const string keyboardPrefix = "<Keyboard>/";
if (controlPath.StartsWith(keyboardPrefix))
{
string key = controlPath.Substring(keyboardPrefix.Length);
return KeyNameToGlyphToken(key);
}
return null;
}
/// <summary>
/// 将 Input System 的键盘 key 名转换为 glyph token 名。
/// 规则与 <c>InputGlyphsSpriteAssetCreator.MapKeyboardName</c> 保持一致。
/// </summary>
private static string KeyNameToGlyphToken(string key)
{
if (string.IsNullOrEmpty(key))
return null;
// 单字母 → 大写
if (key.Length == 1 && char.IsLetter(key[0]))
return key.ToUpperInvariant();
// 单数字 → 原样
if (key.Length == 1 && char.IsDigit(key[0]))
return key;
// 功能键 f1-f12 → 全大写
if (key.Length >= 2 && key[0] == 'f' && int.TryParse(key.Substring(1), out _))
return key.ToUpperInvariant();
// 其余 → 首字母大写space → Space, tab → Tab, enter → Enter, etc.
return char.ToUpperInvariant(key[0]) + key.Substring(1);
}
}
}