816 lines
33 KiB
C#
816 lines
33 KiB
C#
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 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
|
||
{
|
||
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;
|
||
}
|
||
}
|