From 6f93b0fc15c02ed57c812f4ac714a8b67511db9c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com>
Date: Mon, 9 Mar 2026 20:38:15 +0800
Subject: [PATCH] 1
---
Client/Assets/InputGlyph/GlyphService.cs | 66 +-
.../Assets/InputGlyph/InputBindingManager.cs | 400 ++++++++----
.../Assets/InputGlyph/InputGlyphDatabase.cs | 219 +++++--
.../InputGlyph/InputGlyphDatabaseEditor.cs | 612 ++++++++----------
Client/Assets/InputGlyph/InputGlyphImage.cs | 34 +-
Client/Assets/InputGlyph/InputGlyphText.cs | 35 +-
.../Assets/InputGlyph/InputGlyphUXButton.cs | 63 ++
.../InputGlyph/InputGlyphUXButton.cs.meta | 3 +
Client/Assets/InputGlyph/TestRebindScript.cs | 82 ++-
Client/Assets/Test/GameLogic.dll.bytes | Bin 61440 -> 61440 bytes
Client/Assets/Test/GameLogic.pdb.bytes | Bin 25200 -> 25200 bytes
Client/Assets/TestAudioPlay.cs | 7 +-
Client/Packages/com.alicizax.unity.entry | 2 +-
Client/Packages/com.alicizax.unity.framework | 2 +-
.../Packages/com.alicizax.unity.ui.extension | 2 +-
Client/UserSettings/EditorUserSettings.asset | 8 +-
Client/UserSettings/Layouts/default-2022.dwlt | 88 +--
17 files changed, 973 insertions(+), 650 deletions(-)
create mode 100644 Client/Assets/InputGlyph/InputGlyphUXButton.cs
create mode 100644 Client/Assets/InputGlyph/InputGlyphUXButton.cs.meta
diff --git a/Client/Assets/InputGlyph/GlyphService.cs b/Client/Assets/InputGlyph/GlyphService.cs
index 056a72c..4247964 100644
--- a/Client/Assets/InputGlyph/GlyphService.cs
+++ b/Client/Assets/InputGlyph/GlyphService.cs
@@ -1,15 +1,15 @@
using System;
-using System.Linq;
-using AlicizaX.InputGlyph;
using UnityEngine;
using UnityEngine.InputSystem;
public static class GlyphService
{
- ///
- /// 可选的全局数据库引用。你可以通过场景内的启动组件在 Awake 时赋值,
- /// 或者在调用每个方法时传入 InputGlyphDatabase 参数(见方法签名)。
- ///
+ // 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,22 +26,22 @@ public static class GlyphService
private static InputGlyphDatabase _database;
- public static string GetBindingControlPath(InputAction action, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
+ public static string GetBindingControlPath(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
{
if (action == null) return string.Empty;
- var binding = GetBindingControl(action, deviceOverride);
+ var binding = GetBindingControl(action, compositePartName, deviceOverride);
return binding.hasOverrides ? binding.effectivePath : binding.path;
}
- public static bool TryGetTMPTagForActionPath(InputActionReference reference, 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, device);
+ string path = GetBindingControlPath(reference, compositePartName, device);
return TryGetTMPTagForActionPath(path, device, out tag, out displayFallback, db);
}
- public static bool TryGetUISpriteForActionPath(InputActionReference reference, 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, device);
+ string path = GetBindingControlPath(reference, compositePartName, device);
return TryGetUISpriteForActionPath(path, device, out sprite, db);
}
@@ -70,52 +70,68 @@ public static class GlyphService
}
- static InputBinding GetBindingControl(InputAction action, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
+ static InputBinding GetBindingControl(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
{
if (action == null) return default;
var curCategory = deviceOverride ?? InputDeviceWatcher.CurrentCategory;
var hints = GetDeviceHintsForCategory(curCategory);
- foreach (var binding in action.bindings)
+ foreach (var b in action.bindings)
{
- var deviceName = binding.path ?? string.Empty;
- if (hints.Any(h => deviceName.IndexOf(h, StringComparison.OrdinalIgnoreCase) >= 0))
+ if (!string.IsNullOrEmpty(compositePartName))
{
- return binding;
+ if (!b.isPartOfComposite) continue;
+ if (!string.Equals(b.name, compositePartName, StringComparison.OrdinalIgnoreCase)) continue;
}
+
+ // Replace LINQ Any() to avoid delegate allocation
+ if (!string.IsNullOrEmpty(b.path) && ContainsAnyHint(b.path, hints)) return b;
+ if (!string.IsNullOrEmpty(b.effectivePath) && ContainsAnyHint(b.effectivePath, hints)) return b;
}
return default;
}
+ // Helper method to avoid LINQ Any() allocation
+ static bool ContainsAnyHint(string path, string[] hints)
+ {
+ for (int i = 0; i < hints.Length; i++)
+ {
+ if (path.IndexOf(hints[i], StringComparison.OrdinalIgnoreCase) >= 0)
+ return true;
+ }
+ return false;
+ }
+
static string[] GetDeviceHintsForCategory(InputDeviceWatcher.InputDeviceCategory cat)
{
switch (cat)
{
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
- return new[] { "Keyboard", "Mouse" };
+ return KeyboardHints;
case InputDeviceWatcher.InputDeviceCategory.Xbox:
- return new[] { "XInput", "Xbox", "Gamepad" };
+ return XboxHints;
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
- return new[] { "DualShock", "DualSense", "PlayStation", "Gamepad" };
+ return PlayStationHints;
default:
- return new[] { "XInput", "Xbox", "Gamepad" };
+ return XboxHints;
}
}
- public static string GetDisplayNameFromInputAction(InputAction reference)
+ public static string GetDisplayNameFromInputAction(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory deviceOverride = InputDeviceWatcher.InputDeviceCategory.Keyboard)
{
- string controlPath = GetBindingControlPath(reference, InputDeviceWatcher.CurrentCategory);
- return GetDisplayNameFromControlPath(controlPath);
+ if (action == null) return string.Empty;
+ var binding = GetBindingControl(action, compositePartName, deviceOverride);
+ return binding.ToDisplayString();
}
public static string GetDisplayNameFromControlPath(string controlPath)
{
if (string.IsNullOrEmpty(controlPath)) return string.Empty;
var parts = controlPath.Split('/');
- var last = parts[parts.Length - 1].Trim(new char[] { '{', '}', '<', '>', '\'', '"' });
+ var last = parts[parts.Length - 1].Trim(TrimChars);
return last;
}
}
diff --git a/Client/Assets/InputGlyph/InputBindingManager.cs b/Client/Assets/InputGlyph/InputBindingManager.cs
index 5dd84c7..2b226a5 100644
--- a/Client/Assets/InputGlyph/InputBindingManager.cs
+++ b/Client/Assets/InputGlyph/InputBindingManager.cs
@@ -9,54 +9,78 @@ using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.InputSystem;
using System.Reactive.Subjects;
-using AlicizaX.InputGlyph;
+using AlicizaX;
+
+using Cysharp.Threading.Tasks;
using RxUnit = System.Reactive.Unit;
-namespace InputRemapper
-{
- public class InputBindingManager : MonoBehaviour
+
+ public class InputBindingManager : MonoSingleton
{
public const string NULL_BINDING = "__NULL__";
+ private const string KEYBOARD_DEVICE = "";
+ private const string MOUSE_DELTA = "/delta";
+ private const string MOUSE_SCROLL = "/scroll";
+ private const string MOUSE_SCROLL_X = "/scroll/x";
+ private const string MOUSE_SCROLL_Y = "/scroll/y";
+ private const string KEYBOARD_ESCAPE = "/escape";
[Tooltip("InputActionAsset to manage")]
public InputActionAsset actions;
- [SerializeField] private InputGlyphDatabase inputGlyphDatabase;
-
public string fileName = "input_bindings.json";
public bool debugMode = false;
public Dictionary actionMap = new Dictionary();
- public List preparedRebinds = new List();
+ public HashSet preparedRebinds = new HashSet();
internal InputActionRebindingExtensions.RebindingOperation rebindOperation;
- private readonly List pressedActions = new List();
private bool isApplyPending = false;
private string defaultBindingsJson = string.Empty;
+ private string cachedSavePath;
+ private Dictionary actionLookup = new Dictionary();
- public ReplaySubject OnInputsInit = new ReplaySubject(1);
- public Subject OnApply = new Subject();
- public Subject OnRebindPrepare = new Subject();
- public Subject OnRebindStart = new Subject();
- public Subject OnRebindEnd = new Subject();
- public Subject<(RebindContext prepared, RebindContext conflict)> OnRebindConflict = new Subject<(RebindContext, RebindContext)>();
+ public readonly ReplaySubject OnInputsInit = new ReplaySubject(1);
+ public readonly Subject<(bool success, HashSet appliedContexts)> OnApply = new Subject<(bool, HashSet)>();
+ public readonly Subject OnRebindPrepare = new Subject();
+ public readonly Subject OnRebindStart = new Subject();
+ public readonly Subject<(bool success, RebindContext context)> OnRebindEnd = new Subject<(bool, RebindContext)>();
+ public readonly Subject<(RebindContext prepared, RebindContext conflict)> OnRebindConflict = new Subject<(RebindContext, RebindContext)>();
public string SavePath
{
get
{
+ if (!string.IsNullOrEmpty(cachedSavePath))
+ return cachedSavePath;
+
#if UNITY_EDITOR
string folder = Application.dataPath;
#else
string folder = Application.persistentDataPath;
#endif
- if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
- return Path.Combine(folder, fileName);
+ cachedSavePath = Path.Combine(folder, fileName);
+ return cachedSavePath;
}
}
- private void Awake()
+ private void EnsureSaveDirectoryExists()
{
+ var directory = Path.GetDirectoryName(SavePath);
+ if (!Directory.Exists(directory))
+ Directory.CreateDirectory(directory);
+ }
+
+ protected override void Awake()
+ {
+ if (_instance != null && _instance != this)
+ {
+ Destroy(gameObject);
+ return;
+ }
+
+ _instance = this;
+
if (actions == null)
{
Debug.LogError("InputBindingManager: InputActionAsset not assigned.");
@@ -69,8 +93,9 @@ namespace InputRemapper
{
defaultBindingsJson = actions.SaveBindingOverridesAsJson();
}
- catch
+ catch (Exception ex)
{
+ Debug.LogWarning($"[InputBindingManager] Failed to save default bindings: {ex.Message}");
defaultBindingsJson = string.Empty;
}
@@ -83,7 +108,10 @@ namespace InputRemapper
{
actions.LoadBindingOverridesFromJson(json);
RefreshBindingPathsFromActions();
- if (debugMode) Debug.Log($"Loaded overrides from {SavePath}");
+ if (debugMode)
+ {
+ Debug.Log($"Loaded overrides from {SavePath}");
+ }
}
}
catch (Exception ex)
@@ -96,8 +124,13 @@ namespace InputRemapper
actions.Enable();
}
- private void OnDestroy()
+ protected override void OnDestroy()
{
+ if (_instance == this)
+ {
+ _instance = null;
+ }
+
rebindOperation?.Dispose();
rebindOperation = null;
@@ -111,18 +144,45 @@ namespace InputRemapper
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)
- actionMap.Add(map.name, new ActionMap(map));
+ {
+ estimatedActionCount += map.actions.Count;
+ }
+
+ // Ensure capacity to avoid rehashing
+ if (actionMap.Count == 0)
+ {
+ actionMap = new Dictionary(mapCount);
+ actionLookup = new Dictionary(estimatedActionCount);
+ }
+
+ foreach (var map in actions.actionMaps)
+ {
+ var actionMapObj = new ActionMap(map);
+ actionMap.Add(map.name, actionMapObj);
+
+ // Build lookup dictionary for O(1) action access
+ foreach (var actionPair in actionMapObj.actions)
+ {
+ actionLookup[actionPair.Key] = (actionMapObj, actionPair.Value);
+ }
+ }
}
private void RefreshBindingPathsFromActions()
{
- foreach (var mapPair in actionMap)
+ foreach (var mapPair in actionMap.Values)
{
- foreach (var actionPair in mapPair.Value.actions)
+ foreach (var actionPair in mapPair.actions.Values)
{
- var a = actionPair.Value;
+ var a = actionPair;
foreach (var bpair in a.bindings)
{
bpair.Value.bindingPath.EffectivePath = a.action.bindings[bpair.Key].effectivePath;
@@ -131,26 +191,33 @@ namespace InputRemapper
}
}
- public class ActionMap
+ public sealed class ActionMap
{
public string name;
- public Dictionary actions = new Dictionary();
+ public Dictionary actions;
public ActionMap(InputActionMap map)
{
name = map.name;
- foreach (var action in map.actions) actions.Add(action.name, new Action(action));
+ int actionCount = map.actions.Count;
+ actions = new Dictionary(actionCount);
+ foreach (var action in map.actions)
+ {
+ actions.Add(action.name, new Action(action));
+ }
}
- public class Action
+ public sealed class Action
{
public InputAction action;
- public Dictionary bindings = new Dictionary();
+ public Dictionary bindings;
public Action(InputAction action)
{
this.action = action;
int count = action.bindings.Count;
+ bindings = new Dictionary(count);
+
for (int i = 0; i < count; i++)
{
if (action.bindings[i].isComposite)
@@ -170,28 +237,39 @@ namespace InputRemapper
void AddBinding(InputBinding binding, int bindingIndex)
{
- bindings.Add(bindingIndex, new Binding
- {
- name = binding.name,
- parentAction = action.name,
- compositePart = binding.name,
- bindingIndex = bindingIndex,
- group = binding.groups?.Split(InputBinding.Separator) ?? Array.Empty(),
- bindingPath = new BindingPath(binding.path, binding.overridePath),
- inputBinding = binding
- });
+ bindings.Add(bindingIndex, new Binding(
+ binding.name,
+ action.name,
+ binding.name,
+ bindingIndex,
+ binding.groups?.Split(InputBinding.Separator) ?? Array.Empty(),
+ new BindingPath(binding.path, binding.overridePath),
+ binding
+ ));
}
}
- public struct Binding
+ public readonly struct Binding
{
- public string name;
- public string parentAction;
- public string compositePart;
- public int bindingIndex;
- public string[] group;
- public BindingPath bindingPath;
- public InputBinding inputBinding;
+ public readonly string name;
+ public readonly string parentAction;
+ public readonly string compositePart;
+ public readonly int bindingIndex;
+ public readonly string[] group;
+ public readonly BindingPath bindingPath;
+ public readonly InputBinding inputBinding;
+
+ public Binding(string name, string parentAction, string compositePart, int bindingIndex,
+ string[] group, BindingPath bindingPath, InputBinding inputBinding)
+ {
+ this.name = name;
+ this.parentAction = parentAction;
+ this.compositePart = compositePart;
+ this.bindingIndex = bindingIndex;
+ this.group = group;
+ this.bindingPath = bindingPath;
+ this.inputBinding = inputBinding;
+ }
}
}
}
@@ -200,7 +278,7 @@ namespace InputRemapper
{
public string bindingPath;
public string overridePath;
- private readonly Subject observer = new Subject();
+ private Subject observer;
public BindingPath(string bindingPath, string overridePath)
{
@@ -214,18 +292,33 @@ namespace InputRemapper
set
{
overridePath = (value == bindingPath) ? string.Empty : value;
- observer.OnNext(RxUnit.Default);
+ observer?.OnNext(RxUnit.Default);
}
}
- public IObservable EffectivePathObservable => observer.Select(_ => EffectivePath);
+ public IObservable EffectivePathObservable
+ {
+ get
+ {
+ observer ??= new Subject();
+ return observer.Select(_ => EffectivePath);
+ }
+ }
+
+ public void Dispose()
+ {
+ observer?.OnCompleted();
+ observer?.Dispose();
+ observer = null;
+ }
}
- public class RebindContext
+ public sealed class RebindContext
{
public InputAction action;
public int bindingIndex;
public string overridePath;
+ private string cachedToString;
public RebindContext(InputAction action, int bindingIndex, string overridePath)
{
@@ -242,16 +335,28 @@ namespace InputRemapper
}
public override int GetHashCode() => (action?.name ?? string.Empty, bindingIndex).GetHashCode();
- public override string ToString() => $"{action?.name ?? ""}:{bindingIndex}";
+
+ public override string ToString()
+ {
+ if (cachedToString == null && action != null)
+ {
+ cachedToString = $"{action.name}:{bindingIndex}";
+ }
+
+ return cachedToString ?? "";
+ }
}
/* ---------------- Public API ---------------- */
public static InputAction Action(string actionName)
{
- foreach (var map in Instance.actionMap)
+ var instance = Instance;
+ if (instance == null) return null;
+
+ if (instance.actionLookup.TryGetValue(actionName, out var result))
{
- if (map.Value.actions.TryGetValue(actionName, out var a)) return a.action;
+ return result.action.action;
}
Debug.LogError($"[InputBindingManager] Could not find action '{actionName}'");
@@ -272,42 +377,61 @@ namespace InputRemapper
}
Instance.actions.Disable();
- Instance.PerformInteractiveRebinding(action, bindingIndex, "", true);
+ Instance.PerformInteractiveRebinding(action, bindingIndex, KEYBOARD_DEVICE, true);
Instance.OnRebindStart.OnNext(RxUnit.Default);
- if (Instance.debugMode) Debug.Log("[InputBindingManager] Rebind started");
+ if (Instance.debugMode)
+ {
+ Debug.Log("[InputBindingManager] Rebind started");
+ }
}
public static void CancelRebind() => Instance.rebindOperation?.Cancel();
- public static async Task ConfirmApply(bool clearConflicts = true)
+ 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)
{
- if (string.IsNullOrEmpty(ctx.overridePath))
+ if (!string.IsNullOrEmpty(ctx.overridePath))
{
+ if (ctx.overridePath == NULL_BINDING)
+ {
+ ctx.action.RemoveBindingOverride(ctx.bindingIndex);
+ }
+ else
+ {
+ ctx.action.ApplyBindingOverride(ctx.bindingIndex, ctx.overridePath);
+ }
}
- else if (ctx.overridePath == NULL_BINDING) ctx.action.RemoveBindingOverride(ctx.bindingIndex);
- else ctx.action.ApplyBindingOverride(ctx.bindingIndex, ctx.overridePath);
var bp = GetBindingPath(ctx.action.name, ctx.bindingIndex);
- if (bp != null) bp.EffectivePath = (ctx.overridePath == NULL_BINDING) ? string.Empty : ctx.overridePath;
+ if (bp != null)
+ {
+ bp.EffectivePath = (ctx.overridePath == NULL_BINDING) ? string.Empty : ctx.overridePath;
+ }
}
Instance.preparedRebinds.Clear();
await Instance.WriteOverridesToDiskAsync();
- Instance.OnApply.OnNext(true);
+ Instance.OnApply.OnNext((true, appliedContexts));
Instance.isApplyPending = false;
- if (Instance.debugMode) Debug.Log("[InputBindingManager] Apply confirmed and saved.");
+ if (Instance.debugMode)
+ {
+ Debug.Log("[InputBindingManager] Apply confirmed and saved.");
+ }
+
return true;
}
catch (Exception ex)
{
Debug.LogError("[InputBindingManager] Failed to apply binds: " + ex);
- Instance.OnApply.OnNext(false);
+ Instance.OnApply.OnNext((false, new HashSet()));
return false;
}
}
@@ -315,23 +439,34 @@ namespace InputRemapper
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();
Instance.isApplyPending = false;
- Instance.OnApply.OnNext(false);
- if (Instance.debugMode) Debug.Log("[InputBindingManager] Prepared rebinds discarded.");
+ Instance.OnApply.OnNext((false, discardedContexts));
+ if (Instance.debugMode)
+ {
+ Debug.Log("[InputBindingManager] Prepared rebinds discarded.");
+ }
}
private void PerformInteractiveRebinding(InputAction action, int bindingIndex, string deviceMatchPath = null, bool excludeMouseMovementAndScroll = true)
{
var op = action.PerformInteractiveRebinding(bindingIndex);
- if (!string.IsNullOrEmpty(deviceMatchPath)) op = op.WithControlsHavingToMatchPath(deviceMatchPath);
+ if (!string.IsNullOrEmpty(deviceMatchPath))
+ {
+ op = op.WithControlsHavingToMatchPath(deviceMatchPath);
+ }
+
if (excludeMouseMovementAndScroll)
{
- op = op.WithControlsExcluding("/delta");
- op = op.WithControlsExcluding("/scroll");
- op = op.WithControlsExcluding("/scroll/x");
- op = op.WithControlsExcluding("/scroll/y");
+ op = op.WithControlsExcluding(MOUSE_DELTA)
+ .WithControlsExcluding(MOUSE_SCROLL)
+ .WithControlsExcluding(MOUSE_SCROLL_X)
+ .WithControlsExcluding(MOUSE_SCROLL_Y);
}
rebindOperation = op
@@ -356,19 +491,27 @@ namespace InputRemapper
})
.OnComplete(opc =>
{
- if (debugMode) Debug.Log("[InputBindingManager] Rebind completed");
+ if (debugMode)
+ {
+ Debug.Log("[InputBindingManager] Rebind completed");
+ }
+
actions.Enable();
- OnRebindEnd.OnNext(true);
+ OnRebindEnd.OnNext((true, new RebindContext(action, bindingIndex, action.bindings[bindingIndex].effectivePath)));
CleanRebindOperation();
})
.OnCancel(opc =>
{
- if (debugMode) Debug.Log("[InputBindingManager] Rebind cancelled");
+ if (debugMode)
+ {
+ Debug.Log("[InputBindingManager] Rebind cancelled");
+ }
+
actions.Enable();
- OnRebindEnd.OnNext(false);
+ OnRebindEnd.OnNext((false, new RebindContext(action, bindingIndex, action.bindings[bindingIndex].effectivePath)));
CleanRebindOperation();
})
- .WithCancelingThrough("/escape")
+ .WithCancelingThrough(KEYBOARD_ESCAPE)
.Start();
}
@@ -395,17 +538,21 @@ namespace InputRemapper
private bool AnyBindingPath(string bindingPath, InputAction currentAction, int currentIndex, out (InputAction action, int bindingIndex) duplicate)
{
- foreach (var map in actionMap)
+ foreach (var map in actionMap.Values)
{
- foreach (var actionPair in map.Value.actions)
+ foreach (var actionPair in map.actions.Values)
{
- foreach (var bindingPair in actionPair.Value.bindings)
+ bool isSameAction = actionPair.action == currentAction;
+
+ foreach (var bindingPair in actionPair.bindings)
{
- if (actionPair.Value.action == currentAction && bindingPair.Key == currentIndex) continue;
- var eff = bindingPair.Value.bindingPath.EffectivePath;
- if (eff == bindingPath)
+ // Skip if it's the same action and same binding index
+ if (isSameAction && bindingPair.Key == currentIndex)
+ continue;
+
+ if (bindingPair.Value.bindingPath.EffectivePath == bindingPath)
{
- duplicate = (actionPair.Value.action, bindingPair.Key);
+ duplicate = (actionPair.action, bindingPair.Key);
return true;
}
}
@@ -418,7 +565,8 @@ namespace InputRemapper
private void PrepareRebind(RebindContext context)
{
- preparedRebinds.RemoveAll(x => x.Equals(context));
+ // Remove existing rebind for same action/binding if exists
+ preparedRebinds.Remove(context);
if (string.IsNullOrEmpty(context.overridePath))
{
@@ -434,19 +582,24 @@ namespace InputRemapper
preparedRebinds.Add(context);
isApplyPending = true;
OnRebindPrepare.OnNext(context);
- if (debugMode) Debug.Log($"Prepared rebind: {context} -> {context.overridePath}");
+ if (debugMode)
+ {
+ Debug.Log($"Prepared rebind: {context} -> {context.overridePath}");
+ }
}
}
- private async Task WriteOverridesToDiskAsync()
+ private async UniTask WriteOverridesToDiskAsync()
{
try
{
var json = actions.SaveBindingOverridesAsJson();
- var dir = Path.GetDirectoryName(SavePath);
- if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
+ EnsureSaveDirectoryExists();
using (var sw = new StreamWriter(SavePath, false)) await sw.WriteAsync(json);
- if (debugMode) Debug.Log($"Overrides saved to {SavePath}");
+ if (debugMode)
+ {
+ Debug.Log($"Overrides saved to {SavePath}");
+ }
}
catch (Exception ex)
{
@@ -455,22 +608,34 @@ namespace InputRemapper
}
}
- public async Task ResetToDefaultAsync()
+ public async UniTask ResetToDefaultAsync()
{
try
{
- if (!string.IsNullOrEmpty(defaultBindingsJson)) actions.LoadBindingOverridesFromJson(defaultBindingsJson);
+ if (!string.IsNullOrEmpty(defaultBindingsJson))
+ {
+ actions.LoadBindingOverridesFromJson(defaultBindingsJson);
+ }
else
{
- foreach (var map in actionMap)
- foreach (var a in map.Value.actions)
- for (int b = 0; b < a.Value.action.bindings.Count; b++)
- a.Value.action.RemoveBindingOverride(b);
+ foreach (var map in actionMap.Values)
+ {
+ foreach (var a in map.actions.Values)
+ {
+ for (int b = 0; b < a.action.bindings.Count; b++)
+ {
+ a.action.RemoveBindingOverride(b);
+ }
+ }
+ }
}
RefreshBindingPathsFromActions();
await WriteOverridesToDiskAsync();
- if (debugMode) Debug.Log("Reset to default and saved.");
+ if (debugMode)
+ {
+ Debug.Log("Reset to default and saved.");
+ }
}
catch (Exception ex)
{
@@ -480,17 +645,21 @@ namespace InputRemapper
public static BindingPath GetBindingPath(string actionName, int bindingIndex = 0)
{
- foreach (var map in Instance.actionMap)
+ var instance = Instance;
+ if (instance == null) return null;
+
+ if (instance.actionLookup.TryGetValue(actionName, out var result))
{
- if (map.Value.actions.TryGetValue(actionName, out var action))
+ if (result.action.bindings.TryGetValue(bindingIndex, out var binding))
{
- if (action.bindings.TryGetValue(bindingIndex, out var binding)) return binding.bindingPath;
+ return binding.bindingPath;
}
}
return null;
}
+
// choose best binding index for keyboard; if compositePartName != null then look for part
public int FindBestBindingIndexForKeyboard(InputAction action, string compositePartName = null)
{
@@ -498,36 +667,51 @@ namespace InputRemapper
int fallbackPart = -1;
int fallbackNonComposite = -1;
+ bool searchingForCompositePart = !string.IsNullOrEmpty(compositePartName);
for (int i = 0; i < action.bindings.Count; i++)
{
var b = action.bindings[i];
- if (!string.IsNullOrEmpty(compositePartName))
+ // 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));
+
if (b.isPartOfComposite)
{
if (fallbackPart == -1) fallbackPart = i;
- if (!string.IsNullOrEmpty(b.path) && b.path.StartsWith("", StringComparison.OrdinalIgnoreCase)) return i;
- if (!string.IsNullOrEmpty(b.effectivePath) && b.effectivePath.StartsWith("", StringComparison.OrdinalIgnoreCase)) return i;
+ if (isKeyboardBinding) return i;
}
else
{
if (fallbackNonComposite == -1) fallbackNonComposite = i;
- if (!string.IsNullOrEmpty(b.path) && b.path.StartsWith("", StringComparison.OrdinalIgnoreCase)) return i;
- if (!string.IsNullOrEmpty(b.effectivePath) && b.effectivePath.StartsWith("", StringComparison.OrdinalIgnoreCase)) return i;
+ if (isKeyboardBinding) return i;
}
}
- if (fallbackNonComposite >= 0) return fallbackNonComposite;
- return fallbackPart;
+ return fallbackNonComposite >= 0 ? fallbackNonComposite : fallbackPart;
+ }
+
+ public static InputBindingManager Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = FindObjectOfType();
+ }
+
+ return _instance;
+ }
}
- public static InputBindingManager Instance => _instance ??= FindObjectOfType();
private static InputBindingManager _instance;
}
-}
+
diff --git a/Client/Assets/InputGlyph/InputGlyphDatabase.cs b/Client/Assets/InputGlyph/InputGlyphDatabase.cs
index 4c5a8d8..81ad70c 100644
--- a/Client/Assets/InputGlyph/InputGlyphDatabase.cs
+++ b/Client/Assets/InputGlyph/InputGlyphDatabase.cs
@@ -5,101 +5,184 @@ using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.U2D;
-namespace AlicizaX.InputGlyph
+
+[Serializable]
+public sealed class GlyphEntry
{
- [Serializable]
- public class GlyphEntry
+ public Sprite Sprite;
+ public InputAction action;
+}
+
+[Serializable]
+public sealed class DeviceGlyphTable
+{
+ public string deviceName;
+
+ public Texture2D spriteSheetTexture;
+
+ public Sprite platformIcons;
+
+ public List entries = new List();
+}
+
+[CreateAssetMenu(fileName = "InputGlyphDatabase", menuName = "InputGlyphs/InputGlyphDatabase", order = 400)]
+public sealed class InputGlyphDatabase : ScriptableObject
+{
+ private const string DEVICE_KEYBOARD = "Keyboard";
+ private const string DEVICE_XBOX = "Xbox";
+ private const string DEVICE_PLAYSTATION = "PlayStation";
+
+ public List tables = new List();
+
+ // 当 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()
{
- public Sprite Sprite;
- public InputAction action;
+ BuildCache();
}
- [Serializable]
- public class DeviceGlyphTable
+ private void BuildCache()
{
- // Table 名称(序列化)
- public string deviceName;
+ if (_tableCache == null)
+ {
+ _tableCache = new Dictionary(tables.Count);
+ }
+ else
+ {
+ _tableCache.Clear();
+ }
- // 支持三种来源:
- // 1) TMP Sprite Asset(TextMeshPro sprite asset)
- public TMP_SpriteAsset tmpAsset;
+ if (_spriteCache == null)
+ {
+ _spriteCache = new Dictionary<(string, InputDeviceWatcher.InputDeviceCategory), Sprite>();
+ }
+ else
+ {
+ _spriteCache.Clear();
+ }
- // 2) Unity SpriteAtlas(可选)
- public SpriteAtlas spriteAtlas;
-
- // 3) Texture2D(Sprite Mode = Multiple),在 Sprite Editor 切好的切片
- public Texture2D spriteSheetTexture;
-
- public List entries = new List();
+ for (int i = 0; i < tables.Count; i++)
+ {
+ var table = tables[i];
+ if (table != null && !string.IsNullOrEmpty(table.deviceName))
+ {
+ _tableCache[table.deviceName.ToLowerInvariant()] = table;
+ }
+ }
}
- [CreateAssetMenu(fileName = "InputGlyphDatabase", menuName = "InputGlyphs/InputGlyphDatabase", order = 400)]
- public class InputGlyphDatabase : ScriptableObject
+ public DeviceGlyphTable GetTable(string deviceName)
{
- public List tables = new List();
+ if (string.IsNullOrEmpty(deviceName)) return null;
+ if (tables == null) return null;
- // 当 FindEntryByControlPath 传空 path 时返回的占位 sprite
- public Sprite placeholderSprite;
-
- public DeviceGlyphTable GetTable(string deviceName)
+ // Ensure cache is built
+ if (_tableCache == null || _tableCache.Count == 0)
{
- if (string.IsNullOrEmpty(deviceName)) return null;
- if (tables == null) return null;
- for (int i = 0; i < tables.Count; ++i)
- {
- var t = tables[i];
- if (t == null) continue;
- if (string.Equals(t.deviceName, deviceName, StringComparison.OrdinalIgnoreCase))
- return t;
- }
- return null;
+ BuildCache();
}
- // 兼容枚举版本(示例)
- public DeviceGlyphTable GetTable(InputDeviceWatcher.InputDeviceCategory device)
+ // Use cache for O(1) lookup
+ if (_tableCache.TryGetValue(deviceName.ToLowerInvariant(), out var table))
{
- string name = "Other";
- switch (device)
- {
- case InputDeviceWatcher.InputDeviceCategory.Keyboard: name = "Keyboard"; break;
- case InputDeviceWatcher.InputDeviceCategory.Xbox: name = "Xbox"; break;
- case InputDeviceWatcher.InputDeviceCategory.PlayStation: name = "PlayStation"; break;
- default: name = "Xbox"; break;
- }
- return GetTable(name);
+ return table;
}
- public Sprite FindSprite(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
+ return null;
+ }
+
+ public Sprite GetPlatformIcon(InputDeviceWatcher.InputDeviceCategory device)
+ {
+ var table = GetTable(device);
+ if (table == null) return null;
+ return table.platformIcons;
+ }
+
+ public DeviceGlyphTable GetTable(InputDeviceWatcher.InputDeviceCategory device)
+ {
+ // Use constants to avoid string allocations
+ string name;
+ switch (device)
{
- var entry = FindEntryByControlPath(controlPath, device);
- if (string.IsNullOrEmpty(controlPath) || entry == null)
- {
- return placeholderSprite;
- }
- return entry.Sprite;
+ case InputDeviceWatcher.InputDeviceCategory.Keyboard:
+ name = DEVICE_KEYBOARD;
+ break;
+ case InputDeviceWatcher.InputDeviceCategory.Xbox:
+ name = DEVICE_XBOX;
+ break;
+ case InputDeviceWatcher.InputDeviceCategory.PlayStation:
+ name = DEVICE_PLAYSTATION;
+ break;
+ default:
+ name = DEVICE_XBOX;
+ break;
}
- public GlyphEntry FindEntryByControlPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
+ return GetTable(name);
+ }
+
+ public Sprite FindSprite(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
+ {
+ if (string.IsNullOrEmpty(controlPath))
{
- var t = GetTable(device);
- if (t != null)
+ return placeholderSprite;
+ }
+
+ // Check cache first
+ var cacheKey = (controlPath, device);
+ if (_spriteCache != null && _spriteCache.TryGetValue(cacheKey, out var cachedSprite))
+ {
+ return cachedSprite ?? placeholderSprite;
+ }
+
+ var entry = FindEntryByControlPath(controlPath, device);
+ var sprite = entry?.Sprite ?? placeholderSprite;
+
+ // Cache the result (including null results to avoid repeated lookups)
+ if (_spriteCache != null)
+ {
+ _spriteCache[cacheKey] = sprite;
+ }
+
+ return sprite;
+ }
+
+ public GlyphEntry FindEntryByControlPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
+ {
+ var t = GetTable(device);
+ if (t != null && t.entries != null)
+ {
+ for (int i = 0; i < t.entries.Count; ++i)
{
- for (int i = 0; i < t.entries.Count; ++i)
+ var e = t.entries[i];
+ if (e == null) continue;
+ if (e.action == null) continue;
+
+ var bindings = e.action.bindings;
+ int bindingCount = bindings.Count;
+ if (bindingCount <= 0) continue;
+
+ for (int j = 0; j < bindingCount; j++)
{
- var e = t.entries[i];
- if (e == null) continue;
- if (e.action == null) continue;
- if (e.action.bindings.Count <= 0) continue;
- foreach (var binding in e.action.bindings)
+ var b = bindings[j];
+ if (!string.IsNullOrEmpty(b.path) && string.Equals(b.path, controlPath, StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(controlPath, binding.path, StringComparison.OrdinalIgnoreCase))
- {
- return e;
- }
+ return e;
+ }
+
+ if (!string.IsNullOrEmpty(b.effectivePath) && string.Equals(b.effectivePath, controlPath, StringComparison.OrdinalIgnoreCase))
+ {
+ return e;
}
}
}
- return null;
}
+
+ return null;
}
}
diff --git a/Client/Assets/InputGlyph/InputGlyphDatabaseEditor.cs b/Client/Assets/InputGlyph/InputGlyphDatabaseEditor.cs
index 9baa8b0..ea71041 100644
--- a/Client/Assets/InputGlyph/InputGlyphDatabaseEditor.cs
+++ b/Client/Assets/InputGlyph/InputGlyphDatabaseEditor.cs
@@ -1,12 +1,7 @@
using System;
using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using AlicizaX.InputGlyph;
using UnityEditor;
using UnityEngine;
-using UnityEngine.U2D;
-using TMPro;
[CustomEditor(typeof(InputGlyphDatabase))]
public class InputGlyphDatabaseEditor : Editor
@@ -22,6 +17,9 @@ public class InputGlyphDatabaseEditor : Editor
List searchStrings = new List();
List currentPages = new List();
+ // per-table temporary fields for adding single entry (only sprite now)
+ List newEntrySprites = new List();
+
const int itemsPerPage = 10;
const int previewSize = 52;
@@ -97,9 +95,11 @@ public class InputGlyphDatabaseEditor : Editor
var nameProp = t.FindPropertyRelative("deviceName");
if (nameProp != null && string.Equals(nameProp.stringValue, trimmed, StringComparison.OrdinalIgnoreCase))
{
- exists = true; break;
+ exists = true;
+ break;
}
}
+
if (exists)
{
EditorUtility.DisplayDialog("Duplicate", "A table with that name already exists.", "OK");
@@ -111,10 +111,6 @@ public class InputGlyphDatabaseEditor : Editor
var newTable = tablesProp.GetArrayElementAtIndex(newIndex);
var nameProp = newTable.FindPropertyRelative("deviceName");
if (nameProp != null) nameProp.stringValue = trimmed;
- var tmpAssetProp = newTable.FindPropertyRelative("tmpAsset");
- if (tmpAssetProp != null) tmpAssetProp.objectReferenceValue = null;
- var atlasProp = newTable.FindPropertyRelative("spriteAtlas");
- if (atlasProp != null) atlasProp.objectReferenceValue = null;
var sheetProp = newTable.FindPropertyRelative("spriteSheetTexture");
if (sheetProp != null) sheetProp.objectReferenceValue = null;
var entriesProp = newTable.FindPropertyRelative("entries");
@@ -130,6 +126,7 @@ public class InputGlyphDatabaseEditor : Editor
}
}
}
+
if (GUILayout.Button("Cancel", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
showAddField = false;
@@ -157,7 +154,7 @@ public class InputGlyphDatabaseEditor : Editor
if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(22)))
{
if (EditorUtility.DisplayDialog("Delete Table?",
- $"Delete table '{name}' and all its entries? This cannot be undone.", "Delete", "Cancel"))
+ $"Delete table '{name}' and all its entries? This cannot be undone.", "Delete", "Cancel"))
{
tablesProp.DeleteArrayElementAtIndex(i);
serializedObject.ApplyModifiedProperties();
@@ -208,6 +205,11 @@ public class InputGlyphDatabaseEditor : Editor
EnsureEditorListsLength();
+ // compute deviceName & runtime index for this table (used when deleting single entry)
+ var nameProp = tableProp.FindPropertyRelative("deviceName");
+ string deviceName = nameProp != null ? nameProp.stringValue : "";
+ int runtimeTableIndex = MapSerializedTableToRuntimeIndex(deviceName);
+
GUILayout.BeginHorizontal();
GUIStyle searchStyle = EditorStyles.toolbarSearchField ?? EditorStyles.textField;
searchStrings[tabIndex] = GUILayout.TextField(searchStrings[tabIndex] ?? "", searchStyle);
@@ -215,59 +217,6 @@ public class InputGlyphDatabaseEditor : Editor
EditorGUILayout.Space(6);
- // TMP Asset row
- var tmpAssetProp = tableProp.FindPropertyRelative("tmpAsset");
- EditorGUILayout.BeginHorizontal();
- GUILayout.Label("TMP Sprite Asset", GUILayout.Width(140));
- EditorGUILayout.PropertyField(tmpAssetProp, GUIContent.none, GUILayout.ExpandWidth(true));
- if (GUILayout.Button("Parse TMP Asset", GUILayout.Width(120)))
- {
- ParseTMPAssetIntoTableSerialized(tableProp);
- }
- if (GUILayout.Button("Clear", GUILayout.Width(80)))
- {
- var entriesProp = tableProp.FindPropertyRelative("entries");
- if (entriesProp != null) entriesProp.arraySize = 0;
- var nameProp = tableProp.FindPropertyRelative("deviceName");
- if (nameProp != null && db != null)
- {
- var deviceName = nameProp.stringValue;
- var table = db.GetTable(deviceName);
- if (table != null) table.entries.Clear();
- }
- serializedObject.ApplyModifiedProperties();
- EditorUtility.SetDirty(db);
- currentPages[tabIndex] = 0;
- }
- EditorGUILayout.EndHorizontal();
-
- // SpriteAtlas row
- var atlasProp = tableProp.FindPropertyRelative("spriteAtlas");
- EditorGUILayout.BeginHorizontal();
- GUILayout.Label("Sprite Atlas", GUILayout.Width(140));
- EditorGUILayout.PropertyField(atlasProp, GUIContent.none, GUILayout.ExpandWidth(true));
- if (GUILayout.Button("Parse Sprite Atlas", GUILayout.Width(120)))
- {
- ParseSpriteAtlasIntoTableSerialized(tableProp);
- }
- if (GUILayout.Button("Clear", GUILayout.Width(80)))
- {
- var entriesProp = tableProp.FindPropertyRelative("entries");
- if (entriesProp != null) entriesProp.arraySize = 0;
- var nameProp = tableProp.FindPropertyRelative("deviceName");
- if (nameProp != null && db != null)
- {
- var deviceName = nameProp.stringValue;
- var table = db.GetTable(deviceName);
- if (table != null) table.entries.Clear();
- }
- serializedObject.ApplyModifiedProperties();
- EditorUtility.SetDirty(db);
- currentPages[tabIndex] = 0;
- }
- EditorGUILayout.EndHorizontal();
-
- // SpriteSheet (Texture2D with Multiple) row
var sheetProp = tableProp.FindPropertyRelative("spriteSheetTexture");
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Sprite Sheet (Texture2D)", GUILayout.Width(140));
@@ -276,23 +225,74 @@ public class InputGlyphDatabaseEditor : Editor
{
ParseSpriteSheetIntoTableSerialized(tableProp);
}
+
+
if (GUILayout.Button("Clear", GUILayout.Width(80)))
{
var entriesProp = tableProp.FindPropertyRelative("entries");
if (entriesProp != null) entriesProp.arraySize = 0;
- var nameProp = tableProp.FindPropertyRelative("deviceName");
- if (nameProp != null && db != null)
+ if (runtimeTableIndex >= 0 && db != null)
{
- var deviceName = nameProp.stringValue;
- var table = db.GetTable(deviceName);
+ var table = db.tables[runtimeTableIndex];
if (table != null) table.entries.Clear();
}
+
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(db);
currentPages[tabIndex] = 0;
}
+
EditorGUILayout.EndHorizontal();
+ var platformProp = tableProp.FindPropertyRelative("platformIcons");
+ EditorGUILayout.PropertyField(platformProp, new GUIContent("Platforms Icons"));
+ Sprite placeholder = platformProp.objectReferenceValue as Sprite;
+ EditorGUILayout.Space(6);
+ EditorGUILayout.LabelField("Preview", EditorStyles.miniBoldLabel);
+ if (placeholder != null)
+ {
+ Texture2D preview = AssetPreview.GetAssetPreview(placeholder);
+ if (preview == null) preview = AssetPreview.GetMiniThumbnail(placeholder);
+ if (preview != null) GUILayout.Label(preview, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
+ else EditorGUILayout.ObjectField(placeholder, typeof(Sprite), false, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
+ }
+ else
+ {
+ EditorGUILayout.HelpBox("No PlatformIcons.", MessageType.Info);
+ }
+
+ EditorGUILayout.Space(6);
+
+ // ---- 新增:单个新增 Entry 的 UI(只支持 Sprite) ----
+ EditorGUILayout.BeginVertical("box");
+ EditorGUILayout.LabelField("Add Single Entry", EditorStyles.boldLabel);
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Label("Sprite", GUILayout.Width(50));
+ newEntrySprites[tabIndex] = (Sprite)EditorGUILayout.ObjectField(newEntrySprites[tabIndex], typeof(Sprite), false, GUILayout.Width(80), GUILayout.Height(80));
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+ EditorGUILayout.Space(6);
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.FlexibleSpace();
+ if (GUILayout.Button("Add Entry", GUILayout.Width(110)))
+ {
+ if (newEntrySprites[tabIndex] == null)
+ {
+ EditorUtility.DisplayDialog("Missing Sprite", "Please assign a Sprite to add.", "OK");
+ }
+ else
+ {
+ AddEntryToTableSerialized(tableProp, newEntrySprites[tabIndex]);
+ // reset temp field
+ newEntrySprites[tabIndex] = null;
+ currentPages[tabIndex] = 0;
+ }
+ }
+
+ EditorGUILayout.EndHorizontal();
+ EditorGUILayout.EndVertical();
+ // ---- end add-single-entry UI ----
+
EditorGUILayout.Space(6);
var entries = tableProp.FindPropertyRelative("entries");
@@ -319,15 +319,30 @@ public class InputGlyphDatabaseEditor : Editor
currentPages[tabIndex] = Mathf.Clamp(currentPages[tabIndex], 0, totalPages - 1);
EditorGUILayout.BeginHorizontal();
- if (GUILayout.Button("<<", EditorStyles.miniButtonLeft, GUILayout.Width(36))) { currentPages[tabIndex] = 0; }
- if (GUILayout.Button("<", EditorStyles.miniButtonMid, GUILayout.Width(36))) { currentPages[tabIndex] = Mathf.Max(0, currentPages[tabIndex] - 1); }
+ if (GUILayout.Button("<<", EditorStyles.miniButtonLeft, GUILayout.Width(36)))
+ {
+ currentPages[tabIndex] = 0;
+ }
+
+ if (GUILayout.Button("<", EditorStyles.miniButtonMid, GUILayout.Width(36)))
+ {
+ currentPages[tabIndex] = Mathf.Max(0, currentPages[tabIndex] - 1);
+ }
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField(string.Format("Page {0}/{1}", currentPages[tabIndex] + 1, totalPages), GUILayout.Width(120));
GUILayout.FlexibleSpace();
- if (GUILayout.Button(">", EditorStyles.miniButtonMid, GUILayout.Width(36))) { currentPages[tabIndex] = Mathf.Min(totalPages - 1, currentPages[tabIndex] + 1); }
- if (GUILayout.Button(">>", EditorStyles.miniButtonRight, GUILayout.Width(36))) { currentPages[tabIndex] = totalPages - 1; }
+ if (GUILayout.Button(">", EditorStyles.miniButtonMid, GUILayout.Width(36)))
+ {
+ currentPages[tabIndex] = Mathf.Min(totalPages - 1, currentPages[tabIndex] + 1);
+ }
+
+ if (GUILayout.Button(">>", EditorStyles.miniButtonRight, GUILayout.Width(36)))
+ {
+ currentPages[tabIndex] = totalPages - 1;
+ }
+
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(4);
@@ -341,8 +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");
@@ -368,11 +385,51 @@ 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)))
+ {
+ string spriteName = null;
+ var sProp = eProp.FindPropertyRelative("Sprite");
+ if (sProp != null) spriteName = (sProp.objectReferenceValue as Sprite)?.name;
+
+ 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)
+ if (runtimeTableIndex >= 0 && db != null && db.tables != null && runtimeTableIndex < db.tables.Count)
+ {
+ var runtimeTable = db.tables[runtimeTableIndex];
+ if (runtimeTable != null && i >= 0 && i < runtimeTable.entries.Count)
+ {
+ runtimeTable.entries.RemoveAt(i);
+ }
+ }
+
+ EditorUtility.SetDirty(db);
+ AssetDatabase.SaveAssets();
+
+ // reset paging and return to avoid continuing to iterate mutated serialized array
+ currentPages[tabIndex] = 0;
+ return;
+ }
+ }
}
EditorGUILayout.Space(4);
@@ -409,10 +466,6 @@ public class InputGlyphDatabaseEditor : Editor
var newTable = tablesProp.GetArrayElementAtIndex(idx);
var deviceNameProp = newTable.FindPropertyRelative("deviceName");
if (deviceNameProp != null) deviceNameProp.stringValue = name;
- var tmpAssetProp = newTable.FindPropertyRelative("tmpAsset");
- if (tmpAssetProp != null) tmpAssetProp.objectReferenceValue = null;
- var atlasProp = newTable.FindPropertyRelative("spriteAtlas");
- if (atlasProp != null) atlasProp.objectReferenceValue = null;
var sheetProp = newTable.FindPropertyRelative("spriteSheetTexture");
if (sheetProp != null) sheetProp.objectReferenceValue = null;
var entriesProp = newTable.FindPropertyRelative("entries");
@@ -427,12 +480,15 @@ public class InputGlyphDatabaseEditor : Editor
int count = tablesProp != null ? tablesProp.arraySize : 0;
if (searchStrings == null) searchStrings = new List();
if (currentPages == null) currentPages = new List();
+ if (newEntrySprites == null) newEntrySprites = new List();
while (searchStrings.Count < count) searchStrings.Add("");
while (currentPages.Count < count) currentPages.Add(0);
+ while (newEntrySprites.Count < count) newEntrySprites.Add(null);
while (searchStrings.Count > count) searchStrings.RemoveAt(searchStrings.Count - 1);
while (currentPages.Count > count) currentPages.RemoveAt(currentPages.Count - 1);
+ while (newEntrySprites.Count > count) newEntrySprites.RemoveAt(newEntrySprites.Count - 1);
}
void EnsureEditorListsLength()
@@ -441,247 +497,58 @@ public class InputGlyphDatabaseEditor : Editor
SyncEditorListsWithTables();
}
- // ----- Parse TMP SpriteAsset(增强版) -----
- void ParseTMPAssetIntoTableSerialized(SerializedProperty tableProp)
+ // ----- 新增:把单个 Sprite 加入到序列化表和 runtime 表 -----
+ void AddEntryToTableSerialized(SerializedProperty tableProp, Sprite sprite)
{
if (tableProp == null) return;
- var tmpAssetProp = tableProp.FindPropertyRelative("tmpAsset");
- var asset = tmpAssetProp.objectReferenceValue as TMP_SpriteAsset;
- if (asset == null)
+ var entriesProp = tableProp.FindPropertyRelative("entries");
+ if (entriesProp == null) return;
+
+ int insertIndex = entriesProp.arraySize;
+ entriesProp.InsertArrayElementAtIndex(insertIndex);
+ var newE = entriesProp.GetArrayElementAtIndex(insertIndex);
+ if (newE != null)
{
- Debug.LogWarning("[InputGlyphDatabase] TMP Sprite Asset is null for table.");
- return;
- }
+ var spriteProp = newE.FindPropertyRelative("Sprite");
+ var actionProp = newE.FindPropertyRelative("action");
- var nameProp = tableProp.FindPropertyRelative("deviceName");
- string deviceName = nameProp != null ? nameProp.stringValue : "";
+ if (spriteProp != null) spriteProp.objectReferenceValue = sprite;
- int tableIndex = MapSerializedTableToRuntimeIndex(deviceName);
- if (tableIndex < 0)
- {
- Debug.LogError($"[InputGlyphDatabase] Could not map serialized table '{deviceName}' to runtime db.tables.");
- return;
- }
-
- var tableObj = db.tables[tableIndex];
- tableObj.entries.Clear();
-
- var chars = asset.spriteCharacterTable;
- SpriteAtlas atlas = GetSpriteAtlasFromTMP(asset);
- string assetPath = AssetDatabase.GetAssetPath(asset);
- string assetFolder = !string.IsNullOrEmpty(assetPath) ? Path.GetDirectoryName(assetPath) : null;
-
- int foundCount = 0;
- for (int i = 0; i < chars.Count; ++i)
- {
- var ch = chars[i];
- if (ch == null) continue;
- string name = ch.name;
- if (string.IsNullOrEmpty(name)) name = $"glyph_{i}";
-
- Sprite s = null;
-
- // 1) 尝试从 glyph / TMP_SpriteGlyph 中取 sprite
- try
- {
- var glyph = ch.glyph as TMP_SpriteGlyph;
- if (glyph != null)
- {
- var possible = typeof(TMP_SpriteGlyph).GetProperty("sprite", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
- if (possible != null)
- {
- s = possible.GetValue(glyph, null) as Sprite;
- }
- else
- {
- var f = typeof(TMP_SpriteGlyph).GetField("sprite", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
- if (f != null) s = f.GetValue(glyph) as Sprite;
- }
- }
- }
- catch { s = null; }
-
- // 2) atlas 查找
- if (s == null && atlas != null)
- {
- try { s = atlas.GetSprite(name); } catch { s = null; }
- if (s == null)
- {
- try
- {
- var m = typeof(SpriteAtlas).GetMethod("GetSprite", new Type[] { typeof(string) });
- if (m != null) s = m.Invoke(atlas, new object[] { name }) as Sprite;
- }
- catch { s = null; }
- }
- }
-
- // 3) asset folder scope 查找
- if (s == null && !string.IsNullOrEmpty(assetFolder))
+ // leave action serialized as-is (most projects can't serialize InputAction directly here)
+ if (actionProp != null)
{
try
{
- string[] scoped = AssetDatabase.FindAssets($"\"{name}\" t:Sprite", new[] { assetFolder });
- if (scoped != null && scoped.Length > 0)
- {
- foreach (var g in scoped)
- {
- var p = AssetDatabase.GUIDToAssetPath(g);
- var sp = AssetDatabase.LoadAssetAtPath(p);
- if (sp != null && sp.name == name)
- {
- s = sp; break;
- }
- }
- }
- }
- catch { s = null; }
- }
-
- // 4) 全项目查找
- if (s == null)
- {
- try
- {
- string[] all = AssetDatabase.FindAssets($"{name} t:Sprite");
- if (all != null && all.Length > 0)
- {
- foreach (var g in all)
- {
- var p = AssetDatabase.GUIDToAssetPath(g);
- var sp = AssetDatabase.LoadAssetAtPath(p);
- if (sp != null && sp.name == name)
- {
- s = sp; break;
- }
- }
- }
- }
- catch { s = null; }
- }
-
- // 5) LoadAllAssetsAtPath (TMP asset 本身) 作为最后手段
- if (s == null && !string.IsNullOrEmpty(assetPath))
- {
- try
- {
- var allAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
- if (allAssets != null)
- {
- foreach (var obj in allAssets)
- {
- if (obj is Sprite sp && sp.name == name)
- {
- s = sp; break;
- }
- }
- }
- }
- catch { s = null; }
- }
-
- GlyphEntry entry = new GlyphEntry();
- entry.Sprite = s;
- entry.action = null;
- tableObj.entries.Add(entry);
-
- if (s != null) foundCount++;
- else Debug.LogWarning($"[InputGlyphDatabase] Failed to resolve sprite '{name}' for TMP asset '{asset.name}' (table '{deviceName}').");
- }
-
- // 按名字逐字符排序(不区分大小写)
- tableObj.entries.Sort((a, b) => CompareSpriteNames(a?.Sprite?.name, b?.Sprite?.name));
-
- EditorUtility.SetDirty(db);
- serializedObject.Update();
- serializedObject.ApplyModifiedProperties();
- AssetDatabase.SaveAssets();
-
- Debug.Log($"[InputGlyphDatabase] Parsed TMP '{asset.name}' into table '{deviceName}'. chars={chars.Count}, resolvedSprites={foundCount}");
- }
-
- // ----- Parse SpriteAtlas(Unity SpriteAtlas) -----
- void ParseSpriteAtlasIntoTableSerialized(SerializedProperty tableProp)
- {
- if (tableProp == null) return;
- var atlasProp = tableProp.FindPropertyRelative("spriteAtlas");
- var atlas = atlasProp != null ? atlasProp.objectReferenceValue as SpriteAtlas : null;
- if (atlas == null)
- {
- Debug.LogWarning("[InputGlyphDatabase] SpriteAtlas is null for table.");
- return;
- }
-
- var nameProp = tableProp.FindPropertyRelative("deviceName");
- string deviceName = nameProp != null ? nameProp.stringValue : "";
-
- int tableIndex = MapSerializedTableToRuntimeIndex(deviceName);
- if (tableIndex < 0)
- {
- Debug.LogError($"[InputGlyphDatabase] Could not map serialized table '{deviceName}' to runtime db.tables.");
- return;
- }
-
- var tableObj = db.tables[tableIndex];
- tableObj.entries.Clear();
-
- string[] guids = AssetDatabase.FindAssets("t:Sprite");
- int added = 0;
- try
- {
- for (int gi = 0; gi < guids.Length; ++gi)
- {
- var guid = guids[gi];
- var path = AssetDatabase.GUIDToAssetPath(guid);
- var sp = AssetDatabase.LoadAssetAtPath(path);
- if (sp == null) continue;
- bool belongs = false;
- try
- {
- var got = atlas.GetSprite(sp.name);
- if (got != null) belongs = true;
+ actionProp.objectReferenceValue = null;
}
catch
{
- try
- {
- var m = typeof(SpriteAtlas).GetMethod("GetSprite", new Type[] { typeof(string) });
- if (m != null)
- {
- var got2 = m.Invoke(atlas, new object[] { sp.name }) as Sprite;
- if (got2 != null) belongs = true;
- }
- }
- catch { }
- }
-
- if (belongs)
- {
- GlyphEntry e = new GlyphEntry();
- e.Sprite = sp;
- e.action = null;
- tableObj.entries.Add(e);
- added++;
}
}
}
- catch (Exception ex)
+
+ serializedObject.ApplyModifiedProperties();
+
+ // also add to runtime list
+ var nameProp = tableProp.FindPropertyRelative("deviceName");
+ string deviceName = nameProp != null ? nameProp.stringValue : "";
+ int tableIndex = MapSerializedTableToRuntimeIndex(deviceName);
+ if (tableIndex >= 0 && db != null && db.tables != null && tableIndex < db.tables.Count)
{
- Debug.LogError("[InputGlyphDatabase] Exception while scanning sprites for atlas: " + ex);
+ var tableObj = db.tables[tableIndex];
+ GlyphEntry e = new GlyphEntry();
+ e.Sprite = sprite;
+ e.action = null; // runtime only: none provided here
+ tableObj.entries.Add(e);
}
- // 按名字逐字符排序(不区分大小写)
- tableObj.entries.Sort((a, b) => CompareSpriteNames(a?.Sprite?.name, b?.Sprite?.name));
-
EditorUtility.SetDirty(db);
- serializedObject.Update();
- serializedObject.ApplyModifiedProperties();
AssetDatabase.SaveAssets();
-
- Debug.Log($"[InputGlyphDatabase] Parsed SpriteAtlas '{atlas.name}' into table '{deviceName}'. foundSprites={added}");
}
- // ----- Parse Sprite Sheet (Texture2D with Multiple) -----
+
+// ----- Parse Sprite Sheet (Texture2D with Multiple) -----
+// 只用名称匹配覆盖 Sprite,不改变已有 action
void ParseSpriteSheetIntoTableSerialized(SerializedProperty tableProp)
{
if (tableProp == null) return;
@@ -705,7 +572,11 @@ public class InputGlyphDatabaseEditor : Editor
}
var tableObj = db.tables[tableIndex];
- tableObj.entries.Clear();
+ if (tableObj == null)
+ {
+ Debug.LogError($"[InputGlyphDatabase] Runtime table object is null for '{deviceName}'.");
+ return;
+ }
string path = AssetDatabase.GetAssetPath(tex);
if (string.IsNullOrEmpty(path))
@@ -721,35 +592,107 @@ public class InputGlyphDatabaseEditor : Editor
return;
}
+ // 收集 sprites(按照文件内顺序;你如果想按名字排序可以在这里加)
List sprites = new List();
foreach (var a in assets)
{
- if (a is Sprite sp)
+ if (a is Sprite sp) sprites.Add(sp);
+ }
+
+ var entriesProp = tableProp.FindPropertyRelative("entries");
+ if (entriesProp == null)
+ {
+ Debug.LogWarning("[InputGlyphDatabase] entries property not found on table.");
+ return;
+ }
+
+ // 构建序列化表名 -> 索引 映射(忽略大小写)
+ var serializedNameToIndex = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ for (int i = 0; i < entriesProp.arraySize; ++i)
+ {
+ var eProp = entriesProp.GetArrayElementAtIndex(i);
+ if (eProp == null) continue;
+ var sProp = eProp.FindPropertyRelative("Sprite");
+ var sRef = sProp != null ? sProp.objectReferenceValue as Sprite : null;
+ if (sRef != null && !serializedNameToIndex.ContainsKey(sRef.name))
{
- sprites.Add(sp);
+ serializedNameToIndex[sRef.name] = i;
}
}
- // 之前按视觉位置排序,改为先按名字逐字符排序(不区分大小写)
- sprites.Sort((a, b) => CompareSpriteNames(a?.name, b?.name));
+ // runtime 名称 -> 索引 映射
+ var runtimeNameToIndex = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ for (int i = 0; i < tableObj.entries.Count; ++i)
+ {
+ var re = tableObj.entries[i];
+ if (re != null && re.Sprite != null)
+ {
+ var rn = re.Sprite.name;
+ if (!runtimeNameToIndex.ContainsKey(rn))
+ runtimeNameToIndex[rn] = i;
+ }
+ }
+
+ int replaced = 0, added = 0;
foreach (var sp in sprites)
{
- GlyphEntry e = new GlyphEntry();
- e.Sprite = sp;
- e.action = null;
- tableObj.entries.Add(e);
+ if (sp == null) continue;
+ string nm = sp.name;
+
+ // -- 序列化层:同名则替换 Sprite 引用(不触碰 action),否则新增元素并把 action 设为 null --
+ if (serializedNameToIndex.TryGetValue(nm, out int sIndex))
+ {
+ var eProp = entriesProp.GetArrayElementAtIndex(sIndex);
+ if (eProp != null)
+ {
+ var spriteProp = eProp.FindPropertyRelative("Sprite");
+ if (spriteProp != null) spriteProp.objectReferenceValue = sp;
+ // 不修改 actionProp,保持原有 action(如果有的话)
+ }
+
+ replaced++;
+ }
+ else
+ {
+ int insertIndex = entriesProp.arraySize;
+ entriesProp.InsertArrayElementAtIndex(insertIndex);
+ var newE = entriesProp.GetArrayElementAtIndex(insertIndex);
+ if (newE != null)
+ {
+ var spriteProp = newE.FindPropertyRelative("Sprite");
+ var actionProp = newE.FindPropertyRelative("action");
+ if (spriteProp != null) spriteProp.objectReferenceValue = sp;
+ if (actionProp != null) actionProp.objectReferenceValue = null; // 新增项 action 为空
+ }
+
+ serializedNameToIndex[nm] = insertIndex;
+ added++;
+ }
+
+ // -- 运行时层:同名则替换 Sprite,否则新增 runtime entry(action 设 null,保持之前 runtime entry 的 action 不变) --
+ if (runtimeNameToIndex.TryGetValue(nm, out int rIndex))
+ {
+ var runtimeEntry = tableObj.entries[rIndex];
+ if (runtimeEntry != null) runtimeEntry.Sprite = sp;
+ }
+ else
+ {
+ GlyphEntry ge = new GlyphEntry();
+ ge.Sprite = sp;
+ ge.action = null;
+ tableObj.entries.Add(ge);
+ runtimeNameToIndex[nm] = tableObj.entries.Count - 1;
+ }
}
- // 额外在 runtime list 里也用相同排序(上面已经排序过 sprites)
- tableObj.entries.Sort((a, b) => CompareSpriteNames(a?.Sprite?.name, b?.Sprite?.name));
-
+ // 应用并保存修改(序列化层与 runtime 层保持同步)
EditorUtility.SetDirty(db);
serializedObject.Update();
serializedObject.ApplyModifiedProperties();
AssetDatabase.SaveAssets();
- Debug.Log($"[InputGlyphDatabase] Parsed sprite sheet '{tex.name}' into table '{deviceName}'. foundSprites={sprites.Count}");
+ Debug.Log($"[InputGlyphDatabase] Merged sprite sheet '{tex.name}' into table '{deviceName}'. spritesFound={sprites.Count}, replaced={replaced}, added={added}, totalEntries={tableObj.entries.Count}");
}
int MapSerializedTableToRuntimeIndex(string deviceName)
@@ -760,56 +703,7 @@ public class InputGlyphDatabaseEditor : Editor
if (string.Equals(db.tables[ti].deviceName, deviceName, StringComparison.OrdinalIgnoreCase))
return ti;
}
+
return -1;
}
-
- SpriteAtlas GetSpriteAtlasFromTMP(TMP_SpriteAsset asset)
- {
- if (asset == null) return null;
- var t = asset.GetType();
- foreach (var f in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
- {
- if (typeof(SpriteAtlas).IsAssignableFrom(f.FieldType))
- {
- var val = f.GetValue(asset) as SpriteAtlas;
- if (val != null) return val;
- }
- }
-
- foreach (var p in t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
- {
- if (typeof(SpriteAtlas).IsAssignableFrom(p.PropertyType))
- {
- try
- {
- var val = p.GetValue(asset, null) as SpriteAtlas;
- if (val != null) return val;
- }
- catch { }
- }
- }
-
- return null;
- }
-
- int CompareSpriteNames(string a, string b)
- {
- // normalize null/empty
- bool aEmpty = string.IsNullOrEmpty(a);
- bool bEmpty = string.IsNullOrEmpty(b);
- if (aEmpty && bEmpty) return 0;
- if (aEmpty) return -1;
- if (bEmpty) return 1;
-
- int la = a.Length;
- int lb = b.Length;
- int n = Math.Min(la, lb);
- for (int i = 0; i < n; ++i)
- {
- char ca = char.ToUpperInvariant(a[i]);
- char cb = char.ToUpperInvariant(b[i]);
- if (ca != cb) return ca - cb;
- }
- return la - lb;
- }
}
diff --git a/Client/Assets/InputGlyph/InputGlyphImage.cs b/Client/Assets/InputGlyph/InputGlyphImage.cs
index fbf38da..d237fe6 100644
--- a/Client/Assets/InputGlyph/InputGlyphImage.cs
+++ b/Client/Assets/InputGlyph/InputGlyphImage.cs
@@ -1,20 +1,22 @@
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.InputSystem;
-using AlicizaX.InputGlyph;
-[RequireComponent(typeof(Image))]
-public class InputGlyphImage : MonoBehaviour
+public sealed class InputGlyphImage : MonoBehaviour
{
[SerializeField] private InputActionReference actionReference;
[SerializeField] private Image targetImage;
[SerializeField] private bool hideIfMissing = false;
[SerializeField] private GameObject hideTargetObject;
+ private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
+ private Sprite _cachedSprite;
+
void OnEnable()
{
if (targetImage == null) targetImage = GetComponent();
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
+ _cachedCategory = InputDeviceWatcher.InputDeviceCategory.Keyboard;
UpdatePrompt();
}
@@ -25,18 +27,34 @@ public class InputGlyphImage : MonoBehaviour
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
{
- UpdatePrompt();
+ if (_cachedCategory != cat)
+ {
+ _cachedCategory = cat;
+ UpdatePrompt();
+ }
}
void UpdatePrompt()
{
if (actionReference == null || actionReference.action == null || targetImage == null) return;
- InputDeviceWatcher.InputDeviceCategory deviceCategory = InputDeviceWatcher.CurrentCategory;
- if (GlyphService.TryGetUISpriteForActionPath(actionReference, deviceCategory, out Sprite sprite))
+
+ // Use cached category instead of re-querying CurrentCategory
+ if (GlyphService.TryGetUISpriteForActionPath(actionReference, "", _cachedCategory, out Sprite sprite))
{
- targetImage.sprite = sprite;
+ if (_cachedSprite != sprite)
+ {
+ _cachedSprite = sprite;
+ targetImage.sprite = sprite;
+ }
}
- if (hideTargetObject) hideTargetObject.SetActive(sprite != null && !hideIfMissing);
+ if (hideTargetObject != null)
+ {
+ bool shouldBeActive = sprite != null && !hideIfMissing;
+ if (hideTargetObject.activeSelf != shouldBeActive)
+ {
+ hideTargetObject.SetActive(shouldBeActive);
+ }
+ }
}
}
diff --git a/Client/Assets/InputGlyph/InputGlyphText.cs b/Client/Assets/InputGlyph/InputGlyphText.cs
index 672192d..078088f 100644
--- a/Client/Assets/InputGlyph/InputGlyphText.cs
+++ b/Client/Assets/InputGlyph/InputGlyphText.cs
@@ -1,22 +1,25 @@
using System;
using System.Linq;
using AlicizaX;
-using AlicizaX.InputGlyph;
using UnityEngine;
using TMPro;
using UnityEngine.InputSystem;
[RequireComponent(typeof(TextMeshProUGUI))]
-public class InputGlyphText : MonoBehaviour
+public sealed class InputGlyphText : MonoBehaviour
{
[SerializeField] private InputActionReference actionReference;
private TMP_Text textField;
private string _oldText;
+ private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
+ private string _cachedFormattedText;
+
void OnEnable()
{
if (textField == null) textField = GetComponent();
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
- _oldText=textField.text;
+ _oldText = textField.text;
+ _cachedCategory = InputDeviceWatcher.InputDeviceCategory.Keyboard;
UpdatePrompt();
}
@@ -25,22 +28,36 @@ public class InputGlyphText : MonoBehaviour
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
}
-
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
{
- UpdatePrompt();
+ if (_cachedCategory != cat)
+ {
+ _cachedCategory = cat;
+ UpdatePrompt();
+ }
}
void UpdatePrompt()
{
if (actionReference == null || actionReference.action == null || textField == null) return;
- var device = InputDeviceWatcher.CurrentCategory;
- if (GlyphService.TryGetTMPTagForActionPath(actionReference, device, out string tag, out string displayFallback))
+
+ // Use cached category instead of re-querying CurrentCategory
+ if (GlyphService.TryGetTMPTagForActionPath(actionReference, "", _cachedCategory, out string tag, out string displayFallback))
{
- textField.text = Utility.Text.Format(_oldText, tag);
+ string formattedText = Utility.Text.Format(_oldText, tag);
+ if (_cachedFormattedText != formattedText)
+ {
+ _cachedFormattedText = formattedText;
+ textField.text = formattedText;
+ }
return;
}
- textField.text = Utility.Text.Format(_oldText, displayFallback);;
+ string fallbackText = Utility.Text.Format(_oldText, displayFallback);
+ if (_cachedFormattedText != fallbackText)
+ {
+ _cachedFormattedText = fallbackText;
+ textField.text = fallbackText;
+ }
}
}
diff --git a/Client/Assets/InputGlyph/InputGlyphUXButton.cs b/Client/Assets/InputGlyph/InputGlyphUXButton.cs
new file mode 100644
index 0000000..9aa1b9c
--- /dev/null
+++ b/Client/Assets/InputGlyph/InputGlyphUXButton.cs
@@ -0,0 +1,63 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.InputSystem;
+
+[RequireComponent(typeof(UXButton))]
+public sealed class InputGlyphUXButton : MonoBehaviour
+{
+ [SerializeField] private UXButton button;
+ [SerializeField] private Image targetImage;
+ private InputActionReference _actionReference;
+ private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
+ private Sprite _cachedSprite;
+
+#if UNITY_EDITOR
+ private void OnValidate()
+ {
+ if (button == null)
+ {
+ button = GetComponent();
+ }
+ }
+#endif
+
+ void OnEnable()
+ {
+ if (button == null) button = GetComponent();
+ if (targetImage == null) targetImage = GetComponent();
+ _actionReference = button.HotKeyRefrence;
+ InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
+ _cachedCategory = InputDeviceWatcher.InputDeviceCategory.Keyboard;
+ UpdatePrompt();
+ }
+
+ void OnDisable()
+ {
+ InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
+ }
+
+ void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
+ {
+ if (_cachedCategory != cat)
+ {
+ _cachedCategory = cat;
+ UpdatePrompt();
+ }
+ }
+
+ void UpdatePrompt()
+ {
+ if (_actionReference == null || _actionReference.action == null || targetImage == null) return;
+
+ // Use cached category instead of re-querying CurrentCategory
+ if (GlyphService.TryGetUISpriteForActionPath(_actionReference, "", _cachedCategory, out Sprite sprite))
+ {
+ if (_cachedSprite != sprite)
+ {
+ _cachedSprite = sprite;
+ targetImage.sprite = sprite;
+ }
+ }
+ }
+}
diff --git a/Client/Assets/InputGlyph/InputGlyphUXButton.cs.meta b/Client/Assets/InputGlyph/InputGlyphUXButton.cs.meta
new file mode 100644
index 0000000..9b528e2
--- /dev/null
+++ b/Client/Assets/InputGlyph/InputGlyphUXButton.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f24ff96430d042109462f373aed1f2cc
+timeCreated: 1765870646
\ No newline at end of file
diff --git a/Client/Assets/InputGlyph/TestRebindScript.cs b/Client/Assets/InputGlyph/TestRebindScript.cs
index 0c24f91..f6b059d 100644
--- a/Client/Assets/InputGlyph/TestRebindScript.cs
+++ b/Client/Assets/InputGlyph/TestRebindScript.cs
@@ -1,16 +1,10 @@
-// TestRebindScript.cs
-
using System;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
-using InputRemapper;
using UnityEngine.UI;
-///
-/// 测试用不需要繁琐处理
-///
public class TestRebindScript : MonoBehaviour
{
[Header("UI")] public UXButton btn;
@@ -29,6 +23,7 @@ public class TestRebindScript : MonoBehaviour
private IDisposable prepareSub;
private IDisposable applySub;
private IDisposable rebindEndSub;
+ private IDisposable conflictSub;
private void Start()
{
@@ -38,6 +33,7 @@ public class TestRebindScript : MonoBehaviour
if (InputBindingManager.Instance != null)
{
+ // Subscribe to prepare events - already filtered by IsTargetContext
prepareSub = InputBindingManager.Instance.OnRebindPrepare.Subscribe(ctx =>
{
if (IsTargetContext(ctx))
@@ -48,8 +44,44 @@ public class TestRebindScript : MonoBehaviour
}
});
- applySub = InputBindingManager.Instance.OnApply.Subscribe(_ => UpdateBindingText());
- rebindEndSub = InputBindingManager.Instance.OnRebindEnd.Subscribe(_ => UpdateBindingText());
+ // Subscribe to apply events - only update if this instance's binding was applied or discarded
+ applySub = InputBindingManager.Instance.OnApply.Subscribe(evt =>
+ {
+ var (success, appliedContexts) = evt;
+ if (appliedContexts != null)
+ {
+ // Only update if any of the applied/discarded contexts match this instance
+ foreach (var ctx in appliedContexts)
+ {
+ if (IsTargetContext(ctx))
+ {
+ UpdateBindingText();
+ break;
+ }
+ }
+ }
+ });
+
+ // Subscribe to rebind end events - only update if this instance's binding ended
+ rebindEndSub = InputBindingManager.Instance.OnRebindEnd.Subscribe(evt =>
+ {
+ var (success, context) = evt;
+ if (IsTargetContext(context))
+ {
+ UpdateBindingText();
+ }
+ });
+
+ // Subscribe to conflict events - update if this instance is involved in conflict
+ conflictSub = InputBindingManager.Instance.OnRebindConflict.Subscribe(evt =>
+ {
+ var (prepared, conflict) = evt;
+ // Update if either the prepared or conflict context matches this instance
+ if (IsTargetContext(prepared) || IsTargetContext(conflict))
+ {
+ UpdateBindingText();
+ }
+ });
}
}
@@ -60,6 +92,7 @@ public class TestRebindScript : MonoBehaviour
prepareSub?.Dispose();
applySub?.Dispose();
rebindEndSub?.Dispose();
+ conflictSub?.Dispose();
}
private void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory _)
@@ -73,12 +106,30 @@ public class TestRebindScript : MonoBehaviour
}
- private bool IsTargetContext(InputRemapper.InputBindingManager.RebindContext ctx)
+ private bool IsTargetContext(InputBindingManager.RebindContext ctx)
{
if (ctx == null || ctx.action == null) return false;
var action = GetAction();
if (action == null) return false;
- return ctx.action == action;
+
+ // 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()
@@ -98,7 +149,6 @@ public class TestRebindScript : MonoBehaviour
try
{
var task = InputBindingManager.ConfirmApply();
- if (task == null) return false;
return await task;
}
catch (Exception ex)
@@ -111,7 +161,7 @@ public class TestRebindScript : MonoBehaviour
public void CancelPrepared()
{
InputBindingManager.DiscardPrepared();
- UpdateBindingText();
+ // UpdateBindingText will be called automatically via OnApply event
}
private void UpdateBindingText()
@@ -125,15 +175,15 @@ public class TestRebindScript : MonoBehaviour
}
- string disp = GlyphService.GetBindingControlPath(action, InputDeviceWatcher.CurrentCategory);
- bindKeyText.text = GlyphService.GetDisplayNameFromInputAction(action);
+ bindKeyText.text = GlyphService.GetDisplayNameFromInputAction(action, compositePartName);
try
{
var deviceCat = InputDeviceWatcher.CurrentCategory;
- string controlPath = GlyphService.GetBindingControlPath(action, deviceCat);
- if (!string.IsNullOrEmpty(controlPath) && GlyphService.TryGetUISpriteForActionPath(controlPath, deviceCat, out Sprite sprite))
+ InputActionReference refr=default;
+ // string controlPath = GlyphService.GetBindingControlPath(action, compositePartName, deviceCat);
+ if ( GlyphService.TryGetUISpriteForActionPath(action,compositePartName, deviceCat, out Sprite sprite))
{
if (targetImage != null) targetImage.sprite = sprite;
}
diff --git a/Client/Assets/Test/GameLogic.dll.bytes b/Client/Assets/Test/GameLogic.dll.bytes
index c640215a095f40e19fdaa95cb833f4db110eab62..edef16180b90d5ff2ded70ce93055a53fe32fda1 100644
GIT binary patch
delta 106
zcmZp8z})bFc|r$^xrNA)jXgJR37qj1I(?BR!GG0~*PB+Tb!2WXx!u9evdfV3{G`VL
z0#^iQt0g#Gb)6RL{ZzatC17&G<7fq_;88KCAV~4^W&h?+N`F{(bj{nO
delta 106
zcmZp8z})bFc|r%vCTJguNgm*TF?NOOX-d&a$H{f?EEhgKkvy2kE0c!f*WCiK*dZ9$Am?!r$?74x~?tWeEYE~4*;N+
BG4=oe
diff --git a/Client/Assets/Test/GameLogic.pdb.bytes b/Client/Assets/Test/GameLogic.pdb.bytes
index 0aa63f431b84330600c89e30601d65d4cb3d9abc..40b60148997b40e73aab363b601eb5f38fe7fede 100644
GIT binary patch
delta 92
zcmV-i0Hgo#!~yWc0g!wY)DfE|U_jMHmR(Eb7jk4!x-kjQv5-Jk7!VOt%m5$*0DtC4
yd>YQ`OORXZP1gFZN0aDQAQ)eA;;;Zf0RRbv+-1zekSn%
delta 92
zcmV-i0Hgo#!~yWc0g!wY@l0l9i(GO+r+wzn`V>w}uou^Yv5-Jk7)`ST!T=xx03a71
y80|~(N~}gY!ejD-^ONXSAQ(+1&7=T80RVN1)cU6N6hfcr%^_9kWpJ|~S5OP0Fex|y
diff --git a/Client/Assets/TestAudioPlay.cs b/Client/Assets/TestAudioPlay.cs
index 7f4618b..8a98ba1 100644
--- a/Client/Assets/TestAudioPlay.cs
+++ b/Client/Assets/TestAudioPlay.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using AlicizaX.InputGlyph;
-using AlicizaX.UI;
-using AlicizaX.UI.Extension;
+
using UnityEngine;
using UnityEngine.UI;
diff --git a/Client/Packages/com.alicizax.unity.entry b/Client/Packages/com.alicizax.unity.entry
index 91cd9d2..90291aa 160000
--- a/Client/Packages/com.alicizax.unity.entry
+++ b/Client/Packages/com.alicizax.unity.entry
@@ -1 +1 @@
-Subproject commit 91cd9d2981e046ce0b03e3bbbd6c6a0294cd1150
+Subproject commit 90291aa4a969a022e7a02d855748d55af03828b0
diff --git a/Client/Packages/com.alicizax.unity.framework b/Client/Packages/com.alicizax.unity.framework
index 0a5ef91..7431888 160000
--- a/Client/Packages/com.alicizax.unity.framework
+++ b/Client/Packages/com.alicizax.unity.framework
@@ -1 +1 @@
-Subproject commit 0a5ef9135ccb240227cf655e2251a63bf2d5da7a
+Subproject commit 74318889a2a1f3d93d1dbd7e35b610fce575043e
diff --git a/Client/Packages/com.alicizax.unity.ui.extension b/Client/Packages/com.alicizax.unity.ui.extension
index f92e919..b3f3f26 160000
--- a/Client/Packages/com.alicizax.unity.ui.extension
+++ b/Client/Packages/com.alicizax.unity.ui.extension
@@ -1 +1 @@
-Subproject commit f92e91920dc4c31b41e36e3fa40058d4e6aace34
+Subproject commit b3f3f268bf27686916f963b219ffda4e8392b914
diff --git a/Client/UserSettings/EditorUserSettings.asset b/Client/UserSettings/EditorUserSettings.asset
index 51d7654..fc153a9 100644
--- a/Client/UserSettings/EditorUserSettings.asset
+++ b/Client/UserSettings/EditorUserSettings.asset
@@ -30,14 +30,14 @@ EditorUserSettings:
value: 56060350000d5b5a5908597a48255a44174e4d797a7d7e6475794f61e7b3643e
flags: 0
RecentlyUsedSceneGuid-7:
- value: 50500404540c580d0f0b5e7543725b44424f4c7a7b7c7734747e4f36e4b1676d
- flags: 0
- RecentlyUsedSceneGuid-8:
value: 015450045700505d0f0a5f2313260a444e164b2e757b76652c2d4d32bab0313a
flags: 0
- RecentlyUsedSceneGuid-9:
+ RecentlyUsedSceneGuid-8:
value: 5a07065703500c59585e0e7748770d44444f4a737d2d7f35787d4f63e0b26668
flags: 0
+ RecentlyUsedSceneGuid-9:
+ value: 50500404540c580d0f0b5e7543725b44424f4c7a7b7c7734747e4f36e4b1676d
+ flags: 0
vcSharedLogLevel:
value: 0d5e400f0650
flags: 0
diff --git a/Client/UserSettings/Layouts/default-2022.dwlt b/Client/UserSettings/Layouts/default-2022.dwlt
index 5bd092c..9f4637c 100644
--- a/Client/UserSettings/Layouts/default-2022.dwlt
+++ b/Client/UserSettings/Layouts/default-2022.dwlt
@@ -19,7 +19,7 @@ MonoBehaviour:
width: 1920
height: 997
m_ShowMode: 4
- m_Title: Project
+ m_Title: Console
m_RootView: {fileID: 4}
m_MinSize: {x: 875, y: 300}
m_MaxSize: {x: 10000, y: 10000}
@@ -41,7 +41,7 @@ MonoBehaviour:
serializedVersion: 2
x: 0
y: 696
- width: 421
+ width: 266
height: 251
m_MinSize: {x: 51, y: 71}
m_MaxSize: {x: 4001, y: 4021}
@@ -70,7 +70,7 @@ MonoBehaviour:
serializedVersion: 2
x: 0
y: 0
- width: 421
+ width: 266
height: 947
m_MinSize: {x: 100, y: 100}
m_MaxSize: {x: 8096, y: 16192}
@@ -174,7 +174,7 @@ MonoBehaviour:
m_MinSize: {x: 400, y: 100}
m_MaxSize: {x: 32384, y: 16192}
vertical: 0
- controlID: 69
+ controlID: 24
draggingID: 0
--- !u!114 &8
MonoBehaviour:
@@ -193,7 +193,7 @@ MonoBehaviour:
serializedVersion: 2
x: 0
y: 0
- width: 421
+ width: 266
height: 696
m_MinSize: {x: 201, y: 221}
m_MaxSize: {x: 4001, y: 4021}
@@ -219,9 +219,9 @@ MonoBehaviour:
- {fileID: 11}
m_Position:
serializedVersion: 2
- x: 421
+ x: 266
y: 0
- width: 284
+ width: 388
height: 947
m_MinSize: {x: 100, y: 100}
m_MaxSize: {x: 8096, y: 16192}
@@ -245,8 +245,8 @@ MonoBehaviour:
serializedVersion: 2
x: 0
y: 0
- width: 284
- height: 249
+ width: 388
+ height: 409
m_MinSize: {x: 202, y: 221}
m_MaxSize: {x: 4002, y: 4021}
m_ActualView: {fileID: 17}
@@ -270,9 +270,9 @@ MonoBehaviour:
m_Position:
serializedVersion: 2
x: 0
- y: 249
- width: 284
- height: 698
+ y: 409
+ width: 388
+ height: 538
m_MinSize: {x: 102, y: 121}
m_MaxSize: {x: 4002, y: 4021}
m_ActualView: {fileID: 18}
@@ -295,9 +295,9 @@ MonoBehaviour:
m_Children: []
m_Position:
serializedVersion: 2
- x: 705
+ x: 654
y: 0
- width: 431
+ width: 479
height: 947
m_MinSize: {x: 232, y: 271}
m_MaxSize: {x: 10002, y: 10021}
@@ -321,9 +321,9 @@ MonoBehaviour:
m_Children: []
m_Position:
serializedVersion: 2
- x: 1136
+ x: 1133
y: 0
- width: 784
+ width: 787
height: 947
m_MinSize: {x: 276, y: 71}
m_MaxSize: {x: 4001, y: 4021}
@@ -354,7 +354,7 @@ MonoBehaviour:
serializedVersion: 2
x: 0
y: 769
- width: 420
+ width: 265
height: 230
m_SerializedDataModeController:
m_DataMode: 0
@@ -372,7 +372,7 @@ MonoBehaviour:
m_ShowGizmos: 0
m_TargetDisplay: 0
m_ClearColor: {r: 0, g: 0, b: 0, a: 0}
- m_TargetSize: {x: 420, y: 209}
+ m_TargetSize: {x: 265, y: 209}
m_TextureFilterMode: 0
m_TextureHideFlags: 61
m_RenderIMGUI: 1
@@ -387,8 +387,8 @@ MonoBehaviour:
m_VRangeLocked: 0
hZoomLockedByDefault: 0
vZoomLockedByDefault: 0
- m_HBaseRangeMin: -210
- m_HBaseRangeMax: 210
+ m_HBaseRangeMin: -132.5
+ m_HBaseRangeMax: 132.5
m_VBaseRangeMin: -104.5
m_VBaseRangeMax: 104.5
m_HAllowExceedBaseRangeMin: 1
@@ -408,23 +408,23 @@ MonoBehaviour:
serializedVersion: 2
x: 0
y: 21
- width: 420
+ width: 265
height: 209
m_Scale: {x: 1, y: 1}
- m_Translation: {x: 210, y: 104.5}
+ m_Translation: {x: 132.5, y: 104.5}
m_MarginLeft: 0
m_MarginRight: 0
m_MarginTop: 0
m_MarginBottom: 0
m_LastShownAreaInsideMargins:
serializedVersion: 2
- x: -210
+ x: -132.5
y: -104.5
- width: 420
+ width: 265
height: 209
m_MinimalGUI: 1
m_defaultScale: 1
- m_LastWindowPixelSize: {x: 420, y: 230}
+ m_LastWindowPixelSize: {x: 265, y: 230}
m_ClearInEditMode: 1
m_NoCameraWarning: 1
m_LowResolutionForAspectRatios: 01000000000000000000
@@ -522,7 +522,7 @@ MonoBehaviour:
serializedVersion: 2
x: 0
y: 73
- width: 420
+ width: 265
height: 675
m_SerializedDataModeController:
m_DataMode: 0
@@ -1163,10 +1163,10 @@ MonoBehaviour:
m_Tooltip:
m_Pos:
serializedVersion: 2
- x: 421
+ x: 266
y: 73
- width: 282
- height: 228
+ width: 386
+ height: 388
m_SerializedDataModeController:
m_DataMode: 0
m_PreferredDataMode: 0
@@ -1180,9 +1180,9 @@ MonoBehaviour:
m_SceneHierarchy:
m_TreeViewState:
scrollPos: {x: 0, y: 0}
- m_SelectedIDs: 626f0000
+ m_SelectedIDs: 2e1c0000
m_LastClickedID: 0
- m_ExpandedIDs: eefafffff6fafffff8faffffd66c0000
+ m_ExpandedIDs: 28fbffff
m_RenameOverlay:
m_UserAcceptedRename: 0
m_Name:
@@ -1226,10 +1226,10 @@ MonoBehaviour:
m_Tooltip:
m_Pos:
serializedVersion: 2
- x: 421
- y: 322
- width: 282
- height: 677
+ x: 266
+ y: 482
+ width: 386
+ height: 517
m_SerializedDataModeController:
m_DataMode: 0
m_PreferredDataMode: 0
@@ -1260,9 +1260,9 @@ MonoBehaviour:
m_Tooltip:
m_Pos:
serializedVersion: 2
- x: 705
+ x: 654
y: 73
- width: 429
+ width: 477
height: 926
m_SerializedDataModeController:
m_DataMode: 0
@@ -1285,7 +1285,7 @@ MonoBehaviour:
m_SkipHidden: 0
m_SearchArea: 2
m_Folders:
- - Packages/com.alicizax.unity.ui.extension/Editor/Res/ComponentIcon
+ - Assets/Plugins/PrimeTween/Demo/Scripts/MeasureAllocations
m_Globs: []
m_OriginalText:
m_ImportLogFlags: 0
@@ -1301,7 +1301,7 @@ MonoBehaviour:
scrollPos: {x: 0, y: 0}
m_SelectedIDs: e48c0000
m_LastClickedID: 36068
- m_ExpandedIDs: 00000000226f0000246f0000266f0000286f00002a6f00002c6f00002e6f0000306f0000326f0000346f0000366f0000386f00003a6f00003c6f00003e6f0000406f0000426f0000446f0000466f0000486f00004a6f00004c6f00004e6f0000506f0000526f0000546f0000566f0000586f00005a6f00005c6f00005e6f0000606f0000626f0000646f0000666f0000686f00006a6f0000
+ m_ExpandedIDs: 00000000860d0000746d0000766d0000786d00007a6d00007c6d00007e6d0000806d0000826d0000846d0000866d0000886d00008a6d00008c6d00008e6d0000906d0000926d0000946d0000966d0000986d00009a6d00009c6d00009e6d0000a06d0000a26d0000a46d0000a66d0000a86d0000aa6d0000ac6d0000ae6d0000b06d0000b26d0000b46d0000b66d0000b86d0000ba6d0000bc6d0000
m_RenameOverlay:
m_UserAcceptedRename: 0
m_Name:
@@ -1326,10 +1326,10 @@ MonoBehaviour:
m_Icon: {fileID: 0}
m_ResourceFile:
m_AssetTreeState:
- scrollPos: {x: 0, y: 720}
+ scrollPos: {x: 0, y: 528}
m_SelectedIDs:
m_LastClickedID: 0
- m_ExpandedIDs: ffffffff00000000226f0000246f0000266f0000286f00002a6f00002e6f0000306f0000326f0000346f0000366f0000386f00003a6f00003c6f00003e6f0000406f0000426f0000446f0000466f0000486f00004a6f00004e6f0000506f0000526f0000546f0000566f0000586f00005a6f00005c6f00005e6f0000606f0000626f0000646f0000666f0000686f00006a6f0000ffffff7f
+ m_ExpandedIDs: ffffffff00000000860d0000746d0000766d0000786d00007a6d00007c6d00007e6d0000806d0000826d0000846d0000866d0000886d00008a6d00008c6d00008e6d0000906d0000926d0000946d0000966d0000986d00009a6d00009c6d00009e6d0000a06d0000a26d0000a46d0000a66d0000a86d0000aa6d0000ac6d0000ae6d0000b06d0000b26d0000b46d0000b66d0000b86d0000ba6d0000bc6d0000c46f0000f47000001c710000
m_RenameOverlay:
m_UserAcceptedRename: 0
m_Name:
@@ -1405,9 +1405,9 @@ MonoBehaviour:
m_Tooltip:
m_Pos:
serializedVersion: 2
- x: 1136
+ x: 1133
y: 73
- width: 783
+ width: 786
height: 926
m_SerializedDataModeController:
m_DataMode: 0
@@ -1426,7 +1426,7 @@ MonoBehaviour:
m_ControlHash: 1412526313
m_PrefName: Preview_InspectorPreview
m_LastInspectedObjectInstanceID: -1
- m_LastVerticalScrollValue: 432
+ m_LastVerticalScrollValue: 0
m_GlobalObjectId:
m_InspectorMode: 0
m_LockTracker: