2025-12-05 19:04:53 +08:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using TMPro;
|
|
|
|
|
using UnityEngine;
|
2025-12-05 20:57:29 +08:00
|
|
|
using UnityEngine.InputSystem;
|
2025-12-17 20:03:29 +08:00
|
|
|
using UnityEngine.U2D;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
public sealed class GlyphEntry
|
|
|
|
|
{
|
|
|
|
|
public Sprite Sprite;
|
|
|
|
|
public InputAction action;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
public sealed class DeviceGlyphTable
|
2025-12-05 19:04:53 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
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()
|
2025-12-05 19:04:53 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
BuildCache();
|
2025-12-05 19:04:53 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
private void BuildCache()
|
2025-12-05 19:04:53 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
if (_tableCache == null)
|
|
|
|
|
{
|
|
|
|
|
_tableCache = new Dictionary<string, DeviceGlyphTable>(tables.Count);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_tableCache.Clear();
|
|
|
|
|
}
|
2025-12-17 20:03:29 +08:00
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
if (_spriteCache == null)
|
|
|
|
|
{
|
|
|
|
|
_spriteCache = new Dictionary<(string, InputDeviceWatcher.InputDeviceCategory), Sprite>();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_spriteCache.Clear();
|
|
|
|
|
}
|
2025-12-17 20:03:29 +08:00
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
for (int i = 0; i < tables.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var table = tables[i];
|
|
|
|
|
if (table != null && !string.IsNullOrEmpty(table.deviceName))
|
|
|
|
|
{
|
|
|
|
|
_tableCache[table.deviceName.ToLowerInvariant()] = table;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-17 20:03:29 +08:00
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
public DeviceGlyphTable GetTable(string deviceName)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(deviceName)) return null;
|
|
|
|
|
if (tables == null) return null;
|
2025-12-17 20:03:29 +08:00
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
// Ensure cache is built
|
|
|
|
|
if (_tableCache == null || _tableCache.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
BuildCache();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use cache for O(1) lookup
|
|
|
|
|
if (_tableCache.TryGetValue(deviceName.ToLowerInvariant(), out var table))
|
|
|
|
|
{
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Sprite GetPlatformIcon(InputDeviceWatcher.InputDeviceCategory device)
|
|
|
|
|
{
|
|
|
|
|
var table = GetTable(device);
|
|
|
|
|
if (table == null) return null;
|
|
|
|
|
return table.platformIcons;
|
2025-12-05 19:04:53 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
public DeviceGlyphTable GetTable(InputDeviceWatcher.InputDeviceCategory device)
|
2025-12-05 19:04:53 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
// Use constants to avoid string allocations
|
|
|
|
|
string name;
|
|
|
|
|
switch (device)
|
|
|
|
|
{
|
|
|
|
|
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
|
|
|
|
name = DEVICE_KEYBOARD;
|
|
|
|
|
break;
|
|
|
|
|
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
|
|
|
|
name = DEVICE_XBOX;
|
|
|
|
|
break;
|
|
|
|
|
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
|
|
|
|
name = DEVICE_PLAYSTATION;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
name = DEVICE_XBOX;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
return GetTable(name);
|
|
|
|
|
}
|
2025-12-10 17:38:31 +08:00
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
public Sprite FindSprite(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(controlPath))
|
2025-12-05 19:04:53 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
return placeholderSprite;
|
2025-12-05 19:04:53 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
// Check cache first
|
|
|
|
|
var cacheKey = (controlPath, device);
|
|
|
|
|
if (_spriteCache != null && _spriteCache.TryGetValue(cacheKey, out var cachedSprite))
|
2025-12-10 17:38:31 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
return cachedSprite ?? placeholderSprite;
|
2025-12-10 17:38:31 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
var entry = FindEntryByControlPath(controlPath, device);
|
|
|
|
|
var sprite = entry?.Sprite ?? placeholderSprite;
|
|
|
|
|
|
|
|
|
|
// Cache the result (including null results to avoid repeated lookups)
|
|
|
|
|
if (_spriteCache != null)
|
2025-12-10 17:38:31 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
_spriteCache[cacheKey] = sprite;
|
2025-12-10 17:38:31 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 20:38:15 +08:00
|
|
|
return sprite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public GlyphEntry FindEntryByControlPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device)
|
|
|
|
|
{
|
|
|
|
|
var t = GetTable(device);
|
|
|
|
|
if (t != null && t.entries != null)
|
2025-12-05 19:04:53 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
for (int i = 0; i < t.entries.Count; ++i)
|
2025-12-05 19:04:53 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
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++)
|
2025-12-05 19:04:53 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
var b = bindings[j];
|
|
|
|
|
if (!string.IsNullOrEmpty(b.path) && string.Equals(b.path, controlPath, StringComparison.OrdinalIgnoreCase))
|
2025-12-10 17:38:31 +08:00
|
|
|
{
|
2026-03-09 20:38:15 +08:00
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(b.effectivePath) && string.Equals(b.effectivePath, controlPath, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
return e;
|
2025-12-10 17:38:31 +08:00
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 20:38:15 +08:00
|
|
|
|
|
|
|
|
return null;
|
2025-12-05 19:04:53 +08:00
|
|
|
}
|
|
|
|
|
}
|