Unity TMP字体包体优化实战:静态字体与回退链技术详解
2026/8/8 15:00:51 网站建设 项目流程

1. 项目概述:为什么TMP字体是Unity包体的“隐形杀手”?

如果你做过Unity项目的发布,尤其是面向移动端或WebGL平台,一定对包体大小这个指标非常敏感。每次构建后看着那个几十甚至上百兆的安装包,心里都在盘算着还能从哪里“抠”出一点空间。在众多优化项中,有一个大家容易忽略但又极其占地方的“大户”——TextMesh Pro(TMP)字体资源。很多项目为了显示中文,直接导入一个完整的字体文件(比如思源黑体),动辄就是10MB以上,如果还用了多种字重(Regular, Bold, Light等),那字体资源轻松吃掉几十MB包体空间是家常便饭。

更让人头疼的是,TMP的默认工作方式对包体并不友好。它为了支持动态添加文字(比如玩家输入的名字),往往会将整个字体文件(.ttf/.otf)打包进去,以确保运行时能生成任何需要的字形。但实际情况是,你的游戏里真的需要显示所有几万个汉字吗?绝大多数项目用到的字符可能就几千个。用几MB甚至十几MB的代价,去服务那几百KB的实际需求,这无疑是巨大的浪费。这就是我们这次“瘦身实战”要解决的核心问题:通过精细化的字体资产管理,把TMP字体相关的包体体积砍掉80%甚至更多,同时保证游戏内所有文本的正常、美观显示。

这次分享的流程,是我在多个上线项目中反复验证过的完整方案,从原理分析、工具准备、实操步骤到避坑指南,都会一一拆解。无论你是正在被包体大小困扰的开发者,还是想提前规避这个问题的项目负责人,这套方法都能直接拿来用。文末我也会附上经过整理的关键脚本源码,帮你省去造轮子的时间。

2. 核心原理拆解:动态字体、静态字体与Fallback机制

要动刀优化,首先得搞清楚TMP字体资源是怎么工作的。这里涉及到三个核心概念:动态字体(Dynamic Font)、静态字体(Static Font)和回退(Fallback)机制。理解它们,是制定有效瘦身策略的基础。

2.1 动态字体(SDF Font Asset - Dynamic)

当你通过TMP的Font Asset Creator窗口,选择一个.ttf.otf源字体文件来创建字体资产(Font Asset)时,如果勾选了Dynamic选项,生成的就是一个动态字体资产。

它的工作原理是:Unity会解析你指定的字符序列(比如一个包含7000个常用汉字的txt文件),为这些字符生成对应的Signed Distance Field(SDF)纹理图集和材质。但是,它并不会删除对原始.ttf/.otf字体文件的引用。在打包时,这个原始的字体文件会被一并打入包内。为什么?因为TMP在运行时如果遇到字体资产中没有预先包含的字符(即“缺失字符”),它需要依靠这个原始字体文件,动态地将其渲染到纹理图集上,然后再显示出来。这就是“动态”二字的由来。

优点:灵活。理论上可以显示任何字符,适合有用户输入、动态生成文本内容的场景。缺点:包体大。因为必须包含完整的原始字体文件。一个常见的误区:很多开发者认为,我生成了字体资产,原始的.ttf文件就可以删了。对于动态字体,这是绝对不行的。删除后,游戏运行时一旦需要动态添加新字,就会因为找不到源字体而失败,表现为该字符显示为空白或默认的“缺失字符”(通常是“□”)。

2.2 静态字体(SDF Font Asset - Static)

创建字体资产时,如果不勾选Dynamic,生成的就是静态字体资产。

它的工作原理是:Unity同样为你指定的字符序列生成SDF纹理和材质。但关键区别在于,生成完成后,这个字体资产就与原始的.ttf/.otf文件解耦了。字体资产自己保存了所有指定字符的纹理信息,不再需要那个原始文件。因此,在打包前,你可以安全地删除项目中的原始字体文件,它不会被打进包里。

