1
This commit is contained in:
parent
8547e57f55
commit
6f93b0fc15
@ -1,15 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using AlicizaX.InputGlyph;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
|
|
||||||
public static class GlyphService
|
public static class GlyphService
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Cached device hint arrays to avoid allocations
|
||||||
/// 可选的全局数据库引用。你可以通过场景内的启动组件在 Awake 时赋值,
|
private static readonly string[] KeyboardHints = { "Keyboard", "Mouse" };
|
||||||
/// 或者在调用每个方法时传入 InputGlyphDatabase 参数(见方法签名)。
|
private static readonly string[] XboxHints = { "XInput", "Xbox", "Gamepad" };
|
||||||
/// </summary>
|
private static readonly string[] PlayStationHints = { "DualShock", "DualSense", "PlayStation", "Gamepad" };
|
||||||
|
private static readonly char[] TrimChars = { '{', '}', '<', '>', '\'', '"' };
|
||||||
|
|
||||||
public static InputGlyphDatabase Database
|
public static InputGlyphDatabase Database
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -26,22 +26,22 @@ public static class GlyphService
|
|||||||
private static InputGlyphDatabase _database;
|
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;
|
if (action == null) return string.Empty;
|
||||||
var binding = GetBindingControl(action, deviceOverride);
|
var binding = GetBindingControl(action, compositePartName, deviceOverride);
|
||||||
return binding.hasOverrides ? binding.effectivePath : binding.path;
|
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);
|
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);
|
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;
|
if (action == null) return default;
|
||||||
|
|
||||||
var curCategory = deviceOverride ?? InputDeviceWatcher.CurrentCategory;
|
var curCategory = deviceOverride ?? InputDeviceWatcher.CurrentCategory;
|
||||||
var hints = GetDeviceHintsForCategory(curCategory);
|
var hints = GetDeviceHintsForCategory(curCategory);
|
||||||
|
|
||||||
foreach (var binding in action.bindings)
|
foreach (var b in action.bindings)
|
||||||
{
|
{
|
||||||
var deviceName = binding.path ?? string.Empty;
|
if (!string.IsNullOrEmpty(compositePartName))
|
||||||
if (hints.Any(h => deviceName.IndexOf(h, StringComparison.OrdinalIgnoreCase) >= 0))
|
|
||||||
{
|
{
|
||||||
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;
|
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)
|
static string[] GetDeviceHintsForCategory(InputDeviceWatcher.InputDeviceCategory cat)
|
||||||
{
|
{
|
||||||
switch (cat)
|
switch (cat)
|
||||||
{
|
{
|
||||||
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
||||||
return new[] { "Keyboard", "Mouse" };
|
return KeyboardHints;
|
||||||
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
||||||
return new[] { "XInput", "Xbox", "Gamepad" };
|
return XboxHints;
|
||||||
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
||||||
return new[] { "DualShock", "DualSense", "PlayStation", "Gamepad" };
|
return PlayStationHints;
|
||||||
default:
|
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);
|
if (action == null) return string.Empty;
|
||||||
return GetDisplayNameFromControlPath(controlPath);
|
var binding = GetBindingControl(action, compositePartName, deviceOverride);
|
||||||
|
return binding.ToDisplayString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDisplayNameFromControlPath(string controlPath)
|
public static string GetDisplayNameFromControlPath(string controlPath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(controlPath)) return string.Empty;
|
if (string.IsNullOrEmpty(controlPath)) return string.Empty;
|
||||||
var parts = controlPath.Split('/');
|
var parts = controlPath.Split('/');
|
||||||
var last = parts[parts.Length - 1].Trim(new char[] { '{', '}', '<', '>', '\'', '"' });
|
var last = parts[parts.Length - 1].Trim(TrimChars);
|
||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,54 +9,78 @@ using System.Threading.Tasks;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
using AlicizaX.InputGlyph;
|
using AlicizaX;
|
||||||
|
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
using RxUnit = System.Reactive.Unit;
|
using RxUnit = System.Reactive.Unit;
|
||||||
|
|
||||||
namespace InputRemapper
|
|
||||||
{
|
public class InputBindingManager : MonoSingleton<InputBindingManager>
|
||||||
public class InputBindingManager : MonoBehaviour
|
|
||||||
{
|
{
|
||||||
public const string NULL_BINDING = "__NULL__";
|
public const string NULL_BINDING = "__NULL__";
|
||||||
|
private const string KEYBOARD_DEVICE = "<Keyboard>";
|
||||||
|
private const string MOUSE_DELTA = "<Mouse>/delta";
|
||||||
|
private const string MOUSE_SCROLL = "<Mouse>/scroll";
|
||||||
|
private const string MOUSE_SCROLL_X = "<Mouse>/scroll/x";
|
||||||
|
private const string MOUSE_SCROLL_Y = "<Mouse>/scroll/y";
|
||||||
|
private const string KEYBOARD_ESCAPE = "<Keyboard>/escape";
|
||||||
|
|
||||||
[Tooltip("InputActionAsset to manage")]
|
[Tooltip("InputActionAsset to manage")]
|
||||||
public InputActionAsset actions;
|
public InputActionAsset actions;
|
||||||
|
|
||||||
[SerializeField] private InputGlyphDatabase inputGlyphDatabase;
|
|
||||||
|
|
||||||
public string fileName = "input_bindings.json";
|
public string fileName = "input_bindings.json";
|
||||||
public bool debugMode = false;
|
public bool debugMode = false;
|
||||||
|
|
||||||
public Dictionary<string, ActionMap> actionMap = new Dictionary<string, ActionMap>();
|
public Dictionary<string, ActionMap> actionMap = new Dictionary<string, ActionMap>();
|
||||||
public List<RebindContext> preparedRebinds = new List<RebindContext>();
|
public HashSet<RebindContext> preparedRebinds = new HashSet<RebindContext>();
|
||||||
|
|
||||||
internal InputActionRebindingExtensions.RebindingOperation rebindOperation;
|
internal InputActionRebindingExtensions.RebindingOperation rebindOperation;
|
||||||
private readonly List<string> pressedActions = new List<string>();
|
|
||||||
private bool isApplyPending = false;
|
private bool isApplyPending = false;
|
||||||
private string defaultBindingsJson = string.Empty;
|
private string defaultBindingsJson = string.Empty;
|
||||||
|
private string cachedSavePath;
|
||||||
|
private Dictionary<string, (ActionMap map, ActionMap.Action action)> actionLookup = new Dictionary<string, (ActionMap, ActionMap.Action)>();
|
||||||
|
|
||||||
public ReplaySubject<RxUnit> OnInputsInit = new ReplaySubject<RxUnit>(1);
|
public readonly ReplaySubject<RxUnit> OnInputsInit = new ReplaySubject<RxUnit>(1);
|
||||||
public Subject<bool> OnApply = new Subject<bool>();
|
public readonly Subject<(bool success, HashSet<RebindContext> appliedContexts)> OnApply = new Subject<(bool, HashSet<RebindContext>)>();
|
||||||
public Subject<RebindContext> OnRebindPrepare = new Subject<RebindContext>();
|
public readonly Subject<RebindContext> OnRebindPrepare = new Subject<RebindContext>();
|
||||||
public Subject<RxUnit> OnRebindStart = new Subject<RxUnit>();
|
public readonly Subject<RxUnit> OnRebindStart = new Subject<RxUnit>();
|
||||||
public Subject<bool> OnRebindEnd = new Subject<bool>();
|
public readonly Subject<(bool success, RebindContext context)> OnRebindEnd = new Subject<(bool, RebindContext)>();
|
||||||
public Subject<(RebindContext prepared, RebindContext conflict)> OnRebindConflict = new Subject<(RebindContext, RebindContext)>();
|
public readonly Subject<(RebindContext prepared, RebindContext conflict)> OnRebindConflict = new Subject<(RebindContext, RebindContext)>();
|
||||||
|
|
||||||
public string SavePath
|
public string SavePath
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrEmpty(cachedSavePath))
|
||||||
|
return cachedSavePath;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
string folder = Application.dataPath;
|
string folder = Application.dataPath;
|
||||||
#else
|
#else
|
||||||
string folder = Application.persistentDataPath;
|
string folder = Application.persistentDataPath;
|
||||||
#endif
|
#endif
|
||||||
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
cachedSavePath = Path.Combine(folder, fileName);
|
||||||
return 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)
|
if (actions == null)
|
||||||
{
|
{
|
||||||
Debug.LogError("InputBindingManager: InputActionAsset not assigned.");
|
Debug.LogError("InputBindingManager: InputActionAsset not assigned.");
|
||||||
@ -69,8 +93,9 @@ namespace InputRemapper
|
|||||||
{
|
{
|
||||||
defaultBindingsJson = actions.SaveBindingOverridesAsJson();
|
defaultBindingsJson = actions.SaveBindingOverridesAsJson();
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
Debug.LogWarning($"[InputBindingManager] Failed to save default bindings: {ex.Message}");
|
||||||
defaultBindingsJson = string.Empty;
|
defaultBindingsJson = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +108,10 @@ namespace InputRemapper
|
|||||||
{
|
{
|
||||||
actions.LoadBindingOverridesFromJson(json);
|
actions.LoadBindingOverridesFromJson(json);
|
||||||
RefreshBindingPathsFromActions();
|
RefreshBindingPathsFromActions();
|
||||||
if (debugMode) Debug.Log($"Loaded overrides from {SavePath}");
|
if (debugMode)
|
||||||
|
{
|
||||||
|
Debug.Log($"Loaded overrides from {SavePath}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -96,8 +124,13 @@ namespace InputRemapper
|
|||||||
actions.Enable();
|
actions.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
protected override void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (_instance == this)
|
||||||
|
{
|
||||||
|
_instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
rebindOperation?.Dispose();
|
rebindOperation?.Dispose();
|
||||||
rebindOperation = null;
|
rebindOperation = null;
|
||||||
|
|
||||||
@ -111,18 +144,45 @@ namespace InputRemapper
|
|||||||
|
|
||||||
private void BuildActionMap()
|
private void BuildActionMap()
|
||||||
{
|
{
|
||||||
|
// Pre-allocate with known capacity to avoid resizing
|
||||||
|
int mapCount = actions.actionMaps.Count;
|
||||||
actionMap.Clear();
|
actionMap.Clear();
|
||||||
|
actionLookup.Clear();
|
||||||
|
|
||||||
|
// Estimate total action count for better allocation
|
||||||
|
int estimatedActionCount = 0;
|
||||||
foreach (var map in actions.actionMaps)
|
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<string, ActionMap>(mapCount);
|
||||||
|
actionLookup = new Dictionary<string, (ActionMap, ActionMap.Action)>(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()
|
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)
|
foreach (var bpair in a.bindings)
|
||||||
{
|
{
|
||||||
bpair.Value.bindingPath.EffectivePath = a.action.bindings[bpair.Key].effectivePath;
|
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 string name;
|
||||||
public Dictionary<string, Action> actions = new Dictionary<string, Action>();
|
public Dictionary<string, Action> actions;
|
||||||
|
|
||||||
public ActionMap(InputActionMap map)
|
public ActionMap(InputActionMap map)
|
||||||
{
|
{
|
||||||
name = map.name;
|
name = map.name;
|
||||||
foreach (var action in map.actions) actions.Add(action.name, new Action(action));
|
int actionCount = map.actions.Count;
|
||||||
|
actions = new Dictionary<string, Action>(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 InputAction action;
|
||||||
public Dictionary<int, Binding> bindings = new Dictionary<int, Binding>();
|
public Dictionary<int, Binding> bindings;
|
||||||
|
|
||||||
public Action(InputAction action)
|
public Action(InputAction action)
|
||||||
{
|
{
|
||||||
this.action = action;
|
this.action = action;
|
||||||
int count = action.bindings.Count;
|
int count = action.bindings.Count;
|
||||||
|
bindings = new Dictionary<int, Binding>(count);
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
if (action.bindings[i].isComposite)
|
if (action.bindings[i].isComposite)
|
||||||
@ -170,28 +237,39 @@ namespace InputRemapper
|
|||||||
|
|
||||||
void AddBinding(InputBinding binding, int bindingIndex)
|
void AddBinding(InputBinding binding, int bindingIndex)
|
||||||
{
|
{
|
||||||
bindings.Add(bindingIndex, new Binding
|
bindings.Add(bindingIndex, new Binding(
|
||||||
{
|
binding.name,
|
||||||
name = binding.name,
|
action.name,
|
||||||
parentAction = action.name,
|
binding.name,
|
||||||
compositePart = binding.name,
|
bindingIndex,
|
||||||
bindingIndex = bindingIndex,
|
binding.groups?.Split(InputBinding.Separator) ?? Array.Empty<string>(),
|
||||||
group = binding.groups?.Split(InputBinding.Separator) ?? Array.Empty<string>(),
|
new BindingPath(binding.path, binding.overridePath),
|
||||||
bindingPath = new BindingPath(binding.path, binding.overridePath),
|
binding
|
||||||
inputBinding = binding
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Binding
|
public readonly struct Binding
|
||||||
{
|
{
|
||||||
public string name;
|
public readonly string name;
|
||||||
public string parentAction;
|
public readonly string parentAction;
|
||||||
public string compositePart;
|
public readonly string compositePart;
|
||||||
public int bindingIndex;
|
public readonly int bindingIndex;
|
||||||
public string[] group;
|
public readonly string[] group;
|
||||||
public BindingPath bindingPath;
|
public readonly BindingPath bindingPath;
|
||||||
public InputBinding inputBinding;
|
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 bindingPath;
|
||||||
public string overridePath;
|
public string overridePath;
|
||||||
private readonly Subject<RxUnit> observer = new Subject<RxUnit>();
|
private Subject<RxUnit> observer;
|
||||||
|
|
||||||
public BindingPath(string bindingPath, string overridePath)
|
public BindingPath(string bindingPath, string overridePath)
|
||||||
{
|
{
|
||||||
@ -214,18 +292,33 @@ namespace InputRemapper
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
overridePath = (value == bindingPath) ? string.Empty : value;
|
overridePath = (value == bindingPath) ? string.Empty : value;
|
||||||
observer.OnNext(RxUnit.Default);
|
observer?.OnNext(RxUnit.Default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IObservable<string> EffectivePathObservable => observer.Select(_ => EffectivePath);
|
public IObservable<string> EffectivePathObservable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
observer ??= new Subject<RxUnit>();
|
||||||
|
return observer.Select(_ => EffectivePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
observer?.OnCompleted();
|
||||||
|
observer?.Dispose();
|
||||||
|
observer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RebindContext
|
public sealed class RebindContext
|
||||||
{
|
{
|
||||||
public InputAction action;
|
public InputAction action;
|
||||||
public int bindingIndex;
|
public int bindingIndex;
|
||||||
public string overridePath;
|
public string overridePath;
|
||||||
|
private string cachedToString;
|
||||||
|
|
||||||
public RebindContext(InputAction action, int bindingIndex, string overridePath)
|
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 int GetHashCode() => (action?.name ?? string.Empty, bindingIndex).GetHashCode();
|
||||||
public override string ToString() => $"{action?.name ?? "<null>"}:{bindingIndex}";
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (cachedToString == null && action != null)
|
||||||
|
{
|
||||||
|
cachedToString = $"{action.name}:{bindingIndex}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedToString ?? "<null>";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- Public API ---------------- */
|
/* ---------------- Public API ---------------- */
|
||||||
|
|
||||||
public static InputAction Action(string actionName)
|
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}'");
|
Debug.LogError($"[InputBindingManager] Could not find action '{actionName}'");
|
||||||
@ -272,42 +377,61 @@ namespace InputRemapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
Instance.actions.Disable();
|
Instance.actions.Disable();
|
||||||
Instance.PerformInteractiveRebinding(action, bindingIndex, "<Keyboard>", true);
|
Instance.PerformInteractiveRebinding(action, bindingIndex, KEYBOARD_DEVICE, true);
|
||||||
Instance.OnRebindStart.OnNext(RxUnit.Default);
|
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 void CancelRebind() => Instance.rebindOperation?.Cancel();
|
||||||
|
|
||||||
public static async Task<bool> ConfirmApply(bool clearConflicts = true)
|
public static async UniTask<bool> ConfirmApply(bool clearConflicts = true)
|
||||||
{
|
{
|
||||||
if (!Instance.isApplyPending) return false;
|
if (!Instance.isApplyPending) return false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Create a copy of the prepared rebinds before clearing
|
||||||
|
var appliedContexts = new HashSet<RebindContext>(Instance.preparedRebinds);
|
||||||
|
|
||||||
foreach (var ctx in 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);
|
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();
|
Instance.preparedRebinds.Clear();
|
||||||
await Instance.WriteOverridesToDiskAsync();
|
await Instance.WriteOverridesToDiskAsync();
|
||||||
Instance.OnApply.OnNext(true);
|
Instance.OnApply.OnNext((true, appliedContexts));
|
||||||
Instance.isApplyPending = false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogError("[InputBindingManager] Failed to apply binds: " + ex);
|
Debug.LogError("[InputBindingManager] Failed to apply binds: " + ex);
|
||||||
Instance.OnApply.OnNext(false);
|
Instance.OnApply.OnNext((false, new HashSet<RebindContext>()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,23 +439,34 @@ namespace InputRemapper
|
|||||||
public static void DiscardPrepared()
|
public static void DiscardPrepared()
|
||||||
{
|
{
|
||||||
if (!Instance.isApplyPending) return;
|
if (!Instance.isApplyPending) return;
|
||||||
|
|
||||||
|
// Create a copy of the prepared rebinds before clearing (for event notification)
|
||||||
|
var discardedContexts = new HashSet<RebindContext>(Instance.preparedRebinds);
|
||||||
|
|
||||||
Instance.preparedRebinds.Clear();
|
Instance.preparedRebinds.Clear();
|
||||||
Instance.isApplyPending = false;
|
Instance.isApplyPending = false;
|
||||||
Instance.OnApply.OnNext(false);
|
Instance.OnApply.OnNext((false, discardedContexts));
|
||||||
if (Instance.debugMode) Debug.Log("[InputBindingManager] Prepared rebinds discarded.");
|
if (Instance.debugMode)
|
||||||
|
{
|
||||||
|
Debug.Log("[InputBindingManager] Prepared rebinds discarded.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PerformInteractiveRebinding(InputAction action, int bindingIndex, string deviceMatchPath = null, bool excludeMouseMovementAndScroll = true)
|
private void PerformInteractiveRebinding(InputAction action, int bindingIndex, string deviceMatchPath = null, bool excludeMouseMovementAndScroll = true)
|
||||||
{
|
{
|
||||||
var op = action.PerformInteractiveRebinding(bindingIndex);
|
var op = action.PerformInteractiveRebinding(bindingIndex);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(deviceMatchPath)) op = op.WithControlsHavingToMatchPath(deviceMatchPath);
|
if (!string.IsNullOrEmpty(deviceMatchPath))
|
||||||
|
{
|
||||||
|
op = op.WithControlsHavingToMatchPath(deviceMatchPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (excludeMouseMovementAndScroll)
|
if (excludeMouseMovementAndScroll)
|
||||||
{
|
{
|
||||||
op = op.WithControlsExcluding("<Mouse>/delta");
|
op = op.WithControlsExcluding(MOUSE_DELTA)
|
||||||
op = op.WithControlsExcluding("<Mouse>/scroll");
|
.WithControlsExcluding(MOUSE_SCROLL)
|
||||||
op = op.WithControlsExcluding("<Mouse>/scroll/x");
|
.WithControlsExcluding(MOUSE_SCROLL_X)
|
||||||
op = op.WithControlsExcluding("<Mouse>/scroll/y");
|
.WithControlsExcluding(MOUSE_SCROLL_Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
rebindOperation = op
|
rebindOperation = op
|
||||||
@ -356,19 +491,27 @@ namespace InputRemapper
|
|||||||
})
|
})
|
||||||
.OnComplete(opc =>
|
.OnComplete(opc =>
|
||||||
{
|
{
|
||||||
if (debugMode) Debug.Log("[InputBindingManager] Rebind completed");
|
if (debugMode)
|
||||||
|
{
|
||||||
|
Debug.Log("[InputBindingManager] Rebind completed");
|
||||||
|
}
|
||||||
|
|
||||||
actions.Enable();
|
actions.Enable();
|
||||||
OnRebindEnd.OnNext(true);
|
OnRebindEnd.OnNext((true, new RebindContext(action, bindingIndex, action.bindings[bindingIndex].effectivePath)));
|
||||||
CleanRebindOperation();
|
CleanRebindOperation();
|
||||||
})
|
})
|
||||||
.OnCancel(opc =>
|
.OnCancel(opc =>
|
||||||
{
|
{
|
||||||
if (debugMode) Debug.Log("[InputBindingManager] Rebind cancelled");
|
if (debugMode)
|
||||||
|
{
|
||||||
|
Debug.Log("[InputBindingManager] Rebind cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
actions.Enable();
|
actions.Enable();
|
||||||
OnRebindEnd.OnNext(false);
|
OnRebindEnd.OnNext((false, new RebindContext(action, bindingIndex, action.bindings[bindingIndex].effectivePath)));
|
||||||
CleanRebindOperation();
|
CleanRebindOperation();
|
||||||
})
|
})
|
||||||
.WithCancelingThrough("<Keyboard>/escape")
|
.WithCancelingThrough(KEYBOARD_ESCAPE)
|
||||||
.Start();
|
.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,17 +538,21 @@ namespace InputRemapper
|
|||||||
|
|
||||||
private bool AnyBindingPath(string bindingPath, InputAction currentAction, int currentIndex, out (InputAction action, int bindingIndex) duplicate)
|
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;
|
// Skip if it's the same action and same binding index
|
||||||
var eff = bindingPair.Value.bindingPath.EffectivePath;
|
if (isSameAction && bindingPair.Key == currentIndex)
|
||||||
if (eff == bindingPath)
|
continue;
|
||||||
|
|
||||||
|
if (bindingPair.Value.bindingPath.EffectivePath == bindingPath)
|
||||||
{
|
{
|
||||||
duplicate = (actionPair.Value.action, bindingPair.Key);
|
duplicate = (actionPair.action, bindingPair.Key);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,7 +565,8 @@ namespace InputRemapper
|
|||||||
|
|
||||||
private void PrepareRebind(RebindContext context)
|
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))
|
if (string.IsNullOrEmpty(context.overridePath))
|
||||||
{
|
{
|
||||||
@ -434,19 +582,24 @@ namespace InputRemapper
|
|||||||
preparedRebinds.Add(context);
|
preparedRebinds.Add(context);
|
||||||
isApplyPending = true;
|
isApplyPending = true;
|
||||||
OnRebindPrepare.OnNext(context);
|
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
|
try
|
||||||
{
|
{
|
||||||
var json = actions.SaveBindingOverridesAsJson();
|
var json = actions.SaveBindingOverridesAsJson();
|
||||||
var dir = Path.GetDirectoryName(SavePath);
|
EnsureSaveDirectoryExists();
|
||||||
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
|
||||||
using (var sw = new StreamWriter(SavePath, false)) await sw.WriteAsync(json);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -455,22 +608,34 @@ namespace InputRemapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ResetToDefaultAsync()
|
public async UniTask ResetToDefaultAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(defaultBindingsJson)) actions.LoadBindingOverridesFromJson(defaultBindingsJson);
|
if (!string.IsNullOrEmpty(defaultBindingsJson))
|
||||||
|
{
|
||||||
|
actions.LoadBindingOverridesFromJson(defaultBindingsJson);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var map in actionMap)
|
foreach (var map in actionMap.Values)
|
||||||
foreach (var a in map.Value.actions)
|
{
|
||||||
for (int b = 0; b < a.Value.action.bindings.Count; b++)
|
foreach (var a in map.actions.Values)
|
||||||
a.Value.action.RemoveBindingOverride(b);
|
{
|
||||||
|
for (int b = 0; b < a.action.bindings.Count; b++)
|
||||||
|
{
|
||||||
|
a.action.RemoveBindingOverride(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RefreshBindingPathsFromActions();
|
RefreshBindingPathsFromActions();
|
||||||
await WriteOverridesToDiskAsync();
|
await WriteOverridesToDiskAsync();
|
||||||
if (debugMode) Debug.Log("Reset to default and saved.");
|
if (debugMode)
|
||||||
|
{
|
||||||
|
Debug.Log("Reset to default and saved.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -480,17 +645,21 @@ namespace InputRemapper
|
|||||||
|
|
||||||
public static BindingPath GetBindingPath(string actionName, int bindingIndex = 0)
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// choose best binding index for keyboard; if compositePartName != null then look for part
|
// choose best binding index for keyboard; if compositePartName != null then look for part
|
||||||
public int FindBestBindingIndexForKeyboard(InputAction action, string compositePartName = null)
|
public int FindBestBindingIndexForKeyboard(InputAction action, string compositePartName = null)
|
||||||
{
|
{
|
||||||
@ -498,36 +667,51 @@ namespace InputRemapper
|
|||||||
|
|
||||||
int fallbackPart = -1;
|
int fallbackPart = -1;
|
||||||
int fallbackNonComposite = -1;
|
int fallbackNonComposite = -1;
|
||||||
|
bool searchingForCompositePart = !string.IsNullOrEmpty(compositePartName);
|
||||||
|
|
||||||
for (int i = 0; i < action.bindings.Count; i++)
|
for (int i = 0; i < action.bindings.Count; i++)
|
||||||
{
|
{
|
||||||
var b = action.bindings[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 (!b.isPartOfComposite) continue;
|
||||||
if (!string.Equals(b.name, compositePartName, StringComparison.OrdinalIgnoreCase)) continue;
|
if (!string.Equals(b.name, compositePartName, StringComparison.OrdinalIgnoreCase)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this binding is for keyboard
|
||||||
|
bool isKeyboardBinding = (!string.IsNullOrEmpty(b.path) && b.path.StartsWith(KEYBOARD_DEVICE, StringComparison.OrdinalIgnoreCase)) ||
|
||||||
|
(!string.IsNullOrEmpty(b.effectivePath) && b.effectivePath.StartsWith(KEYBOARD_DEVICE, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (b.isPartOfComposite)
|
if (b.isPartOfComposite)
|
||||||
{
|
{
|
||||||
if (fallbackPart == -1) fallbackPart = i;
|
if (fallbackPart == -1) fallbackPart = i;
|
||||||
if (!string.IsNullOrEmpty(b.path) && b.path.StartsWith("<Keyboard>", StringComparison.OrdinalIgnoreCase)) return i;
|
if (isKeyboardBinding) return i;
|
||||||
if (!string.IsNullOrEmpty(b.effectivePath) && b.effectivePath.StartsWith("<Keyboard>", StringComparison.OrdinalIgnoreCase)) return i;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (fallbackNonComposite == -1) fallbackNonComposite = i;
|
if (fallbackNonComposite == -1) fallbackNonComposite = i;
|
||||||
if (!string.IsNullOrEmpty(b.path) && b.path.StartsWith("<Keyboard>", StringComparison.OrdinalIgnoreCase)) return i;
|
if (isKeyboardBinding) return i;
|
||||||
if (!string.IsNullOrEmpty(b.effectivePath) && b.effectivePath.StartsWith("<Keyboard>", StringComparison.OrdinalIgnoreCase)) return i;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fallbackNonComposite >= 0) return fallbackNonComposite;
|
return fallbackNonComposite >= 0 ? fallbackNonComposite : fallbackPart;
|
||||||
return fallbackPart;
|
}
|
||||||
|
|
||||||
|
public static InputBindingManager Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
_instance = FindObjectOfType<InputBindingManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InputBindingManager Instance => _instance ??= FindObjectOfType<InputBindingManager>();
|
|
||||||
private static InputBindingManager _instance;
|
private static InputBindingManager _instance;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -5,101 +5,184 @@ using UnityEngine;
|
|||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
using UnityEngine.U2D;
|
using UnityEngine.U2D;
|
||||||
|
|
||||||
namespace AlicizaX.InputGlyph
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class GlyphEntry
|
||||||
{
|
{
|
||||||
[Serializable]
|
public Sprite Sprite;
|
||||||
public class GlyphEntry
|
public InputAction action;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class DeviceGlyphTable
|
||||||
|
{
|
||||||
|
public string deviceName;
|
||||||
|
|
||||||
|
public Texture2D spriteSheetTexture;
|
||||||
|
|
||||||
|
public Sprite platformIcons;
|
||||||
|
|
||||||
|
public List<GlyphEntry> entries = new List<GlyphEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<DeviceGlyphTable> tables = new List<DeviceGlyphTable>();
|
||||||
|
|
||||||
|
// 当 FindEntryByControlPath 传空 path 时返回的占位 sprite
|
||||||
|
public Sprite placeholderSprite;
|
||||||
|
|
||||||
|
// Cache for faster lookups
|
||||||
|
private Dictionary<string, DeviceGlyphTable> _tableCache;
|
||||||
|
private Dictionary<(string path, InputDeviceWatcher.InputDeviceCategory device), Sprite> _spriteCache;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
public Sprite Sprite;
|
BuildCache();
|
||||||
public InputAction action;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
private void BuildCache()
|
||||||
public class DeviceGlyphTable
|
|
||||||
{
|
{
|
||||||
// Table 名称(序列化)
|
if (_tableCache == null)
|
||||||
public string deviceName;
|
{
|
||||||
|
_tableCache = new Dictionary<string, DeviceGlyphTable>(tables.Count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_tableCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// 支持三种来源:
|
if (_spriteCache == null)
|
||||||
// 1) TMP Sprite Asset(TextMeshPro sprite asset)
|
{
|
||||||
public TMP_SpriteAsset tmpAsset;
|
_spriteCache = new Dictionary<(string, InputDeviceWatcher.InputDeviceCategory), Sprite>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_spriteCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// 2) Unity SpriteAtlas(可选)
|
for (int i = 0; i < tables.Count; i++)
|
||||||
public SpriteAtlas spriteAtlas;
|
{
|
||||||
|
var table = tables[i];
|
||||||
// 3) Texture2D(Sprite Mode = Multiple),在 Sprite Editor 切好的切片
|
if (table != null && !string.IsNullOrEmpty(table.deviceName))
|
||||||
public Texture2D spriteSheetTexture;
|
{
|
||||||
|
_tableCache[table.deviceName.ToLowerInvariant()] = table;
|
||||||
public List<GlyphEntry> entries = new List<GlyphEntry>();
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[CreateAssetMenu(fileName = "InputGlyphDatabase", menuName = "InputGlyphs/InputGlyphDatabase", order = 400)]
|
public DeviceGlyphTable GetTable(string deviceName)
|
||||||
public class InputGlyphDatabase : ScriptableObject
|
|
||||||
{
|
{
|
||||||
public List<DeviceGlyphTable> tables = new List<DeviceGlyphTable>();
|
if (string.IsNullOrEmpty(deviceName)) return null;
|
||||||
|
if (tables == null) return null;
|
||||||
|
|
||||||
// 当 FindEntryByControlPath 传空 path 时返回的占位 sprite
|
// Ensure cache is built
|
||||||
public Sprite placeholderSprite;
|
if (_tableCache == null || _tableCache.Count == 0)
|
||||||
|
|
||||||
public DeviceGlyphTable GetTable(string deviceName)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(deviceName)) return null;
|
BuildCache();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兼容枚举版本(示例)
|
// Use cache for O(1) lookup
|
||||||
public DeviceGlyphTable GetTable(InputDeviceWatcher.InputDeviceCategory device)
|
if (_tableCache.TryGetValue(deviceName.ToLowerInvariant(), out var table))
|
||||||
{
|
{
|
||||||
string name = "Other";
|
return table;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
||||||
if (string.IsNullOrEmpty(controlPath) || entry == null)
|
name = DEVICE_KEYBOARD;
|
||||||
{
|
break;
|
||||||
return placeholderSprite;
|
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
||||||
}
|
name = DEVICE_XBOX;
|
||||||
return entry.Sprite;
|
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);
|
return placeholderSprite;
|
||||||
if (t != null)
|
}
|
||||||
|
|
||||||
|
// 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];
|
var b = bindings[j];
|
||||||
if (e == null) continue;
|
if (!string.IsNullOrEmpty(b.path) && string.Equals(b.path, controlPath, StringComparison.OrdinalIgnoreCase))
|
||||||
if (e.action == null) continue;
|
|
||||||
if (e.action.bindings.Count <= 0) continue;
|
|
||||||
foreach (var binding in e.action.bindings)
|
|
||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
using AlicizaX.InputGlyph;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.U2D;
|
|
||||||
using TMPro;
|
|
||||||
|
|
||||||
[CustomEditor(typeof(InputGlyphDatabase))]
|
[CustomEditor(typeof(InputGlyphDatabase))]
|
||||||
public class InputGlyphDatabaseEditor : Editor
|
public class InputGlyphDatabaseEditor : Editor
|
||||||
@ -22,6 +17,9 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
List<string> searchStrings = new List<string>();
|
List<string> searchStrings = new List<string>();
|
||||||
List<int> currentPages = new List<int>();
|
List<int> currentPages = new List<int>();
|
||||||
|
|
||||||
|
// per-table temporary fields for adding single entry (only sprite now)
|
||||||
|
List<Sprite> newEntrySprites = new List<Sprite>();
|
||||||
|
|
||||||
const int itemsPerPage = 10;
|
const int itemsPerPage = 10;
|
||||||
const int previewSize = 52;
|
const int previewSize = 52;
|
||||||
|
|
||||||
@ -97,9 +95,11 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
var nameProp = t.FindPropertyRelative("deviceName");
|
var nameProp = t.FindPropertyRelative("deviceName");
|
||||||
if (nameProp != null && string.Equals(nameProp.stringValue, trimmed, StringComparison.OrdinalIgnoreCase))
|
if (nameProp != null && string.Equals(nameProp.stringValue, trimmed, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
exists = true; break;
|
exists = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exists)
|
if (exists)
|
||||||
{
|
{
|
||||||
EditorUtility.DisplayDialog("Duplicate", "A table with that name already exists.", "OK");
|
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 newTable = tablesProp.GetArrayElementAtIndex(newIndex);
|
||||||
var nameProp = newTable.FindPropertyRelative("deviceName");
|
var nameProp = newTable.FindPropertyRelative("deviceName");
|
||||||
if (nameProp != null) nameProp.stringValue = trimmed;
|
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");
|
var sheetProp = newTable.FindPropertyRelative("spriteSheetTexture");
|
||||||
if (sheetProp != null) sheetProp.objectReferenceValue = null;
|
if (sheetProp != null) sheetProp.objectReferenceValue = null;
|
||||||
var entriesProp = newTable.FindPropertyRelative("entries");
|
var entriesProp = newTable.FindPropertyRelative("entries");
|
||||||
@ -130,6 +126,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("Cancel", EditorStyles.toolbarButton, GUILayout.Width(80)))
|
if (GUILayout.Button("Cancel", EditorStyles.toolbarButton, GUILayout.Width(80)))
|
||||||
{
|
{
|
||||||
showAddField = false;
|
showAddField = false;
|
||||||
@ -157,7 +154,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(22)))
|
if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(22)))
|
||||||
{
|
{
|
||||||
if (EditorUtility.DisplayDialog("Delete Table?",
|
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);
|
tablesProp.DeleteArrayElementAtIndex(i);
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
@ -208,6 +205,11 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
|
|
||||||
EnsureEditorListsLength();
|
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();
|
GUILayout.BeginHorizontal();
|
||||||
GUIStyle searchStyle = EditorStyles.toolbarSearchField ?? EditorStyles.textField;
|
GUIStyle searchStyle = EditorStyles.toolbarSearchField ?? EditorStyles.textField;
|
||||||
searchStrings[tabIndex] = GUILayout.TextField(searchStrings[tabIndex] ?? "", searchStyle);
|
searchStrings[tabIndex] = GUILayout.TextField(searchStrings[tabIndex] ?? "", searchStyle);
|
||||||
@ -215,59 +217,6 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
|
|
||||||
EditorGUILayout.Space(6);
|
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");
|
var sheetProp = tableProp.FindPropertyRelative("spriteSheetTexture");
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
GUILayout.Label("Sprite Sheet (Texture2D)", GUILayout.Width(140));
|
GUILayout.Label("Sprite Sheet (Texture2D)", GUILayout.Width(140));
|
||||||
@ -276,23 +225,74 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
{
|
{
|
||||||
ParseSpriteSheetIntoTableSerialized(tableProp);
|
ParseSpriteSheetIntoTableSerialized(tableProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (GUILayout.Button("Clear", GUILayout.Width(80)))
|
if (GUILayout.Button("Clear", GUILayout.Width(80)))
|
||||||
{
|
{
|
||||||
var entriesProp = tableProp.FindPropertyRelative("entries");
|
var entriesProp = tableProp.FindPropertyRelative("entries");
|
||||||
if (entriesProp != null) entriesProp.arraySize = 0;
|
if (entriesProp != null) entriesProp.arraySize = 0;
|
||||||
var nameProp = tableProp.FindPropertyRelative("deviceName");
|
if (runtimeTableIndex >= 0 && db != null)
|
||||||
if (nameProp != null && db != null)
|
|
||||||
{
|
{
|
||||||
var deviceName = nameProp.stringValue;
|
var table = db.tables[runtimeTableIndex];
|
||||||
var table = db.GetTable(deviceName);
|
|
||||||
if (table != null) table.entries.Clear();
|
if (table != null) table.entries.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
EditorUtility.SetDirty(db);
|
EditorUtility.SetDirty(db);
|
||||||
currentPages[tabIndex] = 0;
|
currentPages[tabIndex] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
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);
|
EditorGUILayout.Space(6);
|
||||||
|
|
||||||
var entries = tableProp.FindPropertyRelative("entries");
|
var entries = tableProp.FindPropertyRelative("entries");
|
||||||
@ -319,15 +319,30 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
currentPages[tabIndex] = Mathf.Clamp(currentPages[tabIndex], 0, totalPages - 1);
|
currentPages[tabIndex] = Mathf.Clamp(currentPages[tabIndex], 0, totalPages - 1);
|
||||||
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
if (GUILayout.Button("<<", EditorStyles.miniButtonLeft, GUILayout.Width(36))) { currentPages[tabIndex] = 0; }
|
if (GUILayout.Button("<<", EditorStyles.miniButtonLeft, GUILayout.Width(36)))
|
||||||
if (GUILayout.Button("<", EditorStyles.miniButtonMid, GUILayout.Width(36))) { currentPages[tabIndex] = Mathf.Max(0, currentPages[tabIndex] - 1); }
|
{
|
||||||
|
currentPages[tabIndex] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("<", EditorStyles.miniButtonMid, GUILayout.Width(36)))
|
||||||
|
{
|
||||||
|
currentPages[tabIndex] = Mathf.Max(0, currentPages[tabIndex] - 1);
|
||||||
|
}
|
||||||
|
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
EditorGUILayout.LabelField(string.Format("Page {0}/{1}", currentPages[tabIndex] + 1, totalPages), GUILayout.Width(120));
|
EditorGUILayout.LabelField(string.Format("Page {0}/{1}", currentPages[tabIndex] + 1, totalPages), GUILayout.Width(120));
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
|
|
||||||
if (GUILayout.Button(">", EditorStyles.miniButtonMid, GUILayout.Width(36))) { currentPages[tabIndex] = Mathf.Min(totalPages - 1, currentPages[tabIndex] + 1); }
|
if (GUILayout.Button(">", EditorStyles.miniButtonMid, GUILayout.Width(36)))
|
||||||
if (GUILayout.Button(">>", EditorStyles.miniButtonRight, GUILayout.Width(36))) { currentPages[tabIndex] = totalPages - 1; }
|
{
|
||||||
|
currentPages[tabIndex] = Mathf.Min(totalPages - 1, currentPages[tabIndex] + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button(">>", EditorStyles.miniButtonRight, GUILayout.Width(36)))
|
||||||
|
{
|
||||||
|
currentPages[tabIndex] = totalPages - 1;
|
||||||
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
@ -341,8 +356,10 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
var eProp = entries.GetArrayElementAtIndex(i);
|
var eProp = entries.GetArrayElementAtIndex(i);
|
||||||
if (eProp == null) continue;
|
if (eProp == null) continue;
|
||||||
|
|
||||||
|
// display one entry with a small remove button on the right
|
||||||
using (new EditorGUILayout.HorizontalScope("box"))
|
using (new EditorGUILayout.HorizontalScope("box"))
|
||||||
{
|
{
|
||||||
|
// left: preview column
|
||||||
using (new EditorGUILayout.VerticalScope(GUILayout.Width(80)))
|
using (new EditorGUILayout.VerticalScope(GUILayout.Width(80)))
|
||||||
{
|
{
|
||||||
var spriteProp = eProp.FindPropertyRelative("Sprite");
|
var spriteProp = eProp.FindPropertyRelative("Sprite");
|
||||||
@ -368,11 +385,51 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// middle: action column
|
||||||
EditorGUILayout.BeginVertical();
|
EditorGUILayout.BeginVertical();
|
||||||
var actionProp = eProp.FindPropertyRelative("action");
|
var actionProp = eProp.FindPropertyRelative("action");
|
||||||
EditorGUILayout.Space(2);
|
EditorGUILayout.Space(2);
|
||||||
EditorGUILayout.PropertyField(actionProp, GUIContent.none, GUILayout.ExpandWidth(true));
|
EditorGUILayout.PropertyField(actionProp, GUIContent.none, GUILayout.ExpandWidth(true));
|
||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
|
|
||||||
|
// right: small remove button
|
||||||
|
GUILayout.Space(6);
|
||||||
|
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) ? "<missing>" : 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);
|
EditorGUILayout.Space(4);
|
||||||
@ -409,10 +466,6 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
var newTable = tablesProp.GetArrayElementAtIndex(idx);
|
var newTable = tablesProp.GetArrayElementAtIndex(idx);
|
||||||
var deviceNameProp = newTable.FindPropertyRelative("deviceName");
|
var deviceNameProp = newTable.FindPropertyRelative("deviceName");
|
||||||
if (deviceNameProp != null) deviceNameProp.stringValue = name;
|
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");
|
var sheetProp = newTable.FindPropertyRelative("spriteSheetTexture");
|
||||||
if (sheetProp != null) sheetProp.objectReferenceValue = null;
|
if (sheetProp != null) sheetProp.objectReferenceValue = null;
|
||||||
var entriesProp = newTable.FindPropertyRelative("entries");
|
var entriesProp = newTable.FindPropertyRelative("entries");
|
||||||
@ -427,12 +480,15 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
int count = tablesProp != null ? tablesProp.arraySize : 0;
|
int count = tablesProp != null ? tablesProp.arraySize : 0;
|
||||||
if (searchStrings == null) searchStrings = new List<string>();
|
if (searchStrings == null) searchStrings = new List<string>();
|
||||||
if (currentPages == null) currentPages = new List<int>();
|
if (currentPages == null) currentPages = new List<int>();
|
||||||
|
if (newEntrySprites == null) newEntrySprites = new List<Sprite>();
|
||||||
|
|
||||||
while (searchStrings.Count < count) searchStrings.Add("");
|
while (searchStrings.Count < count) searchStrings.Add("");
|
||||||
while (currentPages.Count < count) currentPages.Add(0);
|
while (currentPages.Count < count) currentPages.Add(0);
|
||||||
|
while (newEntrySprites.Count < count) newEntrySprites.Add(null);
|
||||||
|
|
||||||
while (searchStrings.Count > count) searchStrings.RemoveAt(searchStrings.Count - 1);
|
while (searchStrings.Count > count) searchStrings.RemoveAt(searchStrings.Count - 1);
|
||||||
while (currentPages.Count > count) currentPages.RemoveAt(currentPages.Count - 1);
|
while (currentPages.Count > count) currentPages.RemoveAt(currentPages.Count - 1);
|
||||||
|
while (newEntrySprites.Count > count) newEntrySprites.RemoveAt(newEntrySprites.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnsureEditorListsLength()
|
void EnsureEditorListsLength()
|
||||||
@ -441,247 +497,58 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
SyncEditorListsWithTables();
|
SyncEditorListsWithTables();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Parse TMP SpriteAsset(增强版) -----
|
// ----- 新增:把单个 Sprite 加入到序列化表和 runtime 表 -----
|
||||||
void ParseTMPAssetIntoTableSerialized(SerializedProperty tableProp)
|
void AddEntryToTableSerialized(SerializedProperty tableProp, Sprite sprite)
|
||||||
{
|
{
|
||||||
if (tableProp == null) return;
|
if (tableProp == null) return;
|
||||||
var tmpAssetProp = tableProp.FindPropertyRelative("tmpAsset");
|
var entriesProp = tableProp.FindPropertyRelative("entries");
|
||||||
var asset = tmpAssetProp.objectReferenceValue as TMP_SpriteAsset;
|
if (entriesProp == null) return;
|
||||||
if (asset == null)
|
|
||||||
|
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.");
|
var spriteProp = newE.FindPropertyRelative("Sprite");
|
||||||
return;
|
var actionProp = newE.FindPropertyRelative("action");
|
||||||
}
|
|
||||||
|
|
||||||
var nameProp = tableProp.FindPropertyRelative("deviceName");
|
if (spriteProp != null) spriteProp.objectReferenceValue = sprite;
|
||||||
string deviceName = nameProp != null ? nameProp.stringValue : "";
|
|
||||||
|
|
||||||
int tableIndex = MapSerializedTableToRuntimeIndex(deviceName);
|
// leave action serialized as-is (most projects can't serialize InputAction directly here)
|
||||||
if (tableIndex < 0)
|
if (actionProp != null)
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string[] scoped = AssetDatabase.FindAssets($"\"{name}\" t:Sprite", new[] { assetFolder });
|
actionProp.objectReferenceValue = null;
|
||||||
if (scoped != null && scoped.Length > 0)
|
|
||||||
{
|
|
||||||
foreach (var g in scoped)
|
|
||||||
{
|
|
||||||
var p = AssetDatabase.GUIDToAssetPath(g);
|
|
||||||
var sp = AssetDatabase.LoadAssetAtPath<Sprite>(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<Sprite>(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<Sprite>(path);
|
|
||||||
if (sp == null) continue;
|
|
||||||
bool belongs = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var got = atlas.GetSprite(sp.name);
|
|
||||||
if (got != null) belongs = true;
|
|
||||||
}
|
}
|
||||||
catch
|
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);
|
EditorUtility.SetDirty(db);
|
||||||
serializedObject.Update();
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
AssetDatabase.SaveAssets();
|
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)
|
void ParseSpriteSheetIntoTableSerialized(SerializedProperty tableProp)
|
||||||
{
|
{
|
||||||
if (tableProp == null) return;
|
if (tableProp == null) return;
|
||||||
@ -705,7 +572,11 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tableObj = db.tables[tableIndex];
|
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);
|
string path = AssetDatabase.GetAssetPath(tex);
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
@ -721,35 +592,107 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 收集 sprites(按照文件内顺序;你如果想按名字排序可以在这里加)
|
||||||
List<Sprite> sprites = new List<Sprite>();
|
List<Sprite> sprites = new List<Sprite>();
|
||||||
foreach (var a in assets)
|
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<string, int>(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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 之前按视觉位置排序,改为先按名字逐字符排序(不区分大小写)
|
// runtime 名称 -> 索引 映射
|
||||||
sprites.Sort((a, b) => CompareSpriteNames(a?.name, b?.name));
|
var runtimeNameToIndex = new Dictionary<string, int>(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)
|
foreach (var sp in sprites)
|
||||||
{
|
{
|
||||||
GlyphEntry e = new GlyphEntry();
|
if (sp == null) continue;
|
||||||
e.Sprite = sp;
|
string nm = sp.name;
|
||||||
e.action = null;
|
|
||||||
tableObj.entries.Add(e);
|
// -- 序列化层:同名则替换 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)
|
// 应用并保存修改(序列化层与 runtime 层保持同步)
|
||||||
tableObj.entries.Sort((a, b) => CompareSpriteNames(a?.Sprite?.name, b?.Sprite?.name));
|
|
||||||
|
|
||||||
EditorUtility.SetDirty(db);
|
EditorUtility.SetDirty(db);
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
AssetDatabase.SaveAssets();
|
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)
|
int MapSerializedTableToRuntimeIndex(string deviceName)
|
||||||
@ -760,56 +703,7 @@ public class InputGlyphDatabaseEditor : Editor
|
|||||||
if (string.Equals(db.tables[ti].deviceName, deviceName, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(db.tables[ti].deviceName, deviceName, StringComparison.OrdinalIgnoreCase))
|
||||||
return ti;
|
return ti;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
using AlicizaX.InputGlyph;
|
|
||||||
|
|
||||||
[RequireComponent(typeof(Image))]
|
public sealed class InputGlyphImage : MonoBehaviour
|
||||||
public class InputGlyphImage : MonoBehaviour
|
|
||||||
{
|
{
|
||||||
[SerializeField] private InputActionReference actionReference;
|
[SerializeField] private InputActionReference actionReference;
|
||||||
[SerializeField] private Image targetImage;
|
[SerializeField] private Image targetImage;
|
||||||
[SerializeField] private bool hideIfMissing = false;
|
[SerializeField] private bool hideIfMissing = false;
|
||||||
[SerializeField] private GameObject hideTargetObject;
|
[SerializeField] private GameObject hideTargetObject;
|
||||||
|
|
||||||
|
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
||||||
|
private Sprite _cachedSprite;
|
||||||
|
|
||||||
void OnEnable()
|
void OnEnable()
|
||||||
{
|
{
|
||||||
if (targetImage == null) targetImage = GetComponent<Image>();
|
if (targetImage == null) targetImage = GetComponent<Image>();
|
||||||
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
|
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
|
||||||
|
_cachedCategory = InputDeviceWatcher.InputDeviceCategory.Keyboard;
|
||||||
UpdatePrompt();
|
UpdatePrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,18 +27,34 @@ public class InputGlyphImage : MonoBehaviour
|
|||||||
|
|
||||||
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
||||||
{
|
{
|
||||||
UpdatePrompt();
|
if (_cachedCategory != cat)
|
||||||
|
{
|
||||||
|
_cachedCategory = cat;
|
||||||
|
UpdatePrompt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePrompt()
|
void UpdatePrompt()
|
||||||
{
|
{
|
||||||
if (actionReference == null || actionReference.action == null || targetImage == null) return;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
using AlicizaX.InputGlyph;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
|
|
||||||
[RequireComponent(typeof(TextMeshProUGUI))]
|
[RequireComponent(typeof(TextMeshProUGUI))]
|
||||||
public class InputGlyphText : MonoBehaviour
|
public sealed class InputGlyphText : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField] private InputActionReference actionReference;
|
[SerializeField] private InputActionReference actionReference;
|
||||||
private TMP_Text textField;
|
private TMP_Text textField;
|
||||||
private string _oldText;
|
private string _oldText;
|
||||||
|
private InputDeviceWatcher.InputDeviceCategory _cachedCategory;
|
||||||
|
private string _cachedFormattedText;
|
||||||
|
|
||||||
void OnEnable()
|
void OnEnable()
|
||||||
{
|
{
|
||||||
if (textField == null) textField = GetComponent<TMP_Text>();
|
if (textField == null) textField = GetComponent<TMP_Text>();
|
||||||
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
|
InputDeviceWatcher.OnDeviceChanged += OnDeviceChanged;
|
||||||
_oldText=textField.text;
|
_oldText = textField.text;
|
||||||
|
_cachedCategory = InputDeviceWatcher.InputDeviceCategory.Keyboard;
|
||||||
UpdatePrompt();
|
UpdatePrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,22 +28,36 @@ public class InputGlyphText : MonoBehaviour
|
|||||||
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
InputDeviceWatcher.OnDeviceChanged -= OnDeviceChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory cat)
|
||||||
{
|
{
|
||||||
UpdatePrompt();
|
if (_cachedCategory != cat)
|
||||||
|
{
|
||||||
|
_cachedCategory = cat;
|
||||||
|
UpdatePrompt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePrompt()
|
void UpdatePrompt()
|
||||||
{
|
{
|
||||||
if (actionReference == null || actionReference.action == null || textField == null) return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.text = Utility.Text.Format(_oldText, displayFallback);;
|
string fallbackText = Utility.Text.Format(_oldText, displayFallback);
|
||||||
|
if (_cachedFormattedText != fallbackText)
|
||||||
|
{
|
||||||
|
_cachedFormattedText = fallbackText;
|
||||||
|
textField.text = fallbackText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
Client/Assets/InputGlyph/InputGlyphUXButton.cs
Normal file
63
Client/Assets/InputGlyph/InputGlyphUXButton.cs
Normal file
@ -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<UXButton>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void OnEnable()
|
||||||
|
{
|
||||||
|
if (button == null) button = GetComponent<UXButton>();
|
||||||
|
if (targetImage == null) targetImage = GetComponent<Image>();
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Client/Assets/InputGlyph/InputGlyphUXButton.cs.meta
Normal file
3
Client/Assets/InputGlyph/InputGlyphUXButton.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f24ff96430d042109462f373aed1f2cc
|
||||||
|
timeCreated: 1765870646
|
||||||
@ -1,16 +1,10 @@
|
|||||||
// TestRebindScript.cs
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
using InputRemapper;
|
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 测试用不需要繁琐处理
|
|
||||||
/// </summary>
|
|
||||||
public class TestRebindScript : MonoBehaviour
|
public class TestRebindScript : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("UI")] public UXButton btn;
|
[Header("UI")] public UXButton btn;
|
||||||
@ -29,6 +23,7 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
private IDisposable prepareSub;
|
private IDisposable prepareSub;
|
||||||
private IDisposable applySub;
|
private IDisposable applySub;
|
||||||
private IDisposable rebindEndSub;
|
private IDisposable rebindEndSub;
|
||||||
|
private IDisposable conflictSub;
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
@ -38,6 +33,7 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
|
|
||||||
if (InputBindingManager.Instance != null)
|
if (InputBindingManager.Instance != null)
|
||||||
{
|
{
|
||||||
|
// Subscribe to prepare events - already filtered by IsTargetContext
|
||||||
prepareSub = InputBindingManager.Instance.OnRebindPrepare.Subscribe(ctx =>
|
prepareSub = InputBindingManager.Instance.OnRebindPrepare.Subscribe(ctx =>
|
||||||
{
|
{
|
||||||
if (IsTargetContext(ctx))
|
if (IsTargetContext(ctx))
|
||||||
@ -48,8 +44,44 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
applySub = InputBindingManager.Instance.OnApply.Subscribe(_ => UpdateBindingText());
|
// Subscribe to apply events - only update if this instance's binding was applied or discarded
|
||||||
rebindEndSub = InputBindingManager.Instance.OnRebindEnd.Subscribe(_ => UpdateBindingText());
|
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();
|
prepareSub?.Dispose();
|
||||||
applySub?.Dispose();
|
applySub?.Dispose();
|
||||||
rebindEndSub?.Dispose();
|
rebindEndSub?.Dispose();
|
||||||
|
conflictSub?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDeviceChanged(InputDeviceWatcher.InputDeviceCategory _)
|
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;
|
if (ctx == null || ctx.action == null) return false;
|
||||||
var action = GetAction();
|
var action = GetAction();
|
||||||
if (action == null) return false;
|
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()
|
private void OnBtnClicked()
|
||||||
@ -98,7 +149,6 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var task = InputBindingManager.ConfirmApply();
|
var task = InputBindingManager.ConfirmApply();
|
||||||
if (task == null) return false;
|
|
||||||
return await task;
|
return await task;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -111,7 +161,7 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
public void CancelPrepared()
|
public void CancelPrepared()
|
||||||
{
|
{
|
||||||
InputBindingManager.DiscardPrepared();
|
InputBindingManager.DiscardPrepared();
|
||||||
UpdateBindingText();
|
// UpdateBindingText will be called automatically via OnApply event
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBindingText()
|
private void UpdateBindingText()
|
||||||
@ -125,15 +175,15 @@ public class TestRebindScript : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string disp = GlyphService.GetBindingControlPath(action, InputDeviceWatcher.CurrentCategory);
|
bindKeyText.text = GlyphService.GetDisplayNameFromInputAction(action, compositePartName);
|
||||||
bindKeyText.text = GlyphService.GetDisplayNameFromInputAction(action);
|
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var deviceCat = InputDeviceWatcher.CurrentCategory;
|
var deviceCat = InputDeviceWatcher.CurrentCategory;
|
||||||
string controlPath = GlyphService.GetBindingControlPath(action, deviceCat);
|
InputActionReference refr=default;
|
||||||
if (!string.IsNullOrEmpty(controlPath) && GlyphService.TryGetUISpriteForActionPath(controlPath, deviceCat, out Sprite sprite))
|
// string controlPath = GlyphService.GetBindingControlPath(action, compositePartName, deviceCat);
|
||||||
|
if ( GlyphService.TryGetUISpriteForActionPath(action,compositePartName, deviceCat, out Sprite sprite))
|
||||||
{
|
{
|
||||||
if (targetImage != null) targetImage.sprite = sprite;
|
if (targetImage != null) targetImage.sprite = sprite;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -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;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit 91cd9d2981e046ce0b03e3bbbd6c6a0294cd1150
|
Subproject commit 90291aa4a969a022e7a02d855748d55af03828b0
|
||||||
@ -1 +1 @@
|
|||||||
Subproject commit 0a5ef9135ccb240227cf655e2251a63bf2d5da7a
|
Subproject commit 74318889a2a1f3d93d1dbd7e35b610fce575043e
|
||||||
@ -1 +1 @@
|
|||||||
Subproject commit f92e91920dc4c31b41e36e3fa40058d4e6aace34
|
Subproject commit b3f3f268bf27686916f963b219ffda4e8392b914
|
||||||
@ -30,14 +30,14 @@ EditorUserSettings:
|
|||||||
value: 56060350000d5b5a5908597a48255a44174e4d797a7d7e6475794f61e7b3643e
|
value: 56060350000d5b5a5908597a48255a44174e4d797a7d7e6475794f61e7b3643e
|
||||||
flags: 0
|
flags: 0
|
||||||
RecentlyUsedSceneGuid-7:
|
RecentlyUsedSceneGuid-7:
|
||||||
value: 50500404540c580d0f0b5e7543725b44424f4c7a7b7c7734747e4f36e4b1676d
|
|
||||||
flags: 0
|
|
||||||
RecentlyUsedSceneGuid-8:
|
|
||||||
value: 015450045700505d0f0a5f2313260a444e164b2e757b76652c2d4d32bab0313a
|
value: 015450045700505d0f0a5f2313260a444e164b2e757b76652c2d4d32bab0313a
|
||||||
flags: 0
|
flags: 0
|
||||||
RecentlyUsedSceneGuid-9:
|
RecentlyUsedSceneGuid-8:
|
||||||
value: 5a07065703500c59585e0e7748770d44444f4a737d2d7f35787d4f63e0b26668
|
value: 5a07065703500c59585e0e7748770d44444f4a737d2d7f35787d4f63e0b26668
|
||||||
flags: 0
|
flags: 0
|
||||||
|
RecentlyUsedSceneGuid-9:
|
||||||
|
value: 50500404540c580d0f0b5e7543725b44424f4c7a7b7c7734747e4f36e4b1676d
|
||||||
|
flags: 0
|
||||||
vcSharedLogLevel:
|
vcSharedLogLevel:
|
||||||
value: 0d5e400f0650
|
value: 0d5e400f0650
|
||||||
flags: 0
|
flags: 0
|
||||||
|
|||||||
@ -19,7 +19,7 @@ MonoBehaviour:
|
|||||||
width: 1920
|
width: 1920
|
||||||
height: 997
|
height: 997
|
||||||
m_ShowMode: 4
|
m_ShowMode: 4
|
||||||
m_Title: Project
|
m_Title: Console
|
||||||
m_RootView: {fileID: 4}
|
m_RootView: {fileID: 4}
|
||||||
m_MinSize: {x: 875, y: 300}
|
m_MinSize: {x: 875, y: 300}
|
||||||
m_MaxSize: {x: 10000, y: 10000}
|
m_MaxSize: {x: 10000, y: 10000}
|
||||||
@ -41,7 +41,7 @@ MonoBehaviour:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 0
|
x: 0
|
||||||
y: 696
|
y: 696
|
||||||
width: 421
|
width: 266
|
||||||
height: 251
|
height: 251
|
||||||
m_MinSize: {x: 51, y: 71}
|
m_MinSize: {x: 51, y: 71}
|
||||||
m_MaxSize: {x: 4001, y: 4021}
|
m_MaxSize: {x: 4001, y: 4021}
|
||||||
@ -70,7 +70,7 @@ MonoBehaviour:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 0
|
x: 0
|
||||||
y: 0
|
y: 0
|
||||||
width: 421
|
width: 266
|
||||||
height: 947
|
height: 947
|
||||||
m_MinSize: {x: 100, y: 100}
|
m_MinSize: {x: 100, y: 100}
|
||||||
m_MaxSize: {x: 8096, y: 16192}
|
m_MaxSize: {x: 8096, y: 16192}
|
||||||
@ -174,7 +174,7 @@ MonoBehaviour:
|
|||||||
m_MinSize: {x: 400, y: 100}
|
m_MinSize: {x: 400, y: 100}
|
||||||
m_MaxSize: {x: 32384, y: 16192}
|
m_MaxSize: {x: 32384, y: 16192}
|
||||||
vertical: 0
|
vertical: 0
|
||||||
controlID: 69
|
controlID: 24
|
||||||
draggingID: 0
|
draggingID: 0
|
||||||
--- !u!114 &8
|
--- !u!114 &8
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
@ -193,7 +193,7 @@ MonoBehaviour:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 0
|
x: 0
|
||||||
y: 0
|
y: 0
|
||||||
width: 421
|
width: 266
|
||||||
height: 696
|
height: 696
|
||||||
m_MinSize: {x: 201, y: 221}
|
m_MinSize: {x: 201, y: 221}
|
||||||
m_MaxSize: {x: 4001, y: 4021}
|
m_MaxSize: {x: 4001, y: 4021}
|
||||||
@ -219,9 +219,9 @@ MonoBehaviour:
|
|||||||
- {fileID: 11}
|
- {fileID: 11}
|
||||||
m_Position:
|
m_Position:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 421
|
x: 266
|
||||||
y: 0
|
y: 0
|
||||||
width: 284
|
width: 388
|
||||||
height: 947
|
height: 947
|
||||||
m_MinSize: {x: 100, y: 100}
|
m_MinSize: {x: 100, y: 100}
|
||||||
m_MaxSize: {x: 8096, y: 16192}
|
m_MaxSize: {x: 8096, y: 16192}
|
||||||
@ -245,8 +245,8 @@ MonoBehaviour:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 0
|
x: 0
|
||||||
y: 0
|
y: 0
|
||||||
width: 284
|
width: 388
|
||||||
height: 249
|
height: 409
|
||||||
m_MinSize: {x: 202, y: 221}
|
m_MinSize: {x: 202, y: 221}
|
||||||
m_MaxSize: {x: 4002, y: 4021}
|
m_MaxSize: {x: 4002, y: 4021}
|
||||||
m_ActualView: {fileID: 17}
|
m_ActualView: {fileID: 17}
|
||||||
@ -270,9 +270,9 @@ MonoBehaviour:
|
|||||||
m_Position:
|
m_Position:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 0
|
x: 0
|
||||||
y: 249
|
y: 409
|
||||||
width: 284
|
width: 388
|
||||||
height: 698
|
height: 538
|
||||||
m_MinSize: {x: 102, y: 121}
|
m_MinSize: {x: 102, y: 121}
|
||||||
m_MaxSize: {x: 4002, y: 4021}
|
m_MaxSize: {x: 4002, y: 4021}
|
||||||
m_ActualView: {fileID: 18}
|
m_ActualView: {fileID: 18}
|
||||||
@ -295,9 +295,9 @@ MonoBehaviour:
|
|||||||
m_Children: []
|
m_Children: []
|
||||||
m_Position:
|
m_Position:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 705
|
x: 654
|
||||||
y: 0
|
y: 0
|
||||||
width: 431
|
width: 479
|
||||||
height: 947
|
height: 947
|
||||||
m_MinSize: {x: 232, y: 271}
|
m_MinSize: {x: 232, y: 271}
|
||||||
m_MaxSize: {x: 10002, y: 10021}
|
m_MaxSize: {x: 10002, y: 10021}
|
||||||
@ -321,9 +321,9 @@ MonoBehaviour:
|
|||||||
m_Children: []
|
m_Children: []
|
||||||
m_Position:
|
m_Position:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 1136
|
x: 1133
|
||||||
y: 0
|
y: 0
|
||||||
width: 784
|
width: 787
|
||||||
height: 947
|
height: 947
|
||||||
m_MinSize: {x: 276, y: 71}
|
m_MinSize: {x: 276, y: 71}
|
||||||
m_MaxSize: {x: 4001, y: 4021}
|
m_MaxSize: {x: 4001, y: 4021}
|
||||||
@ -354,7 +354,7 @@ MonoBehaviour:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 0
|
x: 0
|
||||||
y: 769
|
y: 769
|
||||||
width: 420
|
width: 265
|
||||||
height: 230
|
height: 230
|
||||||
m_SerializedDataModeController:
|
m_SerializedDataModeController:
|
||||||
m_DataMode: 0
|
m_DataMode: 0
|
||||||
@ -372,7 +372,7 @@ MonoBehaviour:
|
|||||||
m_ShowGizmos: 0
|
m_ShowGizmos: 0
|
||||||
m_TargetDisplay: 0
|
m_TargetDisplay: 0
|
||||||
m_ClearColor: {r: 0, g: 0, b: 0, a: 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_TextureFilterMode: 0
|
||||||
m_TextureHideFlags: 61
|
m_TextureHideFlags: 61
|
||||||
m_RenderIMGUI: 1
|
m_RenderIMGUI: 1
|
||||||
@ -387,8 +387,8 @@ MonoBehaviour:
|
|||||||
m_VRangeLocked: 0
|
m_VRangeLocked: 0
|
||||||
hZoomLockedByDefault: 0
|
hZoomLockedByDefault: 0
|
||||||
vZoomLockedByDefault: 0
|
vZoomLockedByDefault: 0
|
||||||
m_HBaseRangeMin: -210
|
m_HBaseRangeMin: -132.5
|
||||||
m_HBaseRangeMax: 210
|
m_HBaseRangeMax: 132.5
|
||||||
m_VBaseRangeMin: -104.5
|
m_VBaseRangeMin: -104.5
|
||||||
m_VBaseRangeMax: 104.5
|
m_VBaseRangeMax: 104.5
|
||||||
m_HAllowExceedBaseRangeMin: 1
|
m_HAllowExceedBaseRangeMin: 1
|
||||||
@ -408,23 +408,23 @@ MonoBehaviour:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 0
|
x: 0
|
||||||
y: 21
|
y: 21
|
||||||
width: 420
|
width: 265
|
||||||
height: 209
|
height: 209
|
||||||
m_Scale: {x: 1, y: 1}
|
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_MarginLeft: 0
|
||||||
m_MarginRight: 0
|
m_MarginRight: 0
|
||||||
m_MarginTop: 0
|
m_MarginTop: 0
|
||||||
m_MarginBottom: 0
|
m_MarginBottom: 0
|
||||||
m_LastShownAreaInsideMargins:
|
m_LastShownAreaInsideMargins:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: -210
|
x: -132.5
|
||||||
y: -104.5
|
y: -104.5
|
||||||
width: 420
|
width: 265
|
||||||
height: 209
|
height: 209
|
||||||
m_MinimalGUI: 1
|
m_MinimalGUI: 1
|
||||||
m_defaultScale: 1
|
m_defaultScale: 1
|
||||||
m_LastWindowPixelSize: {x: 420, y: 230}
|
m_LastWindowPixelSize: {x: 265, y: 230}
|
||||||
m_ClearInEditMode: 1
|
m_ClearInEditMode: 1
|
||||||
m_NoCameraWarning: 1
|
m_NoCameraWarning: 1
|
||||||
m_LowResolutionForAspectRatios: 01000000000000000000
|
m_LowResolutionForAspectRatios: 01000000000000000000
|
||||||
@ -522,7 +522,7 @@ MonoBehaviour:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 0
|
x: 0
|
||||||
y: 73
|
y: 73
|
||||||
width: 420
|
width: 265
|
||||||
height: 675
|
height: 675
|
||||||
m_SerializedDataModeController:
|
m_SerializedDataModeController:
|
||||||
m_DataMode: 0
|
m_DataMode: 0
|
||||||
@ -1163,10 +1163,10 @@ MonoBehaviour:
|
|||||||
m_Tooltip:
|
m_Tooltip:
|
||||||
m_Pos:
|
m_Pos:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 421
|
x: 266
|
||||||
y: 73
|
y: 73
|
||||||
width: 282
|
width: 386
|
||||||
height: 228
|
height: 388
|
||||||
m_SerializedDataModeController:
|
m_SerializedDataModeController:
|
||||||
m_DataMode: 0
|
m_DataMode: 0
|
||||||
m_PreferredDataMode: 0
|
m_PreferredDataMode: 0
|
||||||
@ -1180,9 +1180,9 @@ MonoBehaviour:
|
|||||||
m_SceneHierarchy:
|
m_SceneHierarchy:
|
||||||
m_TreeViewState:
|
m_TreeViewState:
|
||||||
scrollPos: {x: 0, y: 0}
|
scrollPos: {x: 0, y: 0}
|
||||||
m_SelectedIDs: 626f0000
|
m_SelectedIDs: 2e1c0000
|
||||||
m_LastClickedID: 0
|
m_LastClickedID: 0
|
||||||
m_ExpandedIDs: eefafffff6fafffff8faffffd66c0000
|
m_ExpandedIDs: 28fbffff
|
||||||
m_RenameOverlay:
|
m_RenameOverlay:
|
||||||
m_UserAcceptedRename: 0
|
m_UserAcceptedRename: 0
|
||||||
m_Name:
|
m_Name:
|
||||||
@ -1226,10 +1226,10 @@ MonoBehaviour:
|
|||||||
m_Tooltip:
|
m_Tooltip:
|
||||||
m_Pos:
|
m_Pos:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 421
|
x: 266
|
||||||
y: 322
|
y: 482
|
||||||
width: 282
|
width: 386
|
||||||
height: 677
|
height: 517
|
||||||
m_SerializedDataModeController:
|
m_SerializedDataModeController:
|
||||||
m_DataMode: 0
|
m_DataMode: 0
|
||||||
m_PreferredDataMode: 0
|
m_PreferredDataMode: 0
|
||||||
@ -1260,9 +1260,9 @@ MonoBehaviour:
|
|||||||
m_Tooltip:
|
m_Tooltip:
|
||||||
m_Pos:
|
m_Pos:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 705
|
x: 654
|
||||||
y: 73
|
y: 73
|
||||||
width: 429
|
width: 477
|
||||||
height: 926
|
height: 926
|
||||||
m_SerializedDataModeController:
|
m_SerializedDataModeController:
|
||||||
m_DataMode: 0
|
m_DataMode: 0
|
||||||
@ -1285,7 +1285,7 @@ MonoBehaviour:
|
|||||||
m_SkipHidden: 0
|
m_SkipHidden: 0
|
||||||
m_SearchArea: 2
|
m_SearchArea: 2
|
||||||
m_Folders:
|
m_Folders:
|
||||||
- Packages/com.alicizax.unity.ui.extension/Editor/Res/ComponentIcon
|
- Assets/Plugins/PrimeTween/Demo/Scripts/MeasureAllocations
|
||||||
m_Globs: []
|
m_Globs: []
|
||||||
m_OriginalText:
|
m_OriginalText:
|
||||||
m_ImportLogFlags: 0
|
m_ImportLogFlags: 0
|
||||||
@ -1301,7 +1301,7 @@ MonoBehaviour:
|
|||||||
scrollPos: {x: 0, y: 0}
|
scrollPos: {x: 0, y: 0}
|
||||||
m_SelectedIDs: e48c0000
|
m_SelectedIDs: e48c0000
|
||||||
m_LastClickedID: 36068
|
m_LastClickedID: 36068
|
||||||
m_ExpandedIDs: 00000000226f0000246f0000266f0000286f00002a6f00002c6f00002e6f0000306f0000326f0000346f0000366f0000386f00003a6f00003c6f00003e6f0000406f0000426f0000446f0000466f0000486f00004a6f00004c6f00004e6f0000506f0000526f0000546f0000566f0000586f00005a6f00005c6f00005e6f0000606f0000626f0000646f0000666f0000686f00006a6f0000
|
m_ExpandedIDs: 00000000860d0000746d0000766d0000786d00007a6d00007c6d00007e6d0000806d0000826d0000846d0000866d0000886d00008a6d00008c6d00008e6d0000906d0000926d0000946d0000966d0000986d00009a6d00009c6d00009e6d0000a06d0000a26d0000a46d0000a66d0000a86d0000aa6d0000ac6d0000ae6d0000b06d0000b26d0000b46d0000b66d0000b86d0000ba6d0000bc6d0000
|
||||||
m_RenameOverlay:
|
m_RenameOverlay:
|
||||||
m_UserAcceptedRename: 0
|
m_UserAcceptedRename: 0
|
||||||
m_Name:
|
m_Name:
|
||||||
@ -1326,10 +1326,10 @@ MonoBehaviour:
|
|||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_ResourceFile:
|
m_ResourceFile:
|
||||||
m_AssetTreeState:
|
m_AssetTreeState:
|
||||||
scrollPos: {x: 0, y: 720}
|
scrollPos: {x: 0, y: 528}
|
||||||
m_SelectedIDs:
|
m_SelectedIDs:
|
||||||
m_LastClickedID: 0
|
m_LastClickedID: 0
|
||||||
m_ExpandedIDs: ffffffff00000000226f0000246f0000266f0000286f00002a6f00002e6f0000306f0000326f0000346f0000366f0000386f00003a6f00003c6f00003e6f0000406f0000426f0000446f0000466f0000486f00004a6f00004e6f0000506f0000526f0000546f0000566f0000586f00005a6f00005c6f00005e6f0000606f0000626f0000646f0000666f0000686f00006a6f0000ffffff7f
|
m_ExpandedIDs: ffffffff00000000860d0000746d0000766d0000786d00007a6d00007c6d00007e6d0000806d0000826d0000846d0000866d0000886d00008a6d00008c6d00008e6d0000906d0000926d0000946d0000966d0000986d00009a6d00009c6d00009e6d0000a06d0000a26d0000a46d0000a66d0000a86d0000aa6d0000ac6d0000ae6d0000b06d0000b26d0000b46d0000b66d0000b86d0000ba6d0000bc6d0000c46f0000f47000001c710000
|
||||||
m_RenameOverlay:
|
m_RenameOverlay:
|
||||||
m_UserAcceptedRename: 0
|
m_UserAcceptedRename: 0
|
||||||
m_Name:
|
m_Name:
|
||||||
@ -1405,9 +1405,9 @@ MonoBehaviour:
|
|||||||
m_Tooltip:
|
m_Tooltip:
|
||||||
m_Pos:
|
m_Pos:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
x: 1136
|
x: 1133
|
||||||
y: 73
|
y: 73
|
||||||
width: 783
|
width: 786
|
||||||
height: 926
|
height: 926
|
||||||
m_SerializedDataModeController:
|
m_SerializedDataModeController:
|
||||||
m_DataMode: 0
|
m_DataMode: 0
|
||||||
@ -1426,7 +1426,7 @@ MonoBehaviour:
|
|||||||
m_ControlHash: 1412526313
|
m_ControlHash: 1412526313
|
||||||
m_PrefName: Preview_InspectorPreview
|
m_PrefName: Preview_InspectorPreview
|
||||||
m_LastInspectedObjectInstanceID: -1
|
m_LastInspectedObjectInstanceID: -1
|
||||||
m_LastVerticalScrollValue: 432
|
m_LastVerticalScrollValue: 0
|
||||||
m_GlobalObjectId:
|
m_GlobalObjectId:
|
||||||
m_InspectorMode: 0
|
m_InspectorMode: 0
|
||||||
m_LockTracker:
|
m_LockTracker:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user