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(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(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(prefabPath); if (prefab == null) continue; bool changed = false; foreach (Component component in prefab.GetComponentsInChildren(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; } }