AlicizaX/Client/Assets/InputGlyph/InputGlyphDatabaseEditor.cs
2025-12-17 20:03:29 +08:00

816 lines
33 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using AlicizaX.InputGlyph;
using UnityEditor;
using UnityEngine;
using UnityEngine.U2D;
using TMPro;
[CustomEditor(typeof(InputGlyphDatabase))]
public class InputGlyphDatabaseEditor : Editor
{
SerializedProperty tablesProp;
SerializedProperty placeholderSpriteProp;
InputGlyphDatabase db;
int tabIndex = 0;
bool showAddField = false;
string newTableName = "";
List<string> searchStrings = new List<string>();
List<int> currentPages = new List<int>();
const int itemsPerPage = 10;
const int previewSize = 52;
void OnEnable()
{
db = target as InputGlyphDatabase;
tablesProp = serializedObject.FindProperty("tables");
placeholderSpriteProp = serializedObject.FindProperty("placeholderSprite");
if (tablesProp == null)
{
Debug.LogError("Could not find serialized property 'tables' on InputGlyphDatabase. Check field name.");
return;
}
EnsureDefaultTable("Keyboard");
EnsureDefaultTable("Xbox");
EnsureDefaultTable("PlayStation");
SyncEditorListsWithTables();
}
public override void OnInspectorGUI()
{
serializedObject.Update();
if (db == null || tablesProp == null) return;
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.Space(4);
if (GUILayout.Button("Save Asset", EditorStyles.toolbarButton))
{
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(db);
AssetDatabase.SaveAssets();
}
GUILayout.FlexibleSpace();
if (GUILayout.Button(showAddField ? "Cancel +" : "+ Add Table", EditorStyles.toolbarButton, GUILayout.Width(110)))
{
showAddField = !showAddField;
newTableName = "";
}
int settingsIndex = tablesProp != null ? tablesProp.arraySize : 0;
bool settingsSelected = (tabIndex == settingsIndex);
if (GUILayout.Toggle(settingsSelected, "Settings", EditorStyles.toolbarButton, GUILayout.Width(90)) != settingsSelected)
{
tabIndex = (tabIndex == settingsIndex) ? 0 : settingsIndex;
}
EditorGUILayout.EndHorizontal();
if (showAddField)
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.Label("Name:", GUILayout.Width(40));
newTableName = EditorGUILayout.TextField(newTableName);
if (GUILayout.Button("Add", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
string trimmed = newTableName != null ? newTableName.Trim() : "";
if (string.IsNullOrEmpty(trimmed))
{
EditorUtility.DisplayDialog("Invalid Name", "Table name cannot be empty.", "OK");
}
else
{
bool exists = false;
for (int i = 0; i < tablesProp.arraySize; ++i)
{
var t = tablesProp.GetArrayElementAtIndex(i);
var nameProp = t.FindPropertyRelative("deviceName");
if (nameProp != null && string.Equals(nameProp.stringValue, trimmed, StringComparison.OrdinalIgnoreCase))
{
exists = true; break;
}
}
if (exists)
{
EditorUtility.DisplayDialog("Duplicate", "A table with that name already exists.", "OK");
}
else
{
int newIndex = tablesProp.arraySize;
tablesProp.InsertArrayElementAtIndex(newIndex);
var newTable = tablesProp.GetArrayElementAtIndex(newIndex);
var nameProp = newTable.FindPropertyRelative("deviceName");
if (nameProp != null) nameProp.stringValue = trimmed;
var tmpAssetProp = newTable.FindPropertyRelative("tmpAsset");
if (tmpAssetProp != null) tmpAssetProp.objectReferenceValue = null;
var atlasProp = newTable.FindPropertyRelative("spriteAtlas");
if (atlasProp != null) atlasProp.objectReferenceValue = null;
var sheetProp = newTable.FindPropertyRelative("spriteSheetTexture");
if (sheetProp != null) sheetProp.objectReferenceValue = null;
var entriesProp = newTable.FindPropertyRelative("entries");
if (entriesProp != null) entriesProp.arraySize = 0;
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(db);
SyncEditorListsWithTables();
showAddField = false;
tabIndex = tablesProp.arraySize - 1;
}
}
}
if (GUILayout.Button("Cancel", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
showAddField = false;
newTableName = "";
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.Space(6);
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
int tablesCount = tablesProp.arraySize;
for (int i = 0; i < tablesCount; ++i)
{
var t = tablesProp.GetArrayElementAtIndex(i);
var nameProp = t.FindPropertyRelative("deviceName");
string name = nameProp != null ? nameProp.stringValue : ("Table " + i);
bool selected = (tabIndex == i);
if (GUILayout.Toggle(selected, name, EditorStyles.toolbarButton, GUILayout.MinWidth(60)))
{
tabIndex = i;
}
if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(22)))
{
if (EditorUtility.DisplayDialog("Delete Table?",
$"Delete table '{name}' and all its entries? This cannot be undone.", "Delete", "Cancel"))
{
tablesProp.DeleteArrayElementAtIndex(i);
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(db);
SyncEditorListsWithTables();
tabIndex = Mathf.Clamp(tabIndex, 0, Math.Max(0, tablesProp.arraySize - 1));
return;
}
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(8);
EditorGUILayout.BeginVertical("box");
if (tabIndex == tablesProp.arraySize)
{
// Settings
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(placeholderSpriteProp, new GUIContent("Placeholder Sprite"));
Sprite placeholder = placeholderSpriteProp.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 placeholder sprite assigned. If FindEntryByControlPath receives an empty path, it will return null.", MessageType.Info);
}
}
else
{
if (tabIndex < 0 || tabIndex >= tablesProp.arraySize)
{
EditorGUILayout.HelpBox("Invalid table index.", MessageType.Error);
}
else
{
var tableProp = tablesProp.GetArrayElementAtIndex(tabIndex);
EnsureEditorListsLength();
GUILayout.BeginHorizontal();
GUIStyle searchStyle = EditorStyles.toolbarSearchField ?? EditorStyles.textField;
searchStrings[tabIndex] = GUILayout.TextField(searchStrings[tabIndex] ?? "", searchStyle);
GUILayout.EndHorizontal();
EditorGUILayout.Space(6);
// TMP Asset row
var tmpAssetProp = tableProp.FindPropertyRelative("tmpAsset");
EditorGUILayout.BeginHorizontal();
GUILayout.Label("TMP Sprite Asset", GUILayout.Width(140));
EditorGUILayout.PropertyField(tmpAssetProp, GUIContent.none, GUILayout.ExpandWidth(true));
if (GUILayout.Button("Parse TMP Asset", GUILayout.Width(120)))
{
ParseTMPAssetIntoTableSerialized(tableProp);
}
if (GUILayout.Button("Clear", GUILayout.Width(80)))
{
var entriesProp = tableProp.FindPropertyRelative("entries");
if (entriesProp != null) entriesProp.arraySize = 0;
var nameProp = tableProp.FindPropertyRelative("deviceName");
if (nameProp != null && db != null)
{
var deviceName = nameProp.stringValue;
var table = db.GetTable(deviceName);
if (table != null) table.entries.Clear();
}
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(db);
currentPages[tabIndex] = 0;
}
EditorGUILayout.EndHorizontal();
// SpriteAtlas row
var atlasProp = tableProp.FindPropertyRelative("spriteAtlas");
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Sprite Atlas", GUILayout.Width(140));
EditorGUILayout.PropertyField(atlasProp, GUIContent.none, GUILayout.ExpandWidth(true));
if (GUILayout.Button("Parse Sprite Atlas", GUILayout.Width(120)))
{
ParseSpriteAtlasIntoTableSerialized(tableProp);
}
if (GUILayout.Button("Clear", GUILayout.Width(80)))
{
var entriesProp = tableProp.FindPropertyRelative("entries");
if (entriesProp != null) entriesProp.arraySize = 0;
var nameProp = tableProp.FindPropertyRelative("deviceName");
if (nameProp != null && db != null)
{
var deviceName = nameProp.stringValue;
var table = db.GetTable(deviceName);
if (table != null) table.entries.Clear();
}
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(db);
currentPages[tabIndex] = 0;
}
EditorGUILayout.EndHorizontal();
// SpriteSheet (Texture2D with Multiple) row
var sheetProp = tableProp.FindPropertyRelative("spriteSheetTexture");
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Sprite Sheet (Texture2D)", GUILayout.Width(140));
EditorGUILayout.PropertyField(sheetProp, GUIContent.none, GUILayout.ExpandWidth(true));
if (GUILayout.Button("Parse Sprite Sheet", GUILayout.Width(120)))
{
ParseSpriteSheetIntoTableSerialized(tableProp);
}
if (GUILayout.Button("Clear", GUILayout.Width(80)))
{
var entriesProp = tableProp.FindPropertyRelative("entries");
if (entriesProp != null) entriesProp.arraySize = 0;
var nameProp = tableProp.FindPropertyRelative("deviceName");
if (nameProp != null && db != null)
{
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();
EditorGUILayout.Space(6);
var entries = tableProp.FindPropertyRelative("entries");
if (entries != null)
{
int total = entries.arraySize;
List<int> matchedIndices = new List<int>();
string query = (searchStrings[tabIndex] ?? "").Trim();
for (int i = 0; i < total; ++i)
{
var eProp = entries.GetArrayElementAtIndex(i);
if (eProp == null) continue;
var spriteProp = eProp.FindPropertyRelative("Sprite");
Sprite s = spriteProp.objectReferenceValue as Sprite;
string name = s != null ? s.name : "";
if (string.IsNullOrEmpty(query) || name.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)
{
matchedIndices.Add(i);
}
}
int matchedTotal = matchedIndices.Count;
int totalPages = Mathf.Max(1, (matchedTotal + itemsPerPage - 1) / itemsPerPage);
currentPages[tabIndex] = Mathf.Clamp(currentPages[tabIndex], 0, totalPages - 1);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("<<", EditorStyles.miniButtonLeft, GUILayout.Width(36))) { currentPages[tabIndex] = 0; }
if (GUILayout.Button("<", EditorStyles.miniButtonMid, GUILayout.Width(36))) { currentPages[tabIndex] = Mathf.Max(0, currentPages[tabIndex] - 1); }
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField(string.Format("Page {0}/{1}", currentPages[tabIndex] + 1, totalPages), GUILayout.Width(120));
GUILayout.FlexibleSpace();
if (GUILayout.Button(">", EditorStyles.miniButtonMid, GUILayout.Width(36))) { currentPages[tabIndex] = Mathf.Min(totalPages - 1, currentPages[tabIndex] + 1); }
if (GUILayout.Button(">>", EditorStyles.miniButtonRight, GUILayout.Width(36))) { currentPages[tabIndex] = totalPages - 1; }
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(4);
int start = currentPages[tabIndex] * itemsPerPage;
int end = Math.Min(start + itemsPerPage, matchedTotal);
for (int mi = start; mi < end; ++mi)
{
int i = matchedIndices[mi];
var eProp = entries.GetArrayElementAtIndex(i);
if (eProp == null) continue;
using (new EditorGUILayout.HorizontalScope("box"))
{
using (new EditorGUILayout.VerticalScope(GUILayout.Width(80)))
{
var spriteProp = eProp.FindPropertyRelative("Sprite");
Sprite s = spriteProp.objectReferenceValue as Sprite;
EditorGUILayout.LabelField(s != null ? s.name : "<missing>", EditorStyles.boldLabel);
if (s != null)
{
Texture2D preview = AssetPreview.GetAssetPreview(s);
if (preview == null) preview = AssetPreview.GetMiniThumbnail(s);
if (preview != null)
{
GUILayout.Label(preview, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
}
else
{
EditorGUILayout.PropertyField(spriteProp, GUIContent.none, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
}
}
else
{
EditorGUILayout.PropertyField(spriteProp, GUIContent.none, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
}
}
EditorGUILayout.BeginVertical();
var actionProp = eProp.FindPropertyRelative("action");
EditorGUILayout.Space(2);
EditorGUILayout.PropertyField(actionProp, GUIContent.none, GUILayout.ExpandWidth(true));
EditorGUILayout.EndVertical();
}
EditorGUILayout.Space(4);
}
if (matchedTotal == 0)
{
EditorGUILayout.HelpBox("No entries match the search.", MessageType.Info);
}
}
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(6);
serializedObject.ApplyModifiedProperties();
}
void EnsureDefaultTable(string name)
{
if (tablesProp == null) return;
for (int i = 0; i < tablesProp.arraySize; ++i)
{
var t = tablesProp.GetArrayElementAtIndex(i);
var nameProp = t.FindPropertyRelative("deviceName");
if (nameProp != null && string.Equals(nameProp.stringValue, name, StringComparison.OrdinalIgnoreCase))
return;
}
int idx = tablesProp.arraySize;
tablesProp.InsertArrayElementAtIndex(idx);
var newTable = tablesProp.GetArrayElementAtIndex(idx);
var deviceNameProp = newTable.FindPropertyRelative("deviceName");
if (deviceNameProp != null) deviceNameProp.stringValue = name;
var tmpAssetProp = newTable.FindPropertyRelative("tmpAsset");
if (tmpAssetProp != null) tmpAssetProp.objectReferenceValue = null;
var atlasProp = newTable.FindPropertyRelative("spriteAtlas");
if (atlasProp != null) atlasProp.objectReferenceValue = null;
var sheetProp = newTable.FindPropertyRelative("spriteSheetTexture");
if (sheetProp != null) sheetProp.objectReferenceValue = null;
var entriesProp = newTable.FindPropertyRelative("entries");
if (entriesProp != null) entriesProp.arraySize = 0;
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(db);
}
void SyncEditorListsWithTables()
{
int count = tablesProp != null ? tablesProp.arraySize : 0;
if (searchStrings == null) searchStrings = new List<string>();
if (currentPages == null) currentPages = new List<int>();
while (searchStrings.Count < count) searchStrings.Add("");
while (currentPages.Count < count) currentPages.Add(0);
while (searchStrings.Count > count) searchStrings.RemoveAt(searchStrings.Count - 1);
while (currentPages.Count > count) currentPages.RemoveAt(currentPages.Count - 1);
}
void EnsureEditorListsLength()
{
if (tablesProp == null) return;
SyncEditorListsWithTables();
}
// ----- Parse TMP SpriteAsset增强版 -----
void ParseTMPAssetIntoTableSerialized(SerializedProperty tableProp)
{
if (tableProp == null) return;
var tmpAssetProp = tableProp.FindPropertyRelative("tmpAsset");
var asset = tmpAssetProp.objectReferenceValue as TMP_SpriteAsset;
if (asset == null)
{
Debug.LogWarning("[InputGlyphDatabase] TMP Sprite Asset 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();
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
{
string[] scoped = AssetDatabase.FindAssets($"\"{name}\" t:Sprite", new[] { assetFolder });
if (scoped != null && scoped.Length > 0)
{
foreach (var g in scoped)
{
var p = AssetDatabase.GUIDToAssetPath(g);
var sp = AssetDatabase.LoadAssetAtPath<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 SpriteAtlasUnity 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
{
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)
{
Debug.LogError("[InputGlyphDatabase] Exception while scanning sprites for atlas: " + ex);
}
// 按名字逐字符排序(不区分大小写)
tableObj.entries.Sort((a, b) => CompareSpriteNames(a?.Sprite?.name, b?.Sprite?.name));
EditorUtility.SetDirty(db);
serializedObject.Update();
serializedObject.ApplyModifiedProperties();
AssetDatabase.SaveAssets();
Debug.Log($"[InputGlyphDatabase] Parsed SpriteAtlas '{atlas.name}' into table '{deviceName}'. foundSprites={added}");
}
// ----- Parse Sprite Sheet (Texture2D with Multiple) -----
void ParseSpriteSheetIntoTableSerialized(SerializedProperty tableProp)
{
if (tableProp == null) return;
var sheetProp = tableProp.FindPropertyRelative("spriteSheetTexture");
var tex = sheetProp != null ? sheetProp.objectReferenceValue as Texture2D : null;
if (tex == null)
{
Debug.LogWarning("[InputGlyphDatabase] spriteSheetTexture 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 path = AssetDatabase.GetAssetPath(tex);
if (string.IsNullOrEmpty(path))
{
Debug.LogWarning("[InputGlyphDatabase] Could not get asset path for texture.");
return;
}
var assets = AssetDatabase.LoadAllAssetsAtPath(path);
if (assets == null || assets.Length == 0)
{
Debug.LogWarning("[InputGlyphDatabase] No sub-assets found at path: " + path);
return;
}
List<Sprite> sprites = new List<Sprite>();
foreach (var a in assets)
{
if (a is Sprite sp)
{
sprites.Add(sp);
}
}
// 之前按视觉位置排序,改为先按名字逐字符排序(不区分大小写)
sprites.Sort((a, b) => CompareSpriteNames(a?.name, b?.name));
foreach (var sp in sprites)
{
GlyphEntry e = new GlyphEntry();
e.Sprite = sp;
e.action = null;
tableObj.entries.Add(e);
}
// 额外在 runtime list 里也用相同排序(上面已经排序过 sprites
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 sprite sheet '{tex.name}' into table '{deviceName}'. foundSprites={sprites.Count}");
}
int MapSerializedTableToRuntimeIndex(string deviceName)
{
if (db == null || db.tables == null) return -1;
for (int ti = 0; ti < db.tables.Count; ++ti)
{
if (string.Equals(db.tables[ti].deviceName, deviceName, StringComparison.OrdinalIgnoreCase))
return ti;
}
return -1;
}
SpriteAtlas GetSpriteAtlasFromTMP(TMP_SpriteAsset asset)
{
if (asset == null) return null;
var t = asset.GetType();
foreach (var f in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (typeof(SpriteAtlas).IsAssignableFrom(f.FieldType))
{
var val = f.GetValue(asset) as SpriteAtlas;
if (val != null) return val;
}
}
foreach (var p in t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (typeof(SpriteAtlas).IsAssignableFrom(p.PropertyType))
{
try
{
var val = p.GetValue(asset, null) as SpriteAtlas;
if (val != null) return val;
}
catch { }
}
}
return null;
}
int CompareSpriteNames(string a, string b)
{
// normalize null/empty
bool aEmpty = string.IsNullOrEmpty(a);
bool bEmpty = string.IsNullOrEmpty(b);
if (aEmpty && bEmpty) return 0;
if (aEmpty) return -1;
if (bEmpty) return 1;
int la = a.Length;
int lb = b.Length;
int n = Math.Min(la, lb);
for (int i = 0; i < n; ++i)
{
char ca = char.ToUpperInvariant(a[i]);
char cb = char.ToUpperInvariant(b[i]);
if (ca != cb) return ca - cb;
}
return la - lb;
}
}