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