Unity 游戏汉化之 TMP 字体替换

Reverse Engineering Jun 4, 2023

当 Unity 游戏用了 TMP 之后由于 Font Atlas 是预先生成的, 导致不存在的字体则会出现 口口口 的现象。这边阐述一个相对简单的思路来替换字体。

  1. 下载同版本的 UnityEditor
  2. 建立项目后安装同版本的 TextMeshPro
  3. 使用 TextMeshPro 建立字体并且打包 AssetBundle
  4. Hook TMPro.TextMeshProUGUI 类中的 OnEnable 方法, 并实现如下类似逻辑:
using System;
using System.IO;
using MonoMod;
using TMPro;
using UnityEngine;

namespace TMPPatch
{
    [MonoModPatch("global::TMPro.TextMeshProUGUI")]
    public class TMPText
    {
        extern void orig_OnEnable();

        private static TMP_FontAsset maruFont = null;
        private static TMP_FontAsset boldFont = null;

        protected void OnEnable()
        {
            if (maruFont == null || boldFont == null)
            {
                var fontPath = Path.Combine(Environment.CurrentDirectory, "majbridge", "chinese_font");
                if (File.Exists(fontPath))
                {
                    var ab = AssetBundle.LoadFromFile(fontPath);
                    maruFont = ab.LoadAsset<TMP_FontAsset>("SweiGothicCJKsc-Regular SDF");
                    boldFont = ab.LoadAsset<TMP_FontAsset>("sgm SDF");

                    if (maruFont.characterLookupTable == null)
                        maruFont.ReadFontAssetDefinition();

                    if (boldFont.characterLookupTable == null)
                        boldFont.ReadFontAssetDefinition();

                    Console.WriteLine("[MajBridge TMP] Loaded font");
                }
            }

            if (maruFont != null && boldFont != null)
            {
                var _this = (TextMeshProUGUI)(object)this;

                var material = _this.fontMaterial;

                _this.font = boldFont;
                if (material != null)
                {
                    material.SetTexture(ShaderUtilities.ID_MainTex, boldFont.atlasTexture);
                    _this.fontMaterial = material;
                }
            }
            
            orig_OnEnable();
        }
    }
}

注: 其中备份 material 并且再设置完字体之后替换回去是为了保留字体的描边等效果, 由于 TMPro 中实现这些完全效果由着色器实现, 参数定义在 Material 中, 所以这么做就可以保留文字效果。

标签