1. 为什么需要批量替换预制体字体最近接手一个从Unity 2019升级到2022版本的老项目时遇到了一个棘手的问题游戏里所有中文文本都变成了方框。排查后发现是Unity 2022移除了内置的Arial字体自动替换成了LegacyRuntime.ttf这个不支持中文的字体。面对项目中几百个预制体手动修改显然不现实这时候就需要编写编辑器脚本来批量处理。这个场景在Unity开发中其实很常见。除了版本升级导致的字体兼容问题外还有几种典型情况美术资源更新后需要统一更换新字体多语言版本需要切换不同字库性能优化时需要将动态字体替换为静态字体不同平台发布时需要适配平台专用字体我去年参与的一个手游项目就遇到过类似问题。当时因为苹果审核要求使用SF字体而安卓端需要用思源黑体最后就是用脚本批量处理了1200多个预制体整个过程只用了不到3分钟。2. 准备工作了解关键组件2.1 Text组件与TextMeshPro的区别在动手写脚本前先要明确我们要修改的是Unity传统的UI.Text组件而不是TextMeshProTMP。这两个组件虽然都能显示文字但底层实现完全不同UI.TextUnity原生UI系统的一部分使用动态字体渲染TextMeshPro基于Signed Distance FieldSDF的文本渲染方案从Unity 2022开始官方推荐使用TMP替代传统的Text组件。但很多老项目由于历史原因仍然大量使用UI.Text。我们的脚本就是针对这种情况设计的。2.2 预制体的结构特点预制体Prefab是Unity中的可复用模板修改预制体会影响所有实例。但要注意预制体可能嵌套其他预制体一个预制体可能包含多个Text组件Text组件可能被禁用或隐藏在非活跃的GameObject下我们的脚本需要能处理所有这些复杂情况。比如去年有个项目就遇到嵌套预制体字体没被修改的问题就是因为脚本没考虑到预制体嵌套的情况。3. 编写字体替换脚本3.1 基础脚本框架先创建一个继承自EditorWindow的类添加菜单项入口using UnityEditor; using UnityEngine; using UnityEngine.UI; public class FontReplacer : EditorWindow { private Font targetFont; [MenuItem(Tools/批量替换字体)] public static void ShowWindow() { GetWindowFontReplacer(字体替换工具); } }这个基础框架创建了一个简单的编辑器窗口可以通过Unity顶部菜单的Tools 批量替换字体打开。3.2 实现核心替换逻辑完整的替换方法需要考虑以下几个关键点遍历项目中的所有预制体检查每个预制体包含的Text组件替换字体并标记为脏对象保存修改private void ReplaceFontsInPrefabs() { // 加载目标字体 targetFont AssetDatabase.LoadAssetAtPathFont(Assets/Fonts/YourFont.ttf); if(targetFont null) { Debug.LogError(字体文件加载失败请检查路径); return; } // 获取所有预制体 string[] prefabGUIDs AssetDatabase.FindAssets(t:Prefab); int modifiedCount 0; foreach(string guid in prefabGUIDs) { string path AssetDatabase.GUIDToAssetPath(guid); GameObject prefab AssetDatabase.LoadAssetAtPathGameObject(path); bool isDirty false; Text[] textComponents prefab.GetComponentsInChildrenText(true); foreach(Text text in textComponents) { if(text.font ! targetFont) { text.font targetFont; isDirty true; } } if(isDirty) { EditorUtility.SetDirty(prefab); modifiedCount; } } AssetDatabase.SaveAssets(); Debug.Log($字体替换完成共修改了{modifiedCount}个预制体); }3.3 添加进度显示和撤销支持为了让工具更友好可以添加进度条和撤销支持private void ReplaceFontsInPrefabs() { // 添加撤销组 Undo.IncrementCurrentGroup(); // 显示进度条 EditorUtility.DisplayProgressBar(批量替换字体, 正在处理预制体..., 0); // ...原有代码... for(int i 0; i prefabGUIDs.Length; i) { // 更新进度 EditorUtility.DisplayProgressBar(批量替换字体, $正在处理预制体 ({i1}/{prefabGUIDs.Length}), (float)i/prefabGUIDs.Length); // ...处理逻辑... } // 清理进度条 EditorUtility.ClearProgressBar(); // 标记撤销点 Undo.SetCurrentGroupName(批量替换字体); }4. 高级功能扩展4.1 支持多种查找方式基础脚本会扫描整个项目但对于大型项目可能效率太低。可以添加多种查找模式enum SearchMode { 整个项目, 选中文件夹, 选中预制体 } private SearchMode currentMode SearchMode.整个项目; private void OnGUI() { currentMode (SearchMode)EditorGUILayout.EnumPopup(查找范围, currentMode); if(GUILayout.Button(开始替换)) { switch(currentMode) { case SearchMode.整个项目: ReplaceInAllPrefabs(); break; case SearchMode.选中文件夹: ReplaceInSelectedFolders(); break; case SearchMode.选中预制体: ReplaceInSelectedPrefabs(); break; } } }4.2 添加过滤条件有时候我们只想修改特定条件的Text组件可以添加过滤选项private bool onlyActiveInHierarchy false; private bool onlyWithChineseCharacters false; private float minFontSize 0; private float maxFontSize 999; private bool ShouldProcessText(Text text) { if(onlyActiveInHierarchy !text.gameObject.activeInHierarchy) return false; if(text.fontSize minFontSize || text.fontSize maxFontSize) return false; if(onlyWithChineseCharacters) { // 简单的中文字符检测 foreach(char c in text.text) { if(c 0x4E00 c 0x9FFF) return true; } return false; } return true; }5. 常见问题与解决方案5.1 字体不生效的可能原因在实际使用中可能会遇到字体替换后不显示的问题。常见原因包括字体文件格式问题Unity支持.ttf和.otf格式但某些特殊格式可能需要转换字体导入设置错误在Inspector窗口检查字体导入设置确保Character包含所需字符集材质丢失某些字体需要特定材质替换后可能需要重新指定材质动态字体回退如果使用动态字体确保Fallback字体包含所需字符5.2 性能优化建议处理大量预制体时可以采取以下优化措施分批处理每处理100个预制体就调用一次AssetDatabase.SaveAssets()缓存机制记录已处理的预制体支持增量更新后台处理使用EditorApplication.delayCall分帧处理多线程对于非常大型项目可以考虑使用JobSystem6. 实际案例分享去年我们团队接手了一个有2000预制体的教育类APP项目需要将原有字体替换为符合WCAG标准的无障碍字体。原始字体是嵌入在各个场景和预制体中的手动修改几乎不可能。我们扩展了基础脚本添加了以下功能字体替换前后对比预览修改记录导出为CSV按字体使用量统计报表自动备份修改前的预制体最终脚本不仅完成了字体替换还生成了详细的修改报告帮助团队评估改动影响。整个过程处理了2437个预制体共修改了8912个Text组件总耗时不到5分钟。