11
This commit is contained in:
parent
97fa4770bf
commit
2b572c7989
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(cd InputGlyph && ls -la *.cs)"
|
"Bash(cd InputGlyph && ls -la *.cs)",
|
||||||
|
"Bash(ls -la InputGlyph/*.cs | grep -v Editor)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,15 @@ using UnityEngine.InputSystem;
|
|||||||
|
|
||||||
public static class GlyphService
|
public static class GlyphService
|
||||||
{
|
{
|
||||||
// Cached device hint arrays to avoid allocations
|
// 缓存的设备提示数组,避免内存分配
|
||||||
private static readonly string[] KeyboardHints = { "Keyboard", "Mouse" };
|
private static readonly string[] KeyboardHints = { "Keyboard", "Mouse" };
|
||||||
private static readonly string[] XboxHints = { "XInput", "Xbox", "Gamepad" };
|
private static readonly string[] XboxHints = { "XInput", "Xbox", "Gamepad" };
|
||||||
private static readonly string[] PlayStationHints = { "DualShock", "DualSense", "PlayStation", "Gamepad" };
|
private static readonly string[] PlayStationHints = { "DualShock", "DualSense", "PlayStation", "Gamepad" };
|
||||||
private static readonly char[] TrimChars = { '{', '}', '<', '>', '\'', '"' };
|
private static readonly char[] TrimChars = { '{', '}', '<', '>', '\'', '"' };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取输入图标数据库实例
|
||||||
|
/// </summary>
|
||||||
public static InputGlyphDatabase Database
|
public static InputGlyphDatabase Database
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -26,6 +29,13 @@ public static class GlyphService
|
|||||||
private static InputGlyphDatabase _database;
|
private static InputGlyphDatabase _database;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取输入操作的绑定控制路径
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">输入操作</param>
|
||||||
|
/// <param name="compositePartName">复合部分名称(可选)</param>
|
||||||
|
/// <param name="deviceOverride">设备类型覆盖(可选)</param>
|
||||||
|
/// <returns>绑定控制路径</returns>
|
||||||
public static string GetBindingControlPath(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
public static string GetBindingControlPath(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
||||||
{
|
{
|
||||||
if (action == null) return string.Empty;
|
if (action == null) return string.Empty;
|
||||||
@ -33,18 +43,46 @@ public static class GlyphService
|
|||||||
return binding.hasOverrides ? binding.effectivePath : binding.path;
|
return binding.hasOverrides ? binding.effectivePath : binding.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试获取输入操作的 TextMeshPro 标签
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reference">输入操作引用</param>
|
||||||
|
/// <param name="compositePartName">复合部分名称</param>
|
||||||
|
/// <param name="device">设备类型</param>
|
||||||
|
/// <param name="tag">输出的 TMP 标签</param>
|
||||||
|
/// <param name="displayFallback">显示回退文本</param>
|
||||||
|
/// <param name="db">数据库实例(可选)</param>
|
||||||
|
/// <returns>是否成功获取</returns>
|
||||||
public static bool TryGetTMPTagForActionPath(InputAction reference, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)
|
public static bool TryGetTMPTagForActionPath(InputAction reference, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)
|
||||||
{
|
{
|
||||||
string path = GetBindingControlPath(reference, compositePartName, device);
|
string path = GetBindingControlPath(reference, compositePartName, device);
|
||||||
return TryGetTMPTagForActionPath(path, device, out tag, out displayFallback, db);
|
return TryGetTMPTagForActionPath(path, device, out tag, out displayFallback, db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试获取输入操作的 UI Sprite
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reference">输入操作引用</param>
|
||||||
|
/// <param name="compositePartName">复合部分名称</param>
|
||||||
|
/// <param name="device">设备类型</param>
|
||||||
|
/// <param name="sprite">输出的 Sprite</param>
|
||||||
|
/// <param name="db">数据库实例(可选)</param>
|
||||||
|
/// <returns>是否成功获取</returns>
|
||||||
public static bool TryGetUISpriteForActionPath(InputAction reference, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)
|
public static bool TryGetUISpriteForActionPath(InputAction reference, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)
|
||||||
{
|
{
|
||||||
string path = GetBindingControlPath(reference, compositePartName, device);
|
string path = GetBindingControlPath(reference, compositePartName, device);
|
||||||
return TryGetUISpriteForActionPath(path, device, out sprite, db);
|
return TryGetUISpriteForActionPath(path, device, out sprite, db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据控制路径尝试获取 TextMeshPro 标签
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="controlPath">控制路径</param>
|
||||||
|
/// <param name="device">设备类型</param>
|
||||||
|
/// <param name="tag">输出的 TMP 标签</param>
|
||||||
|
/// <param name="displayFallback">显示回退文本</param>
|
||||||
|
/// <param name="db">数据库实例(可选)</param>
|
||||||
|
/// <returns>是否成功获取</returns>
|
||||||
public static bool TryGetTMPTagForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)
|
public static bool TryGetTMPTagForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)
|
||||||
{
|
{
|
||||||
tag = null;
|
tag = null;
|
||||||
@ -60,6 +98,14 @@ public static class GlyphService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据控制路径尝试获取 UI Sprite
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="controlPath">控制路径</param>
|
||||||
|
/// <param name="device">设备类型</param>
|
||||||
|
/// <param name="sprite">输出的 Sprite</param>
|
||||||
|
/// <param name="db">数据库实例(可选)</param>
|
||||||
|
/// <returns>是否成功获取</returns>
|
||||||
public static bool TryGetUISpriteForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)
|
public static bool TryGetUISpriteForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)
|
||||||
{
|
{
|
||||||
sprite = null;
|
sprite = null;
|
||||||
@ -70,6 +116,9 @@ public static class GlyphService
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取输入操作的绑定控制
|
||||||
|
/// </summary>
|
||||||
static InputBinding GetBindingControl(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
static InputBinding GetBindingControl(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
||||||
{
|
{
|
||||||
if (action == null) return default;
|
if (action == null) return default;
|
||||||
@ -85,7 +134,7 @@ public static class GlyphService
|
|||||||
if (!string.Equals(b.name, compositePartName, StringComparison.OrdinalIgnoreCase)) continue;
|
if (!string.Equals(b.name, compositePartName, StringComparison.OrdinalIgnoreCase)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace LINQ Any() to avoid delegate allocation
|
// 替换 LINQ Any() 以避免委托分配
|
||||||
if (!string.IsNullOrEmpty(b.path) && ContainsAnyHint(b.path, hints)) return b;
|
if (!string.IsNullOrEmpty(b.path) && ContainsAnyHint(b.path, hints)) return b;
|
||||||
if (!string.IsNullOrEmpty(b.effectivePath) && ContainsAnyHint(b.effectivePath, hints)) return b;
|
if (!string.IsNullOrEmpty(b.effectivePath) && ContainsAnyHint(b.effectivePath, hints)) return b;
|
||||||
}
|
}
|
||||||
@ -93,7 +142,10 @@ public static class GlyphService
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to avoid LINQ Any() allocation
|
// 辅助方法,避免 LINQ Any() 的内存分配
|
||||||
|
/// <summary>
|
||||||
|
/// 检查路径是否包含任何提示字符串
|
||||||
|
/// </summary>
|
||||||
static bool ContainsAnyHint(string path, string[] hints)
|
static bool ContainsAnyHint(string path, string[] hints)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < hints.Length; i++)
|
for (int i = 0; i < hints.Length; i++)
|
||||||
@ -104,6 +156,9 @@ public static class GlyphService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据设备类型获取设备提示字符串数组
|
||||||
|
/// </summary>
|
||||||
static string[] GetDeviceHintsForCategory(InputDeviceWatcher.InputDeviceCategory cat)
|
static string[] GetDeviceHintsForCategory(InputDeviceWatcher.InputDeviceCategory cat)
|
||||||
{
|
{
|
||||||
switch (cat)
|
switch (cat)
|
||||||
@ -120,6 +175,13 @@ public static class GlyphService
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从输入操作获取显示名称
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">输入操作</param>
|
||||||
|
/// <param name="compositePartName">复合部分名称(可选)</param>
|
||||||
|
/// <param name="deviceOverride">设备类型覆盖</param>
|
||||||
|
/// <returns>显示名称</returns>
|
||||||
public static string GetDisplayNameFromInputAction(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory deviceOverride = InputDeviceWatcher.InputDeviceCategory.Keyboard)
|
public static string GetDisplayNameFromInputAction(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory deviceOverride = InputDeviceWatcher.InputDeviceCategory.Keyboard)
|
||||||
{
|
{
|
||||||
if (action == null) return string.Empty;
|
if (action == null) return string.Empty;
|
||||||
@ -127,6 +189,11 @@ public static class GlyphService
|
|||||||
return binding.ToDisplayString();
|
return binding.ToDisplayString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从控制路径获取显示名称
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="controlPath">控制路径</param>
|
||||||
|
/// <returns>显示名称</returns>
|
||||||
public static string GetDisplayNameFromControlPath(string controlPath)
|
public static string GetDisplayNameFromControlPath(string controlPath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(controlPath)) return string.Empty;
|
if (string.IsNullOrEmpty(controlPath)) return string.Empty;
|
||||||
|
|||||||
@ -37,14 +37,14 @@ using Cysharp.Threading.Tasks;
|
|||||||
private string cachedSavePath;
|
private string cachedSavePath;
|
||||||
private Dictionary<string, (ActionMap map, ActionMap.Action action)> actionLookup = new Dictionary<string, (ActionMap, ActionMap.Action)>();
|
private Dictionary<string, (ActionMap map, ActionMap.Action action)> actionLookup = new Dictionary<string, (ActionMap, ActionMap.Action)>();
|
||||||
|
|
||||||
// Events to replace Rx.NET Subjects
|
// 用于替代 Rx.NET Subjects 的事件
|
||||||
private event Action _onInputsInit;
|
private event Action _onInputsInit;
|
||||||
public event Action OnInputsInit
|
public event Action OnInputsInit
|
||||||
{
|
{
|
||||||
add
|
add
|
||||||
{
|
{
|
||||||
_onInputsInit += value;
|
_onInputsInit += value;
|
||||||
// Replay behavior: if already initialized, invoke immediately
|
// 重放行为:如果已经初始化,立即调用
|
||||||
if (isInputsInitialized)
|
if (isInputsInitialized)
|
||||||
{
|
{
|
||||||
value?.Invoke();
|
value?.Invoke();
|
||||||
@ -152,7 +152,7 @@ using Cysharp.Threading.Tasks;
|
|||||||
rebindOperation?.Dispose();
|
rebindOperation?.Dispose();
|
||||||
rebindOperation = null;
|
rebindOperation = null;
|
||||||
|
|
||||||
// Clear all event handlers
|
// 清除所有事件处理器
|
||||||
_onInputsInit = null;
|
_onInputsInit = null;
|
||||||
OnApply = null;
|
OnApply = null;
|
||||||
OnRebindPrepare = null;
|
OnRebindPrepare = null;
|
||||||
@ -163,19 +163,19 @@ using Cysharp.Threading.Tasks;
|
|||||||
|
|
||||||
private void BuildActionMap()
|
private void BuildActionMap()
|
||||||
{
|
{
|
||||||
// Pre-allocate with known capacity to avoid resizing
|
// 预分配已知容量以避免调整大小
|
||||||
int mapCount = actions.actionMaps.Count;
|
int mapCount = actions.actionMaps.Count;
|
||||||
actionMap.Clear();
|
actionMap.Clear();
|
||||||
actionLookup.Clear();
|
actionLookup.Clear();
|
||||||
|
|
||||||
// Estimate total action count for better allocation
|
// 估算总操作数以便更好地分配内存
|
||||||
int estimatedActionCount = 0;
|
int estimatedActionCount = 0;
|
||||||
foreach (var map in actions.actionMaps)
|
foreach (var map in actions.actionMaps)
|
||||||
{
|
{
|
||||||
estimatedActionCount += map.actions.Count;
|
estimatedActionCount += map.actions.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure capacity to avoid rehashing
|
// 确保容量以避免重新哈希
|
||||||
if (actionMap.Count == 0)
|
if (actionMap.Count == 0)
|
||||||
{
|
{
|
||||||
actionMap = new Dictionary<string, ActionMap>(mapCount);
|
actionMap = new Dictionary<string, ActionMap>(mapCount);
|
||||||
@ -187,7 +187,7 @@ using Cysharp.Threading.Tasks;
|
|||||||
var actionMapObj = new ActionMap(map);
|
var actionMapObj = new ActionMap(map);
|
||||||
actionMap.Add(map.name, actionMapObj);
|
actionMap.Add(map.name, actionMapObj);
|
||||||
|
|
||||||
// Build lookup dictionary for O(1) action access
|
// 构建查找字典以实现 O(1) 操作访问
|
||||||
foreach (var actionPair in actionMapObj.actions)
|
foreach (var actionPair in actionMapObj.actions)
|
||||||
{
|
{
|
||||||
actionLookup[actionPair.Key] = (actionMapObj, actionPair.Value);
|
actionLookup[actionPair.Key] = (actionMapObj, actionPair.Value);
|
||||||
@ -367,6 +367,11 @@ using Cysharp.Threading.Tasks;
|
|||||||
|
|
||||||
/* ---------------- Public API ---------------- */
|
/* ---------------- Public API ---------------- */
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据操作名称获取输入操作
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionName">操作名称</param>
|
||||||
|
/// <returns>输入操作,未找到则返回 null</returns>
|
||||||
public static InputAction Action(string actionName)
|
public static InputAction Action(string actionName)
|
||||||
{
|
{
|
||||||
var instance = Instance;
|
var instance = Instance;
|
||||||
@ -381,12 +386,17 @@ using Cysharp.Threading.Tasks;
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始重新绑定指定的输入操作
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionName">操作名称</param>
|
||||||
|
/// <param name="compositePartName">复合部分名称(可选)</param>
|
||||||
public static void StartRebind(string actionName, string compositePartName = null)
|
public static void StartRebind(string actionName, string compositePartName = null)
|
||||||
{
|
{
|
||||||
var action = Action(actionName);
|
var action = Action(actionName);
|
||||||
if (action == null) return;
|
if (action == null) return;
|
||||||
|
|
||||||
// decide bindingIndex & deviceMatch automatically
|
// 自动决定 bindingIndex 和 deviceMatch
|
||||||
int bindingIndex = Instance.FindBestBindingIndexForKeyboard(action, compositePartName);
|
int bindingIndex = Instance.FindBestBindingIndexForKeyboard(action, compositePartName);
|
||||||
if (bindingIndex < 0)
|
if (bindingIndex < 0)
|
||||||
{
|
{
|
||||||
@ -403,15 +413,23 @@ using Cysharp.Threading.Tasks;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取消当前的重新绑定操作
|
||||||
|
/// </summary>
|
||||||
public static void CancelRebind() => Instance.rebindOperation?.Cancel();
|
public static void CancelRebind() => Instance.rebindOperation?.Cancel();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认并应用准备好的重新绑定
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clearConflicts">是否清除冲突</param>
|
||||||
|
/// <returns>是否成功应用</returns>
|
||||||
public static async UniTask<bool> ConfirmApply(bool clearConflicts = true)
|
public static async UniTask<bool> ConfirmApply(bool clearConflicts = true)
|
||||||
{
|
{
|
||||||
if (!Instance.isApplyPending) return false;
|
if (!Instance.isApplyPending) return false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Create a copy of the prepared rebinds before clearing
|
// 在清除之前创建准备好的重绑定的副本
|
||||||
var appliedContexts = new HashSet<RebindContext>(Instance.preparedRebinds);
|
var appliedContexts = new HashSet<RebindContext>(Instance.preparedRebinds);
|
||||||
|
|
||||||
foreach (var ctx in Instance.preparedRebinds)
|
foreach (var ctx in Instance.preparedRebinds)
|
||||||
@ -454,11 +472,14 @@ using Cysharp.Threading.Tasks;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 丢弃准备好的重新绑定
|
||||||
|
/// </summary>
|
||||||
public static void DiscardPrepared()
|
public static void DiscardPrepared()
|
||||||
{
|
{
|
||||||
if (!Instance.isApplyPending) return;
|
if (!Instance.isApplyPending) return;
|
||||||
|
|
||||||
// Create a copy of the prepared rebinds before clearing (for event notification)
|
// 在清除之前创建准备好的重绑定的副本(用于事件通知)
|
||||||
var discardedContexts = new HashSet<RebindContext>(Instance.preparedRebinds);
|
var discardedContexts = new HashSet<RebindContext>(Instance.preparedRebinds);
|
||||||
|
|
||||||
Instance.preparedRebinds.Clear();
|
Instance.preparedRebinds.Clear();
|
||||||
@ -583,7 +604,7 @@ using Cysharp.Threading.Tasks;
|
|||||||
|
|
||||||
private void PrepareRebind(RebindContext context)
|
private void PrepareRebind(RebindContext context)
|
||||||
{
|
{
|
||||||
// Remove existing rebind for same action/binding if exists
|
// 如果存在相同操作/绑定的现有重绑定,则移除
|
||||||
preparedRebinds.Remove(context);
|
preparedRebinds.Remove(context);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(context.overridePath))
|
if (string.IsNullOrEmpty(context.overridePath))
|
||||||
@ -626,6 +647,9 @@ using Cysharp.Threading.Tasks;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置所有绑定到默认值
|
||||||
|
/// </summary>
|
||||||
public async UniTask ResetToDefaultAsync()
|
public async UniTask ResetToDefaultAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -661,6 +685,12 @@ using Cysharp.Threading.Tasks;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定操作的绑定路径
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionName">操作名称</param>
|
||||||
|
/// <param name="bindingIndex">绑定索引</param>
|
||||||
|
/// <returns>绑定路径,未找到则返回 null</returns>
|
||||||
public static BindingPath GetBindingPath(string actionName, int bindingIndex = 0)
|
public static BindingPath GetBindingPath(string actionName, int bindingIndex = 0)
|
||||||
{
|
{
|
||||||
var instance = Instance;
|
var instance = Instance;
|
||||||
@ -678,7 +708,13 @@ using Cysharp.Threading.Tasks;
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// choose best binding index for keyboard; if compositePartName != null then look for part
|
// 为键盘选择最佳绑定索引;如果 compositePartName != null 则查找部分
|
||||||
|
/// <summary>
|
||||||
|
/// 为键盘查找最佳的绑定索引
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">输入操作</param>
|
||||||
|
/// <param name="compositePartName">复合部分名称(可选)</param>
|
||||||
|
/// <returns>绑定索引,未找到则返回 -1</returns>
|
||||||
public int FindBestBindingIndexForKeyboard(InputAction action, string compositePartName = null)
|
public int FindBestBindingIndexForKeyboard(InputAction action, string compositePartName = null)
|
||||||
{
|
{
|
||||||
if (action == null) return -1;
|
if (action == null) return -1;
|
||||||
@ -691,14 +727,14 @@ using Cysharp.Threading.Tasks;
|
|||||||
{
|
{
|
||||||
var b = action.bindings[i];
|
var b = action.bindings[i];
|
||||||
|
|
||||||
// If searching for a specific composite part, skip non-matching bindings
|
// 如果搜索特定的复合部分,跳过不匹配的绑定
|
||||||
if (searchingForCompositePart)
|
if (searchingForCompositePart)
|
||||||
{
|
{
|
||||||
if (!b.isPartOfComposite) continue;
|
if (!b.isPartOfComposite) continue;
|
||||||
if (!string.Equals(b.name, compositePartName, StringComparison.OrdinalIgnoreCase)) continue;
|
if (!string.Equals(b.name, compositePartName, StringComparison.OrdinalIgnoreCase)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this binding is for keyboard
|
// 检查此绑定是否用于键盘
|
||||||
bool isKeyboardBinding = (!string.IsNullOrEmpty(b.path) && b.path.StartsWith(KEYBOARD_DEVICE, StringComparison.OrdinalIgnoreCase)) ||
|
bool isKeyboardBinding = (!string.IsNullOrEmpty(b.path) && b.path.StartsWith(KEYBOARD_DEVICE, StringComparison.OrdinalIgnoreCase)) ||
|
||||||
(!string.IsNullOrEmpty(b.effectivePath) && b.effectivePath.StartsWith(KEYBOARD_DEVICE, StringComparison.OrdinalIgnoreCase));
|
(!string.IsNullOrEmpty(b.effectivePath) && b.effectivePath.StartsWith(KEYBOARD_DEVICE, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
|||||||
@ -37,15 +37,21 @@ public sealed class InputGlyphDatabase : ScriptableObject
|
|||||||
// 当 FindEntryByControlPath 传空 path 时返回的占位 sprite
|
// 当 FindEntryByControlPath 传空 path 时返回的占位 sprite
|
||||||
public Sprite placeholderSprite;
|
public Sprite placeholderSprite;
|
||||||
|
|
||||||
// Cache for faster lookups
|
// 用于更快查找的缓存
|
||||||
private Dictionary<string, DeviceGlyphTable> _tableCache;
|
private Dictionary<string, DeviceGlyphTable> _tableCache;
|
||||||
private Dictionary<(string path, InputDeviceWatcher.InputDeviceCategory device), Sprite> _spriteCache;
|
private Dictionary<(string path, InputDeviceWatcher.InputDeviceCategory device), Sprite> _spriteCache;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启用时构建缓存
|
||||||
|
/// </summary>
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
BuildCache();
|
BuildCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建表和精灵的查找缓存
|
||||||
|
/// </summary>
|
||||||
private void BuildCache()
|
private void BuildCache()
|
||||||
{
|
{
|
||||||
if (_tableCache == null)
|
if (_tableCache == null)
|
||||||
@ -76,18 +82,23 @@ public sealed class InputGlyphDatabase : ScriptableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据设备名称获取设备图标表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceName">设备名称</param>
|
||||||
|
/// <returns>设备图标表</returns>
|
||||||
public DeviceGlyphTable GetTable(string deviceName)
|
public DeviceGlyphTable GetTable(string deviceName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(deviceName)) return null;
|
if (string.IsNullOrEmpty(deviceName)) return null;
|
||||||
if (tables == null) return null;
|
if (tables == null) return null;
|
||||||
|
|
||||||
// Ensure cache is built
|
// 确保缓存已构建
|
||||||
if (_tableCache == null || _tableCache.Count == 0)
|
if (_tableCache == null || _tableCache.Count == 0)
|
||||||
{
|
{
|
||||||
BuildCache();
|
BuildCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use cache for O(1) lookup
|
// 使用缓存进行 O(1) 查找
|
||||||
if (_tableCache.TryGetValue(deviceName.ToLowerInvariant(), out var table))
|
if (_tableCache.TryGetValue(deviceName.ToLowerInvariant(), out var table))
|
||||||
{
|
{
|
||||||
return table;
|
return table;
|
||||||
@ -96,6 +107,11 @@ public sealed class InputGlyphDatabase : ScriptableObject
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取平台图标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="device">设备类型</param>
|
||||||
|
/// <returns>平台图标 Sprite</returns>
|
||||||
public Sprite GetPlatformIcon(InputDeviceWatcher.InputDeviceCategory device)
|
public Sprite GetPlatformIcon(InputDeviceWatcher.InputDeviceCategory device)
|
||||||
{
|
{
|
||||||
var table = GetTable(device);
|
var table = GetTable(device);
|
||||||
@ -103,9 +119,14 @@ public sealed class InputGlyphDatabase : ScriptableObject
|
|||||||
return table.platformIcons;
|
return table.platformIcons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据设备类型获取设备图标表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="device">设备类型</param>
|
||||||
|
/// <returns>设备图标表</returns>
|
||||||
public DeviceGlyphTable GetTable(InputDeviceWatcher.InputDeviceCategory device)
|
public DeviceGlyphTable GetTable(InputDeviceWatcher.InputDeviceCategory device)
|
||||||
{
|
{
|
||||||
// Use constants to avoid string allocations
|
// 使用常量避免字符串分配
|
||||||
string name;
|
string name;
|
||||||
switch (device)
|
switch (device)
|
||||||
{
|
{
|
||||||
@ -126,6 +147,12 @@ public sealed class InputGlyphDatabase : ScriptableObject
|
|||||||
return GetTable(name);
|
return GetTable(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据控制路径和设备类型查找 Sprite
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="controlPath">控制路径</param>
|
||||||
|
/// <param name="device">设备类型</param>
|
||||||
|
/// <returns>找到的 Sprite,未找到则返回占位 Sprite</returns>
|
||||||
public Sprite FindSprite(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
|
public Sprite FindSprite(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(controlPath))
|
if (string.IsNullOrEmpty(controlPath))
|
||||||
@ -133,7 +160,7 @@ public sealed class InputGlyphDatabase : ScriptableObject
|
|||||||
return placeholderSprite;
|
return placeholderSprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cache first
|
// 首先检查缓存
|
||||||
var cacheKey = (controlPath, device);
|
var cacheKey = (controlPath, device);
|
||||||
if (_spriteCache != null && _spriteCache.TryGetValue(cacheKey, out var cachedSprite))
|
if (_spriteCache != null && _spriteCache.TryGetValue(cacheKey, out var cachedSprite))
|
||||||
{
|
{
|
||||||
@ -143,7 +170,7 @@ public sealed class InputGlyphDatabase : ScriptableObject
|
|||||||
var entry = FindEntryByControlPath(controlPath, device);
|
var entry = FindEntryByControlPath(controlPath, device);
|
||||||
var sprite = entry?.Sprite ?? placeholderSprite;
|
var sprite = entry?.Sprite ?? placeholderSprite;
|
||||||
|
|
||||||
// Cache the result (including null results to avoid repeated lookups)
|
// 缓存结果(包括 null 结果以避免重复查找)
|
||||||
if (_spriteCache != null)
|
if (_spriteCache != null)
|
||||||
{
|
{
|
||||||
_spriteCache[cacheKey] = sprite;
|
_spriteCache[cacheKey] = sprite;
|
||||||
@ -152,6 +179,12 @@ public sealed class InputGlyphDatabase : ScriptableObject
|
|||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据控制路径和设备类型查找图标条目
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="controlPath">控制路径</param>
|
||||||
|
/// <param name="device">设备类型</param>
|
||||||
|
/// <returns>找到的图标条目,未找到则返回 null</returns>
|
||||||
public GlyphEntry FindEntryByControlPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
|
public GlyphEntry FindEntryByControlPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
|
||||||
{
|
{
|
||||||
var t = GetTable(device);
|
var t = GetTable(device);
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
List<string> searchStrings = new List<string>();
|
List<string> searchStrings = new List<string>();
|
||||||
List<int> currentPages = new List<int>();
|
List<int> currentPages = new List<int>();
|
||||||
|
|
||||||
// per-table temporary fields for adding single entry (only sprite now)
|
// 每个表的临时字段,用于添加单个条目(目前仅支持 sprite)
|
||||||
List<Sprite> newEntrySprites = new List<Sprite>();
|
List<Sprite> newEntrySprites = new List<Sprite>();
|
||||||
|
|
||||||
const int itemsPerPage = 10;
|
const int itemsPerPage = 10;
|
||||||
@ -174,7 +174,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
EditorGUILayout.BeginVertical("box");
|
EditorGUILayout.BeginVertical("box");
|
||||||
if (tabIndex == tablesProp.arraySize)
|
if (tabIndex == tablesProp.arraySize)
|
||||||
{
|
{
|
||||||
// Settings
|
// 设置
|
||||||
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
EditorGUILayout.PropertyField(placeholderSpriteProp, new GUIContent("Placeholder Sprite"));
|
EditorGUILayout.PropertyField(placeholderSpriteProp, new GUIContent("Placeholder Sprite"));
|
||||||
@ -205,7 +205,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
|
|
||||||
EnsureEditorListsLength();
|
EnsureEditorListsLength();
|
||||||
|
|
||||||
// compute deviceName & runtime index for this table (used when deleting single entry)
|
// 计算此表的 deviceName 和运行时索引(用于删除单个条目时)
|
||||||
var nameProp = tableProp.FindPropertyRelative("deviceName");
|
var nameProp = tableProp.FindPropertyRelative("deviceName");
|
||||||
string deviceName = nameProp != null ? nameProp.stringValue : "";
|
string deviceName = nameProp != null ? nameProp.stringValue : "";
|
||||||
int runtimeTableIndex = MapSerializedTableToRuntimeIndex(deviceName);
|
int runtimeTableIndex = MapSerializedTableToRuntimeIndex(deviceName);
|
||||||
@ -356,10 +356,10 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
var eProp = entries.GetArrayElementAtIndex(i);
|
var eProp = entries.GetArrayElementAtIndex(i);
|
||||||
if (eProp == null) continue;
|
if (eProp == null) continue;
|
||||||
|
|
||||||
// display one entry with a small remove button on the right
|
// 显示一个条目,右侧带有小的删除按钮
|
||||||
using (new EditorGUILayout.HorizontalScope("box"))
|
using (new EditorGUILayout.HorizontalScope("box"))
|
||||||
{
|
{
|
||||||
// left: preview column
|
// 左侧:预览列
|
||||||
using (new EditorGUILayout.VerticalScope(GUILayout.Width(80)))
|
using (new EditorGUILayout.VerticalScope(GUILayout.Width(80)))
|
||||||
{
|
{
|
||||||
var spriteProp = eProp.FindPropertyRelative("Sprite");
|
var spriteProp = eProp.FindPropertyRelative("Sprite");
|
||||||
@ -385,14 +385,14 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// middle: action column
|
// 中间:操作列
|
||||||
EditorGUILayout.BeginVertical();
|
EditorGUILayout.BeginVertical();
|
||||||
var actionProp = eProp.FindPropertyRelative("action");
|
var actionProp = eProp.FindPropertyRelative("action");
|
||||||
EditorGUILayout.Space(2);
|
EditorGUILayout.Space(2);
|
||||||
EditorGUILayout.PropertyField(actionProp, GUIContent.none, GUILayout.ExpandWidth(true));
|
EditorGUILayout.PropertyField(actionProp, GUIContent.none, GUILayout.ExpandWidth(true));
|
||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
|
|
||||||
// right: small remove button
|
// 右侧:小的删除按钮
|
||||||
GUILayout.Space(6);
|
GUILayout.Space(6);
|
||||||
if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(24)))
|
if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(24)))
|
||||||
{
|
{
|
||||||
@ -403,16 +403,16 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
if (EditorUtility.DisplayDialog("Remove Entry?",
|
if (EditorUtility.DisplayDialog("Remove Entry?",
|
||||||
$"Remove entry '{(string.IsNullOrEmpty(spriteName) ? "<missing>" : spriteName)}' from table '{deviceName}'?", "Remove", "Cancel"))
|
$"Remove entry '{(string.IsNullOrEmpty(spriteName) ? "<missing>" : spriteName)}' from table '{deviceName}'?", "Remove", "Cancel"))
|
||||||
{
|
{
|
||||||
// remove from serialized array
|
// 从序列化数组中移除
|
||||||
var entriesProp = tableProp.FindPropertyRelative("entries");
|
var entriesProp = tableProp.FindPropertyRelative("entries");
|
||||||
if (entriesProp != null && i >= 0 && i < entriesProp.arraySize)
|
if (entriesProp != null && i >= 0 && i < entriesProp.arraySize)
|
||||||
{
|
{
|
||||||
entriesProp.DeleteArrayElementAtIndex(i);
|
entriesProp.DeleteArrayElementAtIndex(i);
|
||||||
// apply then remove from runtime to keep both in sync
|
// 应用后从运行时移除以保持两者同步
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove from runtime list (db.tables)
|
// 从运行时列表中移除(db.tables)
|
||||||
if (runtimeTableIndex >= 0 && db != null && db.tables != null && runtimeTableIndex < db.tables.Count)
|
if (runtimeTableIndex >= 0 && db != null && db.tables != null && runtimeTableIndex < db.tables.Count)
|
||||||
{
|
{
|
||||||
var runtimeTable = db.tables[runtimeTableIndex];
|
var runtimeTable = db.tables[runtimeTableIndex];
|
||||||
@ -425,7 +425,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
EditorUtility.SetDirty(db);
|
EditorUtility.SetDirty(db);
|
||||||
AssetDatabase.SaveAssets();
|
AssetDatabase.SaveAssets();
|
||||||
|
|
||||||
// reset paging and return to avoid continuing to iterate mutated serialized array
|
// 重置分页并返回,避免继续迭代已变更的序列化数组
|
||||||
currentPages[tabIndex] = 0;
|
currentPages[tabIndex] = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -514,7 +514,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
|
|
||||||
if (spriteProp != null) spriteProp.objectReferenceValue = sprite;
|
if (spriteProp != null) spriteProp.objectReferenceValue = sprite;
|
||||||
|
|
||||||
// leave action serialized as-is (most projects can't serialize InputAction directly here)
|
// 保持 action 序列化不变(大多数项目无法在此处直接序列化 InputAction)
|
||||||
if (actionProp != null)
|
if (actionProp != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -529,7 +529,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
// also add to runtime list
|
// 同时添加到运行时列表
|
||||||
var nameProp = tableProp.FindPropertyRelative("deviceName");
|
var nameProp = tableProp.FindPropertyRelative("deviceName");
|
||||||
string deviceName = nameProp != null ? nameProp.stringValue : "";
|
string deviceName = nameProp != null ? nameProp.stringValue : "";
|
||||||
int tableIndex = MapSerializedTableToRuntimeIndex(deviceName);
|
int tableIndex = MapSerializedTableToRuntimeIndex(deviceName);
|
||||||
|
|||||||
@ -12,6 +12,9 @@ public sealed class InputGlyphImage : MonoBehaviour
|
|||||||
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
||||||
private Sprite _cachedSprite;
|
private Sprite _cachedSprite;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启用时初始化组件并订阅设备变更事件
|
||||||
|
/// </summary>
|
||||||
void OnEnable()
|
void OnEnable()
|
||||||
{
|
{
|
||||||
if (targetImage == null) targetImage = GetComponent<Image>();
|
if (targetImage == null) targetImage = GetComponent<Image>();
|
||||||
@ -20,11 +23,17 @@ public sealed class InputGlyphImage : MonoBehaviour
|
|||||||
UpdatePrompt();
|
UpdatePrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禁用时取消订阅设备变更事件
|
||||||
|
/// </summary>
|
||||||
void OnDisable()
|
void OnDisable()
|
||||||
{
|
{
|
||||||
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备类型变更时的回调,更新图标显示
|
||||||
|
/// </summary>
|
||||||
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
||||||
{
|
{
|
||||||
if (_cachedCategory != cat)
|
if (_cachedCategory != cat)
|
||||||
@ -34,11 +43,14 @@ public sealed class InputGlyphImage : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新输入提示图标,并根据配置控制目标对象的显示/隐藏
|
||||||
|
/// </summary>
|
||||||
void UpdatePrompt()
|
void UpdatePrompt()
|
||||||
{
|
{
|
||||||
if (actionReference == null || actionReference.action == null || targetImage == null) return;
|
if (actionReference == null || actionReference.action == null || targetImage == null) return;
|
||||||
|
|
||||||
// Use cached category instead of re-querying CurrentCategory
|
// 使用缓存的设备类型,避免重复查询 CurrentCategory
|
||||||
if (GlyphService.TryGetUISpriteForActionPath(actionReference, "", _cachedCategory, out Sprite sprite))
|
if (GlyphService.TryGetUISpriteForActionPath(actionReference, "", _cachedCategory, out Sprite sprite))
|
||||||
{
|
{
|
||||||
if (_cachedSprite != sprite)
|
if (_cachedSprite != sprite)
|
||||||
|
|||||||
@ -14,6 +14,9 @@ public sealed class InputGlyphText : MonoBehaviour
|
|||||||
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
||||||
private string _cachedFormattedText;
|
private string _cachedFormattedText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启用时初始化组件并订阅设备变更事件
|
||||||
|
/// </summary>
|
||||||
void OnEnable()
|
void OnEnable()
|
||||||
{
|
{
|
||||||
if (textField == null) textField = GetComponent<TMP_Text>();
|
if (textField == null) textField = GetComponent<TMP_Text>();
|
||||||
@ -23,11 +26,17 @@ public sealed class InputGlyphText : MonoBehaviour
|
|||||||
UpdatePrompt();
|
UpdatePrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禁用时取消订阅设备变更事件
|
||||||
|
/// </summary>
|
||||||
void OnDisable()
|
void OnDisable()
|
||||||
{
|
{
|
||||||
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备类型变更时的回调,更新文本显示
|
||||||
|
/// </summary>
|
||||||
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
||||||
{
|
{
|
||||||
if (_cachedCategory != cat)
|
if (_cachedCategory != cat)
|
||||||
@ -37,11 +46,14 @@ public sealed class InputGlyphText : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新文本中的输入提示标签,使用 TextMeshPro 的 sprite 标签或文本回退
|
||||||
|
/// </summary>
|
||||||
void UpdatePrompt()
|
void UpdatePrompt()
|
||||||
{
|
{
|
||||||
if (actionReference == null || actionReference.action == null || textField == null) return;
|
if (actionReference == null || actionReference.action == null || textField == null) return;
|
||||||
|
|
||||||
// Use cached category instead of re-querying CurrentCategory
|
// 使用缓存的设备类型,避免重复查询 CurrentCategory
|
||||||
if (GlyphService.TryGetTMPTagForActionPath(actionReference, "", _cachedCategory, out string tag, out string displayFallback))
|
if (GlyphService.TryGetTMPTagForActionPath(actionReference, "", _cachedCategory, out string tag, out string displayFallback))
|
||||||
{
|
{
|
||||||
string formattedText = Utility.Text.Format(_oldText, tag);
|
string formattedText = Utility.Text.Format(_oldText, tag);
|
||||||
|
|||||||
@ -13,6 +13,9 @@ public sealed class InputGlyphUXButton : MonoBehaviour
|
|||||||
private Sprite _cachedSprite;
|
private Sprite _cachedSprite;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
|
/// <summary>
|
||||||
|
/// 编辑器验证,自动获取 UXButton 组件
|
||||||
|
/// </summary>
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
if (button == null)
|
if (button == null)
|
||||||
@ -22,6 +25,9 @@ public sealed class InputGlyphUXButton : MonoBehaviour
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启用时初始化组件并订阅设备变更事件
|
||||||
|
/// </summary>
|
||||||
void OnEnable()
|
void OnEnable()
|
||||||
{
|
{
|
||||||
if (button == null) button = GetComponent<UXButton>();
|
if (button == null) button = GetComponent<UXButton>();
|
||||||
@ -32,11 +38,17 @@ public sealed class InputGlyphUXButton : MonoBehaviour
|
|||||||
UpdatePrompt();
|
UpdatePrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禁用时取消订阅设备变更事件
|
||||||
|
/// </summary>
|
||||||
void OnDisable()
|
void OnDisable()
|
||||||
{
|
{
|
||||||
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备类型变更时的回调,更新图标显示
|
||||||
|
/// </summary>
|
||||||
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
||||||
{
|
{
|
||||||
if (_cachedCategory != cat)
|
if (_cachedCategory != cat)
|
||||||
@ -46,11 +58,14 @@ public sealed class InputGlyphUXButton : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新按钮的输入提示图标
|
||||||
|
/// </summary>
|
||||||
void UpdatePrompt()
|
void UpdatePrompt()
|
||||||
{
|
{
|
||||||
if (_actionReference == null || _actionReference.action == null || targetImage == null) return;
|
if (_actionReference == null || _actionReference.action == null || targetImage == null) return;
|
||||||
|
|
||||||
// Use cached category instead of re-querying CurrentCategory
|
// 使用缓存的设备类型,避免重复查询 CurrentCategory
|
||||||
if (GlyphService.TryGetUISpriteForActionPath(_actionReference, "", _cachedCategory, out Sprite sprite))
|
if (GlyphService.TryGetUISpriteForActionPath(_actionReference, "", _cachedCategory, out Sprite sprite))
|
||||||
{
|
{
|
||||||
if (_cachedSprite != sprite)
|
if (_cachedSprite != sprite)
|
||||||
|
|||||||
@ -21,6 +21,9 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
[Header("Behavior")] [Tooltip("如果 true,在 Prepare 后自动调用 ConfirmApply() 并保存;否则等待手动 ConfirmPrepared()/CancelPrepared()")]
|
[Header("Behavior")] [Tooltip("如果 true,在 Prepare 后自动调用 ConfirmApply() 并保存;否则等待手动 ConfirmPrepared()/CancelPrepared()")]
|
||||||
public bool autoConfirm = false;
|
public bool autoConfirm = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动时初始化并订阅事件
|
||||||
|
/// </summary>
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
if (btn != null) btn.onClick.AddListener(OnBtnClicked);
|
if (btn != null) btn.onClick.AddListener(OnBtnClicked);
|
||||||
@ -29,7 +32,7 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
|
|
||||||
if (InputBindingManager.Instance != null)
|
if (InputBindingManager.Instance != null)
|
||||||
{
|
{
|
||||||
// Subscribe to events
|
// 订阅事件
|
||||||
InputBindingManager.Instance.OnRebindPrepare += OnRebindPrepareHandler;
|
InputBindingManager.Instance.OnRebindPrepare += OnRebindPrepareHandler;
|
||||||
InputBindingManager.Instance.OnApply += OnApplyHandler;
|
InputBindingManager.Instance.OnApply += OnApplyHandler;
|
||||||
InputBindingManager.Instance.OnRebindEnd += OnRebindEndHandler;
|
InputBindingManager.Instance.OnRebindEnd += OnRebindEndHandler;
|
||||||
@ -37,6 +40,9 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禁用时取消订阅事件
|
||||||
|
/// </summary>
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
if (btn != null) btn.onClick.RemoveListener(OnBtnClicked);
|
if (btn != null) btn.onClick.RemoveListener(OnBtnClicked);
|
||||||
@ -51,6 +57,9 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重新绑定准备完成的处理器
|
||||||
|
/// </summary>
|
||||||
private void OnRebindPrepareHandler(InputBindingManager.RebindContext ctx)
|
private void OnRebindPrepareHandler(InputBindingManager.RebindContext ctx)
|
||||||
{
|
{
|
||||||
if (IsTargetContext(ctx))
|
if (IsTargetContext(ctx))
|
||||||
@ -61,11 +70,14 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用重新绑定的处理器
|
||||||
|
/// </summary>
|
||||||
private void OnApplyHandler(bool success, HashSet<InputBindingManager.RebindContext> appliedContexts)
|
private void OnApplyHandler(bool success, HashSet<InputBindingManager.RebindContext> appliedContexts)
|
||||||
{
|
{
|
||||||
if (appliedContexts != null)
|
if (appliedContexts != null)
|
||||||
{
|
{
|
||||||
// Only update if any of the applied/discarded contexts match this instance
|
// 仅当任何应用/丢弃的上下文与此实例匹配时才更新
|
||||||
foreach (var ctx in appliedContexts)
|
foreach (var ctx in appliedContexts)
|
||||||
{
|
{
|
||||||
if (IsTargetContext(ctx))
|
if (IsTargetContext(ctx))
|
||||||
@ -77,6 +89,9 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重新绑定结束的处理器
|
||||||
|
/// </summary>
|
||||||
private void OnRebindEndHandler(bool success, InputBindingManager.RebindContext context)
|
private void OnRebindEndHandler(bool success, InputBindingManager.RebindContext context)
|
||||||
{
|
{
|
||||||
if (IsTargetContext(context))
|
if (IsTargetContext(context))
|
||||||
@ -85,64 +100,84 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重新绑定冲突的处理器
|
||||||
|
/// </summary>
|
||||||
private void OnRebindConflictHandler(InputBindingManager.RebindContext prepared, InputBindingManager.RebindContext conflict)
|
private void OnRebindConflictHandler(InputBindingManager.RebindContext prepared, InputBindingManager.RebindContext conflict)
|
||||||
{
|
{
|
||||||
// Update if either the prepared or conflict context matches this instance
|
// 如果准备的或冲突的上下文匹配此实例,则更新
|
||||||
if (IsTargetContext(prepared) || IsTargetContext(conflict))
|
if (IsTargetContext(prepared) || IsTargetContext(conflict))
|
||||||
{
|
{
|
||||||
UpdateBindingText();
|
UpdateBindingText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备变更的回调
|
||||||
|
/// </summary>
|
||||||
private void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory _)
|
private void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory _)
|
||||||
{
|
{
|
||||||
UpdateBindingText();
|
UpdateBindingText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前的输入操作
|
||||||
|
/// </summary>
|
||||||
private InputAction GetAction()
|
private InputAction GetAction()
|
||||||
{
|
{
|
||||||
return InputBindingManager.Action(actionName);
|
return InputBindingManager.Action(actionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断上下文是否为目标上下文
|
||||||
|
/// </summary>
|
||||||
private bool IsTargetContext(InputBindingManager.RebindContext ctx)
|
private bool IsTargetContext(InputBindingManager.RebindContext ctx)
|
||||||
{
|
{
|
||||||
if (ctx == null || ctx.action == null) return false;
|
if (ctx == null || ctx.action == null) return false;
|
||||||
var action = GetAction();
|
var action = GetAction();
|
||||||
if (action == null) return false;
|
if (action == null) return false;
|
||||||
|
|
||||||
// Must match the action
|
// 必须匹配操作
|
||||||
if (ctx.action != action) return false;
|
if (ctx.action != action) return false;
|
||||||
|
|
||||||
// If we have a composite part specified, we need to match the binding index
|
// 如果指定了复合部分,需要匹配绑定索引
|
||||||
if (!string.IsNullOrEmpty(compositePartName))
|
if (!string.IsNullOrEmpty(compositePartName))
|
||||||
{
|
{
|
||||||
// Get the binding at the context's index
|
// 获取上下文索引处的绑定
|
||||||
if (ctx.bindingIndex < 0 || ctx.bindingIndex >= action.bindings.Count)
|
if (ctx.bindingIndex < 0 || ctx.bindingIndex >= action.bindings.Count)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var binding = action.bindings[ctx.bindingIndex];
|
var binding = action.bindings[ctx.bindingIndex];
|
||||||
|
|
||||||
// Check if the binding's name matches our composite part
|
// 检查绑定的名称是否与我们的复合部分匹配
|
||||||
return string.Equals(binding.name, compositePartName, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(binding.name, compositePartName, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no composite part specified, just matching the action is enough
|
// 如果未指定复合部分,仅匹配操作就足够了
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按钮点击的回调
|
||||||
|
/// </summary>
|
||||||
private void OnBtnClicked()
|
private void OnBtnClicked()
|
||||||
{
|
{
|
||||||
// Use manager API (we pass part name so manager can pick proper binding if needed)
|
// 使用管理器 API(我们传递部分名称,以便管理器可以在需要时选择适当的绑定)
|
||||||
InputBindingManager.StartRebind(actionName, string.IsNullOrEmpty(compositePartName) ? null : compositePartName);
|
InputBindingManager.StartRebind(actionName, string.IsNullOrEmpty(compositePartName) ? null : compositePartName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认准备好的重新绑定(公共方法)
|
||||||
|
/// </summary>
|
||||||
public async void ConfirmPrepared()
|
public async void ConfirmPrepared()
|
||||||
{
|
{
|
||||||
bool ok = await ConfirmPreparedAsync();
|
bool ok = await ConfirmPreparedAsync();
|
||||||
if (!ok) Debug.LogError("ConfirmPrepared: apply failed.");
|
if (!ok) Debug.LogError("ConfirmPrepared: apply failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确认准备好的重新绑定(异步)
|
||||||
|
/// </summary>
|
||||||
private async Task<bool> ConfirmPreparedAsync()
|
private async Task<bool> ConfirmPreparedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -157,12 +192,18 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取消准备好的重新绑定
|
||||||
|
/// </summary>
|
||||||
public void CancelPrepared()
|
public void CancelPrepared()
|
||||||
{
|
{
|
||||||
InputBindingManager.DiscardPrepared();
|
InputBindingManager.DiscardPrepared();
|
||||||
// UpdateBindingText will be called automatically via OnApply event
|
// UpdateBindingText 将通过 OnApply 事件自动调用
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新绑定文本和图标显示
|
||||||
|
/// </summary>
|
||||||
private void UpdateBindingText()
|
private void UpdateBindingText()
|
||||||
{
|
{
|
||||||
var action = GetAction();
|
var action = GetAction();
|
||||||
|
|||||||
8
Client/Assets/Test.meta
Normal file
8
Client/Assets/Test.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 120be4f03dcf52a4c827a44807402f14
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Client/Assets/Test/GameBase.dll.bytes
Normal file
BIN
Client/Assets/Test/GameBase.dll.bytes
Normal file
Binary file not shown.
7
Client/Assets/Test/GameBase.dll.bytes.meta
Normal file
7
Client/Assets/Test/GameBase.dll.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fece5a979e9f6ad489f077bad6fb358c
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Client/Assets/Test/GameBase.pdb.bytes
Normal file
BIN
Client/Assets/Test/GameBase.pdb.bytes
Normal file
Binary file not shown.
7
Client/Assets/Test/GameBase.pdb.bytes.meta
Normal file
7
Client/Assets/Test/GameBase.pdb.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d7fad1223ee359940a43eccb1814103f
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Client/Assets/Test/GameLib.dll.bytes
Normal file
BIN
Client/Assets/Test/GameLib.dll.bytes
Normal file
Binary file not shown.
7
Client/Assets/Test/GameLib.dll.bytes.meta
Normal file
7
Client/Assets/Test/GameLib.dll.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 66312dfa5dc7a594fa239d5e23983099
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Client/Assets/Test/GameLib.pdb.bytes
Normal file
BIN
Client/Assets/Test/GameLib.pdb.bytes
Normal file
Binary file not shown.
7
Client/Assets/Test/GameLib.pdb.bytes.meta
Normal file
7
Client/Assets/Test/GameLib.pdb.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0be83be7a9ef27d4a98952a568da1bc4
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Client/Assets/Test/GameLogic.dll.bytes
Normal file
BIN
Client/Assets/Test/GameLogic.dll.bytes
Normal file
Binary file not shown.
7
Client/Assets/Test/GameLogic.dll.bytes.meta
Normal file
7
Client/Assets/Test/GameLogic.dll.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ca9087bc37b3f8d48b90396b79b283f3
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Client/Assets/Test/GameLogic.pdb.bytes
Normal file
BIN
Client/Assets/Test/GameLogic.pdb.bytes
Normal file
Binary file not shown.
7
Client/Assets/Test/GameLogic.pdb.bytes.meta
Normal file
7
Client/Assets/Test/GameLogic.pdb.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 200d1a5604e5e984e89a9bf9f4317d22
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Client/Assets/Test/GameProto.dll.bytes
Normal file
BIN
Client/Assets/Test/GameProto.dll.bytes
Normal file
Binary file not shown.
7
Client/Assets/Test/GameProto.dll.bytes.meta
Normal file
7
Client/Assets/Test/GameProto.dll.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3bd16e6ed56d37c448334a4fe1a0fec3
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Client/Assets/Test/GameProto.pdb.bytes
Normal file
BIN
Client/Assets/Test/GameProto.pdb.bytes
Normal file
Binary file not shown.
7
Client/Assets/Test/GameProto.pdb.bytes.meta
Normal file
7
Client/Assets/Test/GameProto.pdb.bytes.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9eb4e0f52fdd98d4dafef1d7c14a07fb
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@ -1 +1 @@
|
|||||||
00e06e6b
|
034249e9
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"BuildPipeline": "EditorSimulateBuildPipeline",
|
"BuildPipeline": "EditorSimulateBuildPipeline",
|
||||||
"PackageName": "DefaultPackage",
|
"PackageName": "DefaultPackage",
|
||||||
"PackageVersion": "Simulate",
|
"PackageVersion": "Simulate",
|
||||||
"PackageNote": "2026/3/11 11:39:18",
|
"PackageNote": "2026/3/11 11:40:21",
|
||||||
"AssetList": [
|
"AssetList": [
|
||||||
{
|
{
|
||||||
"Address": "Click",
|
"Address": "Click",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user