Files
AIC-Project/Client/Assets/Script/Editor/XWorldUIFontInstaller.cs
T
2026-07-10 10:24:29 +08:00

270 lines
9.6 KiB
C#

using System.IO;
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;
using Object = UnityEngine.Object;
public static class XWorldUIFontInstaller
{
private const string SourceFontPath = "Assets/Game/Art/UI/Font/siyuan_CN_Bold.otf";
private const string TmpFontAssetPath = "Assets/Game/Art/UI/Font/siyuan_CN_Bold SDF.asset";
private const string TmpSettingsPath = "Assets/TextMesh Pro/Resources/TMP Settings.asset";
private static bool s_IsInstalling;
[InitializeOnLoadMethod]
private static void InstallDefaultFontOnLoad()
{
EditorApplication.delayCall += () =>
{
if (!File.Exists(SourceFontPath))
return;
InstallDefaultFont();
};
}
[MenuItem("XWorld/UI/Install Default Font", false, 702)]
public static void InstallDefaultFont()
{
if (s_IsInstalling)
return;
s_IsInstalling = true;
try
{
AssetDatabase.ImportAsset(SourceFontPath, ImportAssetOptions.ForceUpdate);
Font sourceFont = AssetDatabase.LoadAssetAtPath<Font>(SourceFontPath);
if (sourceFont == null)
{
Debug.LogError($"Default UI font source is missing: {SourceFontPath}");
return;
}
Type tmpFontAssetType = FindType("TMPro.TMP_FontAsset");
if (tmpFontAssetType == null)
{
Debug.LogError("Unable to find TMPro.TMP_FontAsset. Make sure TextMeshPro is installed.");
return;
}
Object fontAsset = AssetDatabase.LoadAssetAtPath(TmpFontAssetPath, tmpFontAssetType);
if (fontAsset == null)
fontAsset = CreateDynamicTmpFontAsset(sourceFont, tmpFontAssetType);
if (fontAsset == null)
return;
SetTmpDefaultFont(fontAsset);
ApplyFontToKnownPrefabs(fontAsset, tmpFontAssetType);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"Installed default UI font: {TmpFontAssetPath}");
}
finally
{
s_IsInstalling = false;
}
}
[MenuItem("XWorld/UI/Install Default Font", true)]
private static bool ValidateInstallDefaultFont()
{
return File.Exists(SourceFontPath);
}
private static Object CreateDynamicTmpFontAsset(Font sourceFont, Type tmpFontAssetType)
{
string directory = Path.GetDirectoryName(TmpFontAssetPath);
if (!string.IsNullOrEmpty(directory))
Directory.CreateDirectory(directory);
const int samplingPointSize = 90;
const int atlasPadding = 9;
const int atlasSize = 2048;
const GlyphRenderMode renderMode = GlyphRenderMode.SDFAA;
Type atlasPopulationModeType = FindType("TMPro.AtlasPopulationMode");
if (atlasPopulationModeType == null)
{
Debug.LogError("Unable to find TMPro.AtlasPopulationMode.");
return null;
}
MethodInfo createFontAsset = tmpFontAssetType.GetMethod(
"CreateFontAsset",
BindingFlags.Public | BindingFlags.Static,
null,
new[]
{
typeof(Font),
typeof(int),
typeof(int),
typeof(GlyphRenderMode),
typeof(int),
typeof(int),
atlasPopulationModeType,
typeof(bool)
},
null);
if (createFontAsset == null)
{
Debug.LogError("Unable to find TMP_FontAsset.CreateFontAsset overload.");
return null;
}
object dynamicMode = Enum.ToObject(atlasPopulationModeType, 1);
Object fontAsset = createFontAsset.Invoke(
null,
new object[]
{
sourceFont,
samplingPointSize,
atlasPadding,
renderMode,
atlasSize,
atlasSize,
dynamicMode,
true
}) as Object;
if (fontAsset == null)
{
Debug.LogError($"Unable to create TMP font asset from: {SourceFontPath}");
return null;
}
fontAsset.name = "siyuan_CN_Bold SDF";
AssetDatabase.CreateAsset(fontAsset, TmpFontAssetPath);
Texture2D[] atlasTextures = tmpFontAssetType.GetProperty("atlasTextures")?.GetValue(fontAsset) as Texture2D[];
Texture2D texture = atlasTextures != null && atlasTextures.Length > 0 ? atlasTextures[0] : null;
if (texture != null)
{
texture.name = "siyuan_CN_Bold Atlas";
AssetDatabase.AddObjectToAsset(texture, fontAsset);
}
Material material = tmpFontAssetType.GetField("material")?.GetValue(fontAsset) as Material;
if (material != null)
{
material.name = "siyuan_CN_Bold Atlas Material";
AssetDatabase.AddObjectToAsset(material, fontAsset);
}
SetCreationSettings(fontAsset, tmpFontAssetType, samplingPointSize, atlasPadding, atlasSize, renderMode);
EditorUtility.SetDirty(fontAsset);
return fontAsset;
}
private static void SetCreationSettings(Object fontAsset, Type tmpFontAssetType, int samplingPointSize, int atlasPadding, int atlasSize, GlyphRenderMode renderMode)
{
PropertyInfo creationSettingsProperty = tmpFontAssetType.GetProperty("creationSettings");
if (creationSettingsProperty == null)
return;
object settings = Activator.CreateInstance(creationSettingsProperty.PropertyType);
SetField(settings, "sourceFontFileGUID", AssetDatabase.AssetPathToGUID(SourceFontPath));
SetField(settings, "pointSize", samplingPointSize);
SetField(settings, "pointSizeSamplingMode", 0);
SetField(settings, "padding", atlasPadding);
SetField(settings, "packingMode", 0);
SetField(settings, "atlasWidth", atlasSize);
SetField(settings, "atlasHeight", atlasSize);
SetField(settings, "characterSetSelectionMode", 7);
SetField(settings, "characterSequence", string.Empty);
SetField(settings, "renderMode", (int)renderMode);
SetField(settings, "includeFontFeatures", false);
creationSettingsProperty.SetValue(fontAsset, settings);
}
private static void SetTmpDefaultFont(Object fontAsset)
{
Object settings = AssetDatabase.LoadAssetAtPath<Object>(TmpSettingsPath);
if (settings == null)
{
Debug.LogWarning($"TMP Settings asset is missing: {TmpSettingsPath}");
return;
}
SerializedObject serializedSettings = new SerializedObject(settings);
serializedSettings.FindProperty("m_defaultFontAsset").objectReferenceValue = fontAsset;
serializedSettings.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(settings);
}
private static void ApplyFontToKnownPrefabs(Object fontAsset, Type tmpFontAssetType)
{
Type tmpTextType = FindType("TMPro.TMP_Text");
Type tmpInputFieldType = FindType("TMPro.TMP_InputField");
Object fontMaterial = tmpFontAssetType.GetField("material")?.GetValue(fontAsset) as Object;
string[] searchFolders =
{
"Assets/Game/Art/UI/Prefab",
"Assets/Base/Prefab"
};
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", searchFolders);
foreach (string prefabGuid in prefabGuids)
{
string prefabPath = AssetDatabase.GUIDToAssetPath(prefabGuid);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (prefab == null)
continue;
bool changed = false;
foreach (Component component in prefab.GetComponentsInChildren<Component>(true))
{
if (component == null)
continue;
Type componentType = component.GetType();
if (tmpTextType != null && tmpTextType.IsAssignableFrom(componentType))
{
changed |= SetSerializedObjectReference(component, "m_fontAsset", fontAsset);
changed |= SetSerializedObjectReference(component, "m_sharedMaterial", fontMaterial);
}
if (tmpInputFieldType != null && tmpInputFieldType.IsAssignableFrom(componentType))
changed |= SetSerializedObjectReference(component, "m_GlobalFontAsset", fontAsset);
}
if (changed)
EditorUtility.SetDirty(prefab);
}
}
private static bool SetSerializedObjectReference(Object target, string propertyName, Object value)
{
SerializedObject serializedObject = new SerializedObject(target);
SerializedProperty property = serializedObject.FindProperty(propertyName);
if (property == null || property.objectReferenceValue == value)
return false;
property.objectReferenceValue = value;
serializedObject.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(target);
return true;
}
private static void SetField(object target, string fieldName, object value)
{
target.GetType().GetField(fieldName)?.SetValue(target, value);
}
private static Type FindType(string fullName)
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type type = assembly.GetType(fullName);
if (type != null)
return type;
}
return null;
}
}