2025-12-09 20:31:44 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using AlicizaX.InputGlyph;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
using UnityEngine;
|
2025-12-09 20:31:44 +08:00
|
|
|
|
using UnityEngine.InputSystem;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-09 20:31:44 +08:00
|
|
|
|
public static class GlyphService
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-09 20:31:44 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 可选的全局数据库引用。你可以通过场景内的启动组件在 Awake 时赋值,
|
|
|
|
|
|
/// 或者在调用每个方法时传入 InputGlyphDatabase 参数(见方法签名)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static InputGlyphDatabase Database { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
public static string GetBindingDisplay(InputAction action, InputGlyphDatabase db = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (action == null) return string.Empty;
|
|
|
|
|
|
var control = GetBindingControl(action);
|
|
|
|
|
|
return control != null ? control.displayName : string.Empty;
|
|
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-09 20:31:44 +08:00
|
|
|
|
public static string GetBindingControlPath(InputAction action, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null, InputGlyphDatabase db = null)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-09 20:31:44 +08:00
|
|
|
|
if (action == null) return string.Empty;
|
|
|
|
|
|
var control = GetBindingControl(action, deviceOverride);
|
|
|
|
|
|
return control != null ? $"{(control.device?.displayName ?? "Unknown")}/{control.displayName}" : string.Empty;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 20:31:44 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据当前设备类别(或传入的 deviceOverride)尝试在 action.controls 中找到匹配的 control。
|
|
|
|
|
|
/// 匹配策略:检查 control.device.displayName 是否包含类别提示词(忽略大小写)。
|
|
|
|
|
|
/// 如果没有匹配项则返回第一个 control(作为最后的退路),或 null。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static InputControl GetBindingControl(InputAction action, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (action == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
var curCategory = deviceOverride ?? InputDeviceWatcher.CurrentCategory;
|
|
|
|
|
|
var hints = GetDeviceHintsForCategory(curCategory);
|
|
|
|
|
|
|
|
|
|
|
|
// 首先找匹配 hints 的 control
|
|
|
|
|
|
foreach (var control in action.controls)
|
|
|
|
|
|
{
|
|
|
|
|
|
var deviceName = control.device?.displayName ?? string.Empty;
|
|
|
|
|
|
if (hints.Any(h => deviceName.IndexOf(h, StringComparison.OrdinalIgnoreCase) >= 0))
|
|
|
|
|
|
{
|
|
|
|
|
|
return control;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有匹配,尝试返回第一个 gamepad/keyboard 优先的 control(更健壮)
|
|
|
|
|
|
if (action.controls.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 优先返回 keyboard/mouse 类型(如果当前类别是 keyboard)
|
|
|
|
|
|
if (curCategory == InputDeviceWatcher.InputDeviceCategory.Keyboard)
|
|
|
|
|
|
{
|
|
|
|
|
|
var k = action.controls.FirstOrDefault(c => (c.device?.displayName ?? "").IndexOf("Keyboard", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
|
|
|
|
?? action.controls.First();
|
|
|
|
|
|
return k;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return action.controls.First();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static string[] GetDeviceHintsForCategory(InputDeviceWatcher.InputDeviceCategory cat)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (cat)
|
|
|
|
|
|
{
|
|
|
|
|
|
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
|
|
|
|
|
return new[] { "Keyboard", "Mouse" };
|
|
|
|
|
|
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
|
|
|
|
|
return new[] { "XInput", "Xbox", "Gamepad" };
|
|
|
|
|
|
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
|
|
|
|
|
return new[] { "DualShock", "DualSense", "PlayStation", "Gamepad" };
|
|
|
|
|
|
default:
|
|
|
|
|
|
return new[] { "Gamepad", "Joystick", "Keyboard", "Mouse" };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 尝试根据 controlPath 和设备获取 TMP sprite 标签;如果失败会返回 displayFallback(可直接显示的文字)。
|
|
|
|
|
|
/// 逻辑:
|
|
|
|
|
|
/// 1) 使用传入 db 或静态 Database;
|
|
|
|
|
|
/// 2) 先在指定设备表中查找 entry,找不到则回退到 Keyboard 表;
|
|
|
|
|
|
/// 3) 如果 table 或 tmpAsset 缺失 或 entry.Sprite 缺失,则用 displayFallback(从 controlPath 提取最后段或控制名)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static bool TryGetTMPTagForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
|
|
|
|
|
tag = null;
|
|
|
|
|
|
displayFallback = null;
|
2025-12-09 20:31:44 +08:00
|
|
|
|
db = db ?? Database;
|
|
|
|
|
|
if (string.IsNullOrEmpty(controlPath) || db == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
displayFallback = GetDisplayNameFromControlPath(controlPath);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-09 20:31:44 +08:00
|
|
|
|
var entry = db.FindEntryByControlPath(controlPath, device);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
if (entry == null)
|
|
|
|
|
|
{
|
2025-12-09 20:31:44 +08:00
|
|
|
|
// 设备缺失或没有 entry -> 回退 keyboard
|
|
|
|
|
|
entry = db.FindEntryByControlPath(controlPath, InputDeviceWatcher.InputDeviceCategory.Keyboard);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (entry == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
displayFallback = GetDisplayNameFromControlPath(controlPath);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 20:31:44 +08:00
|
|
|
|
// 查找对应表(优先目标设备,再 keyboard)
|
|
|
|
|
|
var table = db.GetTable(device) ?? db.GetTable(InputDeviceWatcher.InputDeviceCategory.Keyboard);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
if (table == null || table.tmpAsset == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
displayFallback = GetDisplayNameFromControlPath(controlPath);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var sprite = entry.Sprite;
|
|
|
|
|
|
if (sprite == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
displayFallback = GetDisplayNameFromControlPath(controlPath);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var spriteName = sprite.name;
|
|
|
|
|
|
tag = $"<sprite name=\"{spriteName}\">";
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 20:31:44 +08:00
|
|
|
|
public static bool TryGetUISpriteForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
|
|
|
|
|
sprite = null;
|
2025-12-09 20:31:44 +08:00
|
|
|
|
db = db ?? Database;
|
|
|
|
|
|
if (string.IsNullOrEmpty(controlPath) || db == null) return false;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-09 20:31:44 +08:00
|
|
|
|
var entry = db.FindEntryByControlPath(controlPath, device) ?? db.FindEntryByControlPath(controlPath, InputDeviceWatcher.InputDeviceCategory.Keyboard);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
if (entry == null) return false;
|
|
|
|
|
|
if (entry.Sprite == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
sprite = entry.Sprite;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 20:31:44 +08:00
|
|
|
|
static string GetDisplayNameFromControlPath(string controlPath)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(controlPath)) return string.Empty;
|
|
|
|
|
|
var parts = controlPath.Split('/');
|
|
|
|
|
|
var last = parts[parts.Length - 1].Trim(new char[] { '{', '}', '<', '>', '\'', '"' });
|
|
|
|
|
|
return last;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|