优点:包体小。只包含实际用到的字符的纹理数据,体积非常精简。缺点:不灵活。只能显示生成时指定的那些字符。遇到字符集之外的文字,就会显示为“缺失字符”。

2.3 回退(Fallback)字体链

TMP提供了一个强大的功能:字体回退链(Fallback Font Asset List)。你可以在一个TMP文本组件上,或者在TMP Settings的全局配置中,指定一个字体列表。当主字体无法显示某个字符时,TMP会依次遍历这个列表中的字体,直到找到一个能显示该字符的字体为止。

这是实现“瘦身”的关键。我们可以利用这个机制,设计一个“主次分明”的字体方案:

  1. 主字体(静态):包含项目绝大多数会用到的字符(例如7000个常用汉字+标点)。它体积小,承担主要的显示任务。
  2. 备用字体(动态或静态):包含主字体遗漏的生僻字。它的字符集很小,只包含那少部分“漏网之鱼”,因此体积也很小。

这样,99%的情况由小巧的静态主字体搞定,1%的生僻字情况由另一个小体积的备用字体搞定。两者加起来,也远小于一个完整的动态字体文件。

2.4 缺失字符(Missing Character)与“□”

当TMP在所有字体(包括回退字体)中都找不到某个字符时,它会尝试用一个“缺失字符”来占位。默认情况下,这个字符是 Unicode 为\u25A1的“□”(白色方框)。但这里有个巨坑:这个“□”本身也是一个字符!它必须存在于你某个字体(无论是主字体还是回退字体)的字符集中,才能被显示出来。

如果你的所有字体都不包含“□”这个字形,那么当字符缺失时,游戏里就什么都不会显示(空白),而不是显示一个方框。这会给调试和玩家体验带来问题。因此,我们的优化方案中,必须确保“□”被包含在某个字体里(通常是主字体)。

3. 实战准备:工具、字库与项目设置

在开始具体操作前,我们需要准备好“武器库”。这套方案不依赖任何付费插件,核心工具都是开源或Unity自带的。

3.1 核心工具与资源

  1. 字库文件(.ttf/.otf):你需要一个高质量、版权允许的字体文件作为源。例如“思源黑体”、“阿里巴巴普惠体”都是优秀的免费可商用选择。确定一个主字体文件(如SourceHanSansCN-Regular.otf)。
  2. 常用汉字字符集:我们需要一个文本文件(.txt),里面包含项目预计会用到所有字符。这里强烈推荐 GitHub 上的开源项目wy-luke/Unity-TextMeshPro-Chinese-Characters-Set。它提供了包含约7000个常用汉字和标点符号的字符集,覆盖了日常使用99%以上的场景。下载后,你会得到一个Common_Chinese_7000.txt文件。务必检查并确保这个txt文件里包含了“□”(Unicode: \u25A1)这个字符,如果原文件没有,手动在末尾添加一行“□”即可。
  3. Python环境与fontTools库(可选,用于高级操作):如果你想对备用字体进行“反向裁剪”(即从一个完整字体中剔除主字体已包含的7000字,只保留生僻字),就需要用到Python的fonttools库。这能进一步压缩备用字体的体积。安装命令很简单:pip install fonttools。如果觉得这一步麻烦,也有替代方案,后文会讲。
  4. Unity项目:确保已导入TextMeshPro插件(通过Package Manager)。建议在项目初期就规划好字体资源的目录结构,例如:
    Assets/ ├─ Fonts/ │ ├─ TMP/ # 存放所有TMP相关资源 │ │ ├─ SourceFonts/ # 存放原始的 .ttf/.otf 文件(编辑期使用) │ │ ├─ FontAssets/ # 存放生成的 .asset 字体资产文件 │ │ ├─ Textures/ # 存放字体图集纹理 │ │ └─ Materials/ # 存放字体材质 │ └─ ... # 其他非TMP字体

3.2 修改TMP设置文件的加载路径(关键预处理)

