惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
A
About on SuperTechFans
量子位
B
Blog RSS Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园_首页
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
Blog — PlanetScale
Blog — PlanetScale
Microsoft Azure Blog
Microsoft Azure Blog
V
V2EX
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
G
Google Developers Blog
U
Unit 42
D
DataBreaches.Net
博客园 - Franky
D
Docker
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Hugging Face - Blog
Hugging Face - Blog

博客园 - 野生西瓜

[Unity 杂货铺] 编辑器中的 C# 代码热重载 [Unity 杂货铺] 禁用 Domain Reload 提高效率 [Unity 杂货铺] SpriteAtlas 和 SBP 打包 [Unity 杂货铺] 游戏字体选择 [Unity 杂货铺] 游戏项目的结构规划与初始化 [Unity 杂货铺] Git 配置与实践 [Unity 杂货铺] 引擎版本的选择 [Unity] 基础寻路算法 - 环境搭建 [Unity] 引擎脚本相关的字符串优化 [Unity] 基础寻路算法 - 代码实践 [Unity] 资源工作流程 - AssetPostprocessor [Unity] 资源工作流程 - ScriptedImporter [Unity] 资源工作流程 - 辅助工具 [Lua游戏AI开发指南] 笔记零 - 框架搭建 [GAME] [Civilization] 文明6字体及字体大小修改 [Unity] 编辑器运行中动态编译执行C#代码 [Unity] 在软件标题栏显示工作路径 [GAMEDEV] 个人开发如何找到合适的图片素材? [施工中] 博客导航 2021 的书 [BACKUP] Visual Studio Code 配置 [theHunterCOTW] 猎人荒野的召唤-一点资料
[Unity 杂货铺] TMP 字体在编辑器中的动态数据清理
野生西瓜 · 2026-09-06 · via 博客园 - 野生西瓜

1 导读

TextMeshPro 字体设置为 Dynamic 时,在每次编辑 UI 时会自动生成字形数据并保存到 .asset 文件,导致 Git 频繁记录无意义变动。

本文分析了问题根源,并给出了解决方案:在 asset 触发变动保存时,调用 TMP_Font ClearFontAssetData 清理数据。

你可以直接从该 Github 库中获取脚本参考:https://github.com/STARasGAMES/tmpro-dynamic-data-cleaner

2 前言

TextMeshPro 动态字体资产,会将动态生成的数据保存在与配置数据相同的 asset 文本文件中。基本上每次编辑 UI 都会导致 .asset 文件变动,这给版本控制带来了很大的麻烦。同时,对字体真正的设置修改也很可能被淹没在这些动态数据中。

TMP_Font 提供了一个 Clear Dynamic Data On Build 的配置选项,但那只是用于构建时。

原本觉得这是一个很小众的“问题”,甚至都算不上问题。顺手问了一下 AI,还真给出了相关讨论和其他人的解决方式:

TMP 字体 asset,每次拼UI都会生成纹理导致 Git 变化,有好办法处理吗?

DeepSeek 不仅提供了一份 Unity Issue Tracker 中很新的链接:DYNAMIC TMP_FONT ASSET SHOWS UP AS MODIFIED IN SOURCE CONTROL WHEN TEXTMESHPRO TEXT USING IT IS CHANGED

还提供了不少 Unity 论坛中相关的讨论,本文的解决方式则来自于其中一则:https://discussions.unity.com/t/solved-tmpro-dynamic-font-asset-constantly-changes-in-source-control/868941/

这点是非常棒的,因为之前很难想象该使用何种关键字,如何通过搜索引擎匹配找到这样的讨论。

3 问题描述

3.1 现象

只是修改了一个 UI 上的文字,提交时却发现 TMP 字体资源(如 NotoSansCJK-Regular SDF.asset)也在变动列表中。

打开版本控制工具查看差异,发现文件变化巨大,但实际只改了寥寥几个字符。

多人协作时,频繁出现字体资源的合并冲突,耗费大量精力。

3.2 根源

TMP 的动态字体(Dynamic)模式,当字体资源的 Atlas Population Mode 设置为 Dynamic 时,TMP 会在编辑器运行时自动将用到的字符字形(glyph)添加到字体图集纹理中。问题是,这些动态生成的二进制图集数据会被直接保存回 .asset 文件中。

具体流程:

你在场景中创建一个 TMP 文本,输入文字“你好”。

TMP 检查当前字体图集中是否包含“你”和“好”的字形。

如果没有,就从源字体文件中提取这两个字,生成 SDF 纹理,并写入图集。

最后,将更新后的图集数据序列化保存到 .asset 文件中。

因此,即便只是新增一个字符,字体文件也会被修改,并被 Git 捕获。

4 解决方案

可以在 Unity 保存任何资产之前,自动检测并清除动态字体资源中的“生成性”二进制数据,只保留配置信息。

直接提供代码,来自于:https://github.com/STARasGAMES/tmpro-dynamic-data-cleaner

字体 .asset 似乎不会在每次修改时都进行写入保存,可以尝试使用 Ctrl + S 来触发 OnWillSaveAssets。

using System;
using TMPro;
using UnityEditor;
using UnityEngine;

internal class DynamicFontAssetAutoCleaner : UnityEditor.AssetModificationProcessor
{
    static string[] OnWillSaveAssets(string[] paths)
    {
        foreach (string path in paths)
        {
            try
            {
                var assetType = AssetDatabase.GetMainAssetTypeAtPath(path);

                // GetMainAssetTypeAtPath() sometimes returns null, for example, when path leads to .meta file
                if (assetType == null)
                    continue;

                // TMP_FontAsset is not marked as sealed class, so also checking for subclasses just in case
                if (assetType != typeof(TMP_FontAsset) && assetType.IsSubclassOf(typeof(TMP_FontAsset)) == false)
                    continue;

                // Loading the asset only when we sure it is a font asset
                var fontAsset = AssetDatabase.LoadMainAssetAtPath(path) as TMP_FontAsset;

                // Theoretically this case is not possible due to asset type check above, but to be on the safe side check for null
                if (fontAsset == null)
                    continue;

                #if UNITY_2022_1_OR_NEWER
                    bool isDynamic = fontAsset.atlasPopulationMode is AtlasPopulationMode.Dynamic or AtlasPopulationMode.DynamicOS;
                #else
                    bool isDynamic = fontAsset.atlasPopulationMode is AtlasPopulationMode.Dynamic;
                #endif

                    if (!isDynamic)
                        continue;

                // Debug.Log("Clearing font asset data at " + path);
                fontAsset.ClearFontAssetData(setAtlasSizeToZero: true);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                Debug.LogError($"Something went wrong while clearing dynamic font data. For more info look at previous log message. Font asset path: '{path}'");
            }
        }

        return paths;
    }
}

调用 ClearFontAssetData 之后,.asset 文本降到 100多行。查看 Inspector,Character Table 和 Gylph Table 也都已清空。