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",