这是一个至关重要的前置步骤,直接影响我们后续流程的顺畅度。默认情况下,TMP会从Resources文件夹加载一个叫TMP Settings.asset的全局配置文件。如果我们想自定义字体回退链,就需要修改这个文件。但直接修改Resources里的文件,在打AssetBundle(AB包)时可能会遇到路径问题。

更优雅的做法是,将TMP Settings.asset移到我们自己的管理目录下(比如Assets/Fonts/TMP/),并修改TMP源码,让它从我们指定的路径加载。不过,修改源码不利于团队协作和版本升级。这里推荐一种无需修改TMP源码的反射方案,在游戏初始化时动态替换单例实例。

操作步骤如下:

  1. 在Unity编辑器中,通过菜单Window > TextMeshPro > Font Asset Creator随便操作一下,TMP会自动初始化并创建默认的TMP Settings文件。你可以在Project窗口搜索TMP Settings找到它。
  2. 将这个文件移动到我们规划好的目录,例如Assets/Fonts/TMP/TMP Settings.asset
  3. 在游戏启动的代码中(例如一个GameManagerAwake方法里),添加以下代码:
using TMPro; using System.Reflection; public class GameManager : MonoBehaviour { void Awake() { ConfigureTmpSettings(); } void ConfigureTmpSettings() { // 1. 从你的资源加载系统(如Resources, Addressables, AssetBundle)加载移动后的TMP Settings文件 // 这里以Resources为例,假设文件放在 Assets/Resources/Fonts/TMP/ 下 var customTmpSettings = Resources.Load<TMP_Settings>("Fonts/TMP/TMP Settings"); if (customTmpSettings == null) { Debug.LogError("Failed to load custom TMP Settings!"); return; } // 2. 使用反射替换TMP_Settings内部的静态单例实例 var field = typeof(TMP_Settings).GetField("s_Instance", BindingFlags.NonPublic | BindingFlags.Static); if (field != null) { field.SetValue(null, customTmpSettings); Debug.Log("Custom TMP Settings applied successfully."); } else { Debug.LogError("Failed to find s_Instance field in TMP_Settings."); } } }

为什么这么做?这确保了游戏运行时使用的是我们自定义的、包含了优化后字体回退链的配置。同时,避免了直接修改TMP源码带来的维护成本。

4. 核心瘦身流程:三步打造极致字体包

万事俱备,现在我们开始核心的“瘦身手术”。整个过程分为三步:制作静态主字体、准备备用字体、配置回退链。

4.1 第一步:创建静态主字体(承担99%的显示任务)

目标:生成一个只包含“常用汉字字符集”(如7000字+标点+“□”)的静态字体资产。完成后可删除源字体文件。

  1. 打开Font Asset CreatorWindow > TextMeshPro > Font Asset Creator
  2. 选择源字体文件:在Source Font File中选择你的中文字体文件(如SourceHanSansCN-Regular.otf)。
  3. 选择字符集
    • Character Set下拉菜单选为Custom Characters
    • 点击Browse按钮,选择你准备好的Common_Chinese_7000.txt文件(确保含“□”)。
    • 导入后,可以在下方的Characters预览框中看到所有字符。
  4. 关键设置
    • Font Asset Type:保持默认的SDF
    • Render Mode:根据项目需求选择,Smooth适用于大多数情况。
    • Atlas Resolution非常重要。这决定了字体纹理图集的大小。7000多个字符,建议至少设置为2048 x 2048。如果预览显示有字符被裁切(红色警告),需要增大到4096 x 4096。目标是让所有字符都变成绿色(已包含)。
    • Padding:保持默认5即可,影响字符在纹理间的间隔。
    • Packing Method:选Optimum
    • 最重要的选项:取消勾选Dynamic取消勾选Dynamic取消勾选Dynamic这样生成的就是静态字体。
  5. 生成字体:点击右下角的Generate Font Atlas按钮。等待进度条完成。
  6. 保存字体资产:点击SaveSave as...,将其保存到你的规划目录,例如Assets/Fonts/TMP/FontAssets/MyStaticFont.asset
  7. 验证与清理
    • 在场景中创建一个TextMeshPro - Text (UI)对象,将它的Font Asset设置为刚生成的MyStaticFont
    • 在文本框中输入一些常用字和“□”,确认都能正常显示。
    • 确认无误后,你可以将项目中原先的.ttf/.otf源字体文件从Assets目录中移走或删除(建议先备份)。因为静态字体不再需要它。这是包体减小的第一波红利。

注意事项Atlas Resolution设置过高(如8192)可能在部分低端移动设备上不支持。4096是移动端相对安全的尺寸上限。如果4096仍装不下所有字符,可以考虑将字符集拆分成两个字体文件(如常用3500字+次常用3500字),但这会增加Draw Call,需要权衡。

4.2 第二步:准备备用字体(兜底1%的生僻字)

主字体覆盖了99%的场景,但我们需要一个“保险丝”来处理玩家名字、罕见地名等可能出现的生僻字。目标是让这个备用字体尽可能小。

方案A:使用精简的动态字体(推荐,简单直接)

  1. 准备生僻字字符集:从完整的汉字字符集(如GB18030标准约27533字)中,剔除主字体已包含的7000常用字。你可以通过网络找到“通用规范汉字表”(约8105字),剔除7000后剩下的1000多字,再加上一些更生僻的字,形成一个Rare_Characters.txt文件。这个文件通常不会很大,可能就几百到一两千个字符。
  2. 创建动态字体
    • 再次打开Font Asset Creator
    • 选择同一个或另一个字体文件作为源(如果删了,重新放回来临时用一下)。
    • 字符集选择Custom Characters并导入Rare_Characters.txt
    • Atlas Resolution可以设小,比如512 x 5121024 x 1024,因为字符数少。
    • 这次务必勾选Dynamic
    • 生成并保存为MyFallbackFont_Dynamic.asset
  3. 原理:由于勾选了Dynamic,这个字体资产会保留对源字体文件的引用。但因为其字符集本身很小,所以生成的纹理图集也很小。虽然源字体文件还是会打包进去,但我们已经有一个主静态字体了,这个动态备用字体只会在极少数情况下被用到。它的存在保证了功能的完备性。

方案B:使用反向裁剪的静态字体(进阶,包体最小化)

如果你追求极致的包体,不希望任何完整的.ttf文件被打包,可以采用此方案。这需要用到Python脚本对字体文件进行“手术”,从完整字体中物理删除主字体已包含的字符,生成一个只包含生僻字的、新的.ttf子集文件。

  1. 安装fontTools:确保已安装pip install fonttools
  2. 准备Python脚本:创建一个subset_font.py脚本。
#!/usr/bin/env python3 from fontTools import subset import sys def create_fallback_font_subset(main_font_path, main_chars_file, fallback_font_path, output_font_path): """ 从完整字体中移除主字体包含的字符,生成生僻字子集字体。 :param main_font_path: 完整字体文件路径 :param main_chars_file: 主字体字符集文件路径(txt,含7000字) :param fallback_font_path: 原始完整字体路径(可与main_font_path相同) :param output_font_path: 输出的子集字体路径 """ # 读取主字体字符集 with open(main_chars_file, 'r', encoding='utf-8') as f: main_chars = f.read().strip() # 将字符转换为Unicode代码点列表,并格式化为fonttools需要的字符串 # 注意:我们需要的是从完整字体中“排除”这些字符,所以要用反向逻辑。 # fonttools subset 的 `--text=` 参数是包含,我们需要用 `--unicodes=` 指定要保留的字符。 # 更简单的方法:先提取完整字体的所有字符,再过滤掉主字体字符。 # 这里我们换一种思路:直接生成一个包含所有生僻字(我们已知列表)的字体。 # 假设我们有一个生僻字列表文件 rare_chars.txt with open('rare_chars.txt', 'r', encoding='utf-8') as f: rare_chars = f.read().strip() # 使用fonttools的子集化功能,指定要保留的字符(生僻字) # 命令参数:输入字体,输出字体,要保留的文本 args = [ fallback_font_path, f'--output-file={output_font_path}', f'--text={rare_chars}', # 关键:只保留这些字符 '--layout-features=*', # 保留所有布局特性(可选) '--glyph-names', '--symbol-cmap', '--legacy-cmap', '--notdef-glyph', '--recommended-glyphs', '--name-IDs=*', '--name-legacy', '--name-languages=*', ] subset.main(args) if __name__ == '__main__': # 示例用法 create_fallback_font_subset( main_font_path='SourceHanSansCN-Regular.otf', main_chars_file='Common_Chinese_7000.txt', fallback_font_path='SourceHanSansCN-Regular.otf', output_font_path='SourceHanSansCN_Fallback_Subset.otf' ) print("Fallback font subset created successfully.")

注意:上述脚本是一个概念示例。实际操作中,你需要一个准确的“生僻字列表”。一个可行的方法是:获取一个全汉字字符集文件(如All_Chinese_Chars.txt),用程序将其与Common_Chinese_7000.txt对比,差值部分就是生僻字列表。生成这个列表后,再用脚本基于完整字体生成子集字体。

  1. 使用子集字体创建静态备用字体:用生成的SourceHanSansCN_Fallback_Subset.otf作为源,字符集选择Characters from File并导入生僻字列表文件,不勾选Dynamic,生成一个静态的MyFallbackFont_Static.asset。这样,备用字体也是静态的,且源文件(子集字体)体积非常小。

实操心得:对于大多数项目,方案A(小字符集动态字体)已经完全够用,且操作简单,不易出错。方案B虽然更极致,但涉及外部脚本和字体处理,流程更复杂,适合对包体有极端要求的项目。我个人的建议是先从方案A开始,如果包体压力依然很大,再考虑方案B。

4.3 第三步:配置字体回退链

现在我们有了一主一备两个字体资产,需要让它们协同工作。

  1. 配置全局回退链(推荐):打开我们之前移动并配置好的TMP Settings.asset文件(在Assets/Fonts/TMP/目录下)。
  2. 在Inspector面板中找到Fallback Font Assets列表。
  3. 将准备好的备用字体(MyFallbackFont_Dynamic.assetMyFallbackFont_Static.asset)拖拽到列表中。顺序很重要!TMP会按列表顺序查找字符。通常我们只放一个备用字体就够了。
  4. 设置默认字体:在TMP Settings中,将Default Font Asset设置为我们创建的静态主字体MyStaticFont.asset
  5. 设置缺失字符:在Missing Character Unicode字段中,填入9633(这是“□”的十进制Unicode)。确保你的主字体或备用字体包含这个字符。

验证回退链

  • 在场景中创建一个TMP文本,使用主字体MyStaticFont
  • 输入一段包含常用字和生僻字(如“喆”、“堃”)的文本。
  • 运行游戏,观察生僻字是否正常显示。如果显示为“□”,检查备用字体是否包含该字,以及回退链配置是否正确。
  • 你可以在TMP文本组件的Inspector中,临时将Font Asset清空,它会使用TMP Settings中的默认字体和回退链,这也是一个验证全局配置的好方法。

5. 高级技巧与疑难排查

完成了以上三步,核心的瘦身工作就结束了。但在实际项目中,你可能会遇到一些边缘情况或问题。下面分享一些进阶技巧和排查方法。

5.1 处理“□”不显示的问题

这是一个高频问题。现象是字符缺失时,该位置一片空白,而不是显示方框。

  • 原因TMP Settings中设置的Missing Character Unicode(9633)对应的“□”字符,没有存在于当前字体链(主字体+所有回退字体)的任何字符集中。
  • 解决方案
    1. 检查并包含:确保你的主字体字符集文件(Common_Chinese_7000.txt)里明确包含了“□”字。手动添加进去。
    2. 重新生成:如果已经生成字体,需要重新生成主字体(或任何一个回退字体),确保“□”被烘焙到字体图集里。
    3. 自定义缺失字符:如果实在不想用“□”,可以换一个你的字体肯定有的字符,比如一个空格 (Unicode 32)或一个点(Unicode 8226)。在TMP Settings中修改Missing Character Unicode值即可。

5.2 字体资产打包与AssetBundle(AB包)注意事项

如果你的项目使用AssetBundle进行资源分包管理,需要注意:

  • 依赖关系:动态字体会依赖其源.ttf/.otf文件。在构建AB包时,需要确保它们被打包在同一个Bundle中,或者有明确的依赖关系,否则运行时加载字体会失败。
  • 静态字体的优势:静态字体资产(.asset)是自包含的,不依赖外部字体文件。打AB包时非常干净,只需要打包这个.asset文件及其关联的材质和纹理即可,依赖管理简单。
  • TMP Settings的打包:我们自定义的TMP Settings.asset文件也需要被打包。通常可以放在一个启动时必须加载的AB包中,并在游戏初始化最早阶段(在加载任何UI之前)通过前面提到的反射方法进行配置。

5.3 性能与内存考量

  • 纹理图集大小:主字体4096x4096的纹理在移动端不算小。但字体纹理通常是ARGB32ARGB64格式,且开启了Mipmap,内存占用需要关注。在Font Asset CreatorAtlas Population设置中,可以调整Point SizePadding来优化字符在纹理上的填充率,有时用2048x2048两个纹理(拆分字符集)可能比一个4096x4096更省内存(但Draw Call增加)。
  • 多字重与风格:如果你的项目使用了同一字体的粗体(Bold)、斜体(Italic),你需要为每种风格单独创建字体资产。注意,TMP的“加粗”效果是模拟的,如果追求精确的粗体字形,仍需从字体的Bold版本文件生成独立的字体资产。
  • 字体资产实例化:确保字体资产(特别是材质)在项目中被引用和共享,避免同一字体被复制多份,造成内存浪费。

5.4 监控与日志

TMP在运行时如果找不到字符,会在控制台输出警告:The character with Unicode value XXXX was not found in the [FontName] font asset or any potential fallbacks. It was replaced by Unicode character 9633 in text object [ObjectName].

这个警告很有用,它告诉你哪个字缺失了,以及被哪个对象使用。你可以定期收集这些警告,如果发现某个生僻字频繁出现,可以考虑将其加入主字体字符集中,以优化体验。

如果你希望将这些警告提升为错误,以便在测试阶段更早发现,可以修改TMP的源码文件TMPro_UGUI_Private.cs,搜索相关的日志输出行进行修改。但更推荐的做法是编写一个简单的监听器,在收到这类日志时进行自定义处理(如上报分析平台)。

6. 效果评估与源码分享

按照上述流程操作后,如何评估瘦身效果?

  1. 构建对比:在优化前和优化后,分别进行项目构建(Build)。
  2. 查看构建报告:在Unity构建结束后弹出的构建报告(Build Report)窗口中,查看Assets/TextMesh Pro/Resources/Fonts或你自定义字体路径下的文件大小变化。你会看到,原先巨大的.ttf文件不再出现在构建结果中,取而代之的是几个.asset文件和对应的纹理文件。纹理文件的大小取决于你的Atlas Resolution设置。
  3. 包体分析工具:使用Unity的Build Report插件或Asset Bundle Browser工具,可以更清晰地看到每个资源对包体的贡献。你会发现字体部分占用的空间大幅下降。

在我的一个实际项目中,将一套完整的思源黑体(Regular + Bold,约20MB)替换为7000常用字静态主字体 + 生僻字动态备用字体后,字体相关的包体从~22MB下降到了~3.5MB,缩减比例超过80%,效果非常显著。

最后,附上本文中提到的关键脚本的整理版源码,方便你快速集成:

1. TMP Settings 运行时配置脚本 (TmpSettingsConfigurator.cs)

using UnityEngine; using TMPro; using System.Reflection; public class TmpSettingsConfigurator : MonoBehaviour { [Header("配置路径 (相对于Resources文件夹)")] public string tmpSettingsResourcePath = "Fonts/TMP/TMP Settings"; void Awake() { ApplyCustomTmpSettings(); } void ApplyCustomTmpSettings() { // 加载自定义的TMP Settings资源 TMP_Settings customSettings = Resources.Load<TMP_Settings>(tmpSettingsResourcePath); if (customSettings == null) { Debug.LogError($"TMP配置失败:未在路径 'Resources/{tmpSettingsResourcePath}' 找到 TMP Settings.asset。"); Debug.LogError("请确保该文件已放置正确,或在Inspector中指定正确路径。"); return; } // 使用反射替换内部单例实例 var instanceField = typeof(TMP_Settings).GetField("s_Instance", BindingFlags.NonPublic | BindingFlags.Static); if (instanceField != null) { instanceField.SetValue(null, customSettings); Debug.Log($"自定义TMP设置已应用: {customSettings.name}"); } else { Debug.LogError("TMP配置失败:无法访问 TMP_Settings.s_Instance 字段。"); } } }

2. 字体工具类示例 (FontUtility.cs)

// 此脚本提供一些静态方法,用于处理字符集文件等。 using System.IO; using System.Text; using UnityEngine; public static class FontUtility { /// <summary> /// 检查并确保字符集文件包含指定的缺失占位符(默认为“□”)。 /// </summary> /// <param name="filePath">字符集文本文件路径(Assets/...)</param> /// <param name="placeholder">要确保存在的占位符,如“□”</param> public static void EnsurePlaceholderInCharacterFile(string filePath, string placeholder = "□") { if (!File.Exists(filePath)) { Debug.LogWarning($"字符集文件不存在: {filePath}"); return; } string content = File.ReadAllText(filePath, Encoding.UTF8); if (!content.Contains(placeholder)) { Debug.Log($"字符集文件 {Path.GetFileName(filePath)} 不包含占位符 '{placeholder}',正在添加..."); // 追加到文件末尾,也可以根据需求插入到特定位置 using (StreamWriter sw = File.AppendText(filePath)) { sw.Write(placeholder); } Debug.Log("占位符已添加。请重新导入该文件以更新TMP Font Asset Creator。"); #if UNITY_EDITOR UnityEditor.AssetDatabase.Refresh(); #endif } else { Debug.Log($"字符集文件已包含占位符 '{placeholder}'。"); } } /// <summary> /// (示例) 比较两个字符集文件,生成差异文件(生僻字列表)。 /// 需要在实际项目中完善逻辑。 /// </summary> public static void GenerateRareCharactersFile(string allCharsPath, string commonCharsPath, string outputRareCharsPath) { // 读取全字符集 string allChars = File.ReadAllText(allCharsPath, Encoding.UTF8); // 读取常用字符集 string commonChars = File.ReadAllText(commonCharsPath, Encoding.UTF8); // 简单的差异计算(实际需考虑去重、排序等) StringBuilder rareCharsBuilder = new StringBuilder(); foreach (char c in allChars) { if (!commonChars.Contains(c)) { rareCharsBuilder.Append(c); } } File.WriteAllText(outputRareCharsPath, rareCharsBuilder.ToString(), Encoding.UTF8); Debug.Log($"生僻字文件已生成至: {outputRareCharsPath},共 {rareCharsBuilder.Length} 个字符。"); #if UNITY_EDITOR UnityEditor.AssetDatabase.Refresh(); #endif } }

将这些脚本放入项目后,记得根据你的实际目录结构调整TmpSettingsConfigurator中的资源路径。字体优化是一个细致活,但带来的包体收益是立竿见影的。希望这套完整的流程能帮你彻底解决Unity项目的TMP字体包体难题。如果在实践中遇到其他问题,欢迎在评论区交流讨论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询