资讯动态

Unity一键换字体:场景与预制体Text批量替换工具实现

发布时间:2026/10/17 10:39:07 来源:尧图企业网站定制
如果你经历过一个项目上线后被要求整体换字体的时刻应该能懂那种头大。策划一句话、设计一张设计稿剩下的就是十几个场景、上百个预制体、几千个Text组件等我手动去点。最初两次换字体我也是老实人打开场景一个一个Object地看选中Text就往Inspector里拖新字体三个场景改完腰酸背痛还总觉得哪个角落漏了。这种纯机械的体力活后来我用一个Editor小工具彻底解决了在Unity编辑器里选择目标字体点一下按钮所有场景和预制体里的Text字体一次性替换完成还能输出一份完整的替换日志。这篇就把我写的这个一键换字体的实现思路、完整代码和后来踩到过的坑整理出来给同样被字体替换折磨过的人一份可以直接抄的作业。1. 换字体这件事为什么会变成体力活1.1 手动改字体时的三类经典事故很多人觉得换字体不就是把所有Text组件的字体引用换一下嘛有什么难的。问题在于项目一旦到了几十个场景、几百个Prefab的规模这件事从“简单操作”直接变成“高危操作”。第一类事故是漏改。Text组件并不全叫“Text”很多叫Label、Title、Desc藏在ScrollView的深层节点里或者挂在某个动态加载的Prefab内部。你在场景里肉眼扫一遍只能看到上面板的一部分漏掉几个是常态。更麻烦的是漏掉的往往不是那些不重要的界面而是某个特定职业的界面、某个特殊模式的弹窗等测试测出来的时候包体已经出完了。第二类事故是改错。Prefab实例在场景里可能已经有过自己的字体覆盖你在场景里把实例上的字体改了但Prefab资源根本没有任何变化反过来你改了Prefab资源某些实例又因为自己的覆盖记录而不跟随。这类问题最容易出现在嵌套Prefab和Prefab变体上手动操作时根本看不出哪个是资源、哪个是实例覆盖。改完之后总会有那么几个UI显示旧字体又找不到在哪。第三类事故是无法回溯。手动改了一大圈既不记得改过哪些对象也不清楚哪些Prefab被改动过。过两天美术说新字体不合适要换回旧字体你只能重新把所有对象再点一遍。这时候已经不是体力活了是纯粹的折磨。还有一个隐藏坑包体膨胀。很多项目的Prefab里散落着各种字体引用Unity在做资源打包时会把被引用的字体一起打进去。如果项目里混用了三四种字体每个字体动辄几MB甚至二十几MB漏一次换字体就会让包体白涨一大截。1.2 为什么不能依赖运行时替换作为主方案聊完手动操作的痛可能有人会说“我直接写个启动脚本场景加载完遍历所有Text罩一遍字体不也是一键吗”这个思路确实能解决一部分问题我以前也这么干过但它只能作为应急方案不能成为正式的换字体流程。运行时替换有几个逃不掉的硬伤第一启动瞬间会闪字体。场景先以默认字体渲染几帧然后脚本才把所有Text换成目标字体。虽然时间很短但在低端机上会被明显感知到尤其是有开场动画的项目第一帧UI字体就露馅。第二动态加载的UI覆盖不全。项目里用AssetBundle或Resources动态加载的Prefab不一定经过统一的初始化入口。只要有一个加载路径遗漏了字体替换逻辑那个界面就是旧字体排查起来比手动改还痛苦。第三编辑器下不是最终效果。美术和程序员在编辑器里看到的UI字体仍然是默认字体只有运行起来才是新字体。所有需要看静态效果的工作流都会受影响比如UI出图、Prefab对比、场景审查全都会出现“编辑器里一个样子运行时另一个样子”的割裂感。所以正确的做法是直接在资源层完成字体替换。让场景、Prefab、AssetBundle最终引用到的字体都是目标字体这样打开项目看到的就是最终效果出包也不会有任何运行时替换的痕迹。这也是下面这套编辑器工具的核心思路。2. 一键换字体工具要覆盖什么2.1 对象范围UGUI、TextMeshPro、3D TextMesh一个都不能少写工具之前我得先梳理清楚项目里到底有哪些组件在存字体引用。很多人只盯着UGUI的Text组件但实际项目里还有另外两类对象组件类型保存字体的字段常见坑UGUITextfont很多Text被默认字体引用占位运行时会做字体内置TextMeshPro/TextMeshProUGUIfont/fontMaterial存的是TMP_FontAsset和UGUI的Font是两套体系3DTextMeshfont旧项目遗留的老UI数量不多但很容易漏如果项目已经大规模使用TextMeshPro那么工具必须同时支持TMP_Text。TMP_Text是TextMeshPro和TextMeshProUGUI的共同基类所以遍历的时候直接遍历TMP_Text就能把两类组件一网打尽非常方便。另外还需要区分一个概念UGUI的Text组件存的是UnityEngine.FontTextMeshPro存的是TMP_FontAsset这俩不是同一个东西。手动改的时候最怕的就是混着来你把一个TTF字体文件拖到TMP组件上Unity根本不会接受反过来你把TMP的FontAsset拖到UGUI的Text上一样报错。工具里如果把两种字体类型分开配置就能避免这种低级错误。2.2 选型为什么用Editor工具而不是AssetBundle策略确定要做资源层替换后还有一条路可以走不改Prefab而是做一个逻辑层在运行时按字体Key去统一解释所有Text。这条路在一些大型项目里也有人用但它要求所有UI文本在初始化的时侯走一套统一的FontProvider对老项目来说改造量巨大而且依然绕不开运行时字体流转的问题。我更推荐用Editor下的批处理脚本原因有几个改完之后Prefab和场景就是最终状态版本控制工具能明确看到“哪些Prefab被改动”审查和回滚都方便。不依赖运行时框架任何不需要写代码的同事也能操作打开工具、选字体、点按钮、完成。可以生成详细的替换日志方便QA核对和后续排查。如果团队用Git或SVN每一次批量替换都是一个清晰的提交点不会像运行时代码那样每次启动都隐式执行。2.3 工作流设计预览、执行、日志三步走工具不是越糙越好我设计的时候特意加了一个“只生成替换清单不执行”的模式。原因很简单批量操作是高风险操作如果在几万个对象里替换错了字体回滚成本不低。所以工作流应该是这样选择目标字体UGUI字体和TMP字体分别指定。点击“只生成替换清单”工具扫描所有场景和Prefab打印出将要修改的组件路径、当前字体、目标字体。确认清单无误再点击“开始一键替换”。替换完成后输出汇总日志关键信息写入CSV文件方便后续需要回滚时使用。这个三步流程看起来多了一步但实际用起来反而更省心。用户可以先用预览确认自己的搜索目录对不对、字体选得对不对避免一把梭之后发现选错了字体又来回找。3. 完整实现一键替换场景与预制体中的Text字体3.1 准备工作版本要求与字体导入这套工具要求Unity 2018.3以上版本因为PrefabUtility.LoadPrefabContents是从这个版本开始提供的。如果你的项目还在用更老的版本建议先把Unity升级到LTS再迁移这套流程不然Prefab资源层的批量修改会非常别扭。开始前先把目标字体导入到工程里。字体文件建议单独放在一个目录例如Assets/Art/Fonts不要和UI图片混在一起。选中字体后需要关注Inspector里的导入设置如果目标是中文字体建议使用Dynamic动态字体模式这样运行时会按需渲染字形包体不需要把所有中文都烘焙进纹理。如果目标和项目已有字体替换注意确认字体导入设置里的Character Set是否覆盖了项目中用到的字符。很多中文字体切成静态模式后字符集不够UI就会出现整片空白。这一点在后面的坑里会详细展开这里只要记住换字体前先确认字体自己的导入设置是正常的否则替换完才发现显示不出来很容易误以为是工具的问题。3.2 创建工具窗口与入口我用一个EditorWindow作为工具外壳比直接放菜单项更好用因为可以在窗口里配置搜索目录、选择字体、选择处理范围。完整入口代码如下using System.IO; using UnityEditor; using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEditor.SceneManagement; public class FastFontReplacerWindow : EditorWindow { private Font uguiTargetFont; private TMP_FontAsset tmpTargetFont; private bool replaceUGUI true; private bool replaceTMP true; private bool includeOpenScenes true; private bool includePrefabs true; private string searchFolder Assets; [MenuItem(Tools/Fast Font Replacer)] private static void Open() { var window GetWindowFastFontReplacerWindow(); window.titleContent new GUIContent(Fast Font Replacer); window.minSize new Vector2(480, 380); window.Show(); } private void OnGUI() { EditorGUILayout.LabelField(目标字体设置, EditorStyles.boldLabel); if (replaceUGUI) { uguiTargetFont (Font)EditorGUILayout.ObjectField( UGUI Font, uguiTargetFont, typeof(Font), false); } if (replaceTMP) { tmpTargetFont (TMP_FontAsset)EditorGUILayout.ObjectField( TMP FontAsset, tmpTargetFont, typeof(TMP_FontAsset), false); } EditorGUILayout.Space(); EditorGUILayout.LabelField(替换范围, EditorStyles.boldLabel); replaceUGUI EditorGUILayout.Toggle(替换 UGUI Text, replaceUGUI); replaceTMP EditorGUILayout.Toggle(替换 TextMeshPro, replaceTMP); includeOpenScenes EditorGUILayout.Toggle(处理已打开场景, includeOpenScenes); includePrefabs EditorGUILayout.Toggle(处理所有预制体, includePrefabs); searchFolder EditorGUILayout.TextField(搜索目录, searchFolder); EditorGUILayout.Space(); if (GUILayout.Button(开始一键替换, GUILayout.Height(40))) { ExecuteReplace(); } if (GUILayout.Button(只生成替换清单不执行, GUILayout.Height(24))) { GeneratePreview(); } EditorGUILayout.HelpBox( 如果项目没有导入 TextMeshPro请删除代码中的 TMPro 相关逻辑否则脚本会编译失败。, MessageType.Info); } }有一点提醒如果项目没有安装TextMeshProusing TMPro;这行会直接导致编译错误。要么给工具做成两个版本要么用条件编译符号最简单的办法是删掉TMP相关代码。现在的Unity版本基本都内置TMP包但老项目可能会遇到这个问题所以我在窗口里放了一个提示。3.3 场景内Text的批量替换代码场景内替换相对简单只要遍历当前打开的所有场景把每个场景根节点下的Text和TMP_Text找出来修改字体引用即可。使用EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()可以在替换前提示保存场景避免替换完又因为场景没保存而全部丢失。private void ReplaceInOpenScenes() { if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { Debug.LogWarning([FastFontReplacer] 用户取消了场景保存跳过场景替换。); return; } int changedCount 0; for (int i 0; i UnityEngine.SceneManagement.SceneManager.sceneCount; i) { var scene UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); if (!scene.isLoaded) continue; var roots scene.GetRootGameObjects(); foreach (var root in roots) { if (replaceUGUI) { var texts root.GetComponentsInChildrenText(true); foreach (var text in texts) { if (text.font null || text.font uguiTargetFont) continue; Undo.RecordObject(text, Replace Font); text.font uguiTargetFont; EditorUtility.SetDirty(text); changedCount; } } if (replaceTMP) { var tmps root.GetComponentsInChildrenTMP_Text(true); foreach (var tmp in tmps) { if (tmp.font null || tmp.font tmpTargetFont) continue; Undo.RecordObject(tmp, Replace TMP Font); tmp.font tmpTargetFont; EditorUtility.SetDirty(tmp); changedCount; } } } } Debug.Log($[FastFontReplacer] 场景替换完成修改组件数量{changedCount}); }这里有几个细节值得说明GetComponentsInChildrenText(true)中的true表示包含未激活对象很多UI在运行前是处于隐藏状态的如果漏掉它们之后一旦显示出来就是旧字体。Undo.RecordObject在编辑器工具里是必须的用户执行完发现不对可以按CtrlZ回退。EditorUtility.SetDirty让Unity意识到对象被修改了否则场景保存的时候可能不会写入改动的序列化数据。3.4 预制体资产的批量替换代码预制体替换是整套工具的核心也最容易写错。很多人第一反应是用AssetDatabase.LoadAssetAtPathGameObject(path)去加载Prefab资源但这样拿到的GameObject并不能很好地直接修改并保存。现代Unity推荐的写法是PrefabUtility.LoadPrefabContents它可以打开Prefab资源对应的临时编辑内容改完用PrefabUtility.SavePrefabAsset保存最后用PrefabUtility.UnloadPrefabContents释放。private void ReplaceInPrefabs() { if (string.IsNullOrEmpty(searchFolder)) { searchFolder Assets; } string[] guids AssetDatabase.FindAssets(t:Prefab, new[] { searchFolder }); int changedPrefabCount 0; int changedComponentCount 0; foreach (var guid in guids) { string path AssetDatabase.GUIDToAssetPath(guid); if (!AssetDatabase.GetMainAssetTypeAtPath(path).IsAssignableFrom(typeof(GameObject))) continue; GameObject root PrefabUtility.LoadPrefabContents(path); bool changed false; if (replaceUGUI) { var texts root.GetComponentsInChildrenText(true); foreach (var text in texts) { if (text.font null || text.font uguiTargetFont) continue; text.font uguiTargetFont; changed true; changedComponentCount; } } if (replaceTMP) { var tmps root.GetComponentsInChildrenTMP_Text(true); foreach (var tmp in tmps) { if (tmp.font null || tmp.font tmpTargetFont) continue; tmp.font tmpTargetFont; changed true; changedComponentCount; } } if (changed) { PrefabUtility.SavePrefabAsset(root); changedPrefabCount; Debug.Log($[FastFontReplacer] 已替换: {path}); } PrefabUtility.UnloadPrefabContents(root); } AssetDatabase.SaveAssets(); Debug.Log($[FastFontReplacer] 预制体替换完成修改预制体 {changedPrefabCount} 个修改组件 {changedComponentCount} 个); }为什么要用LoadPrefabContents而不是直接加载Prefab资源后修改因为LoadPrefabContents会为Prefab资源创建一个临时的Preview环境你拿到的GameObject是这份资产在Prefab Stage中的内容修改它并保存等价于直接修改Prefab资产本身。这样做的好处有两个第一不会污染当前打开的场景第二保存出来的结果就是干净的Prefab数据不会像老旧方案那样引入莫名其妙的实例覆盖记录。这里还要注意一个问题PrefabUtility.SavePrefabAsset(root)之后root这个对象其实已经不在Prefab Stage中了所以必须在保存完成后马上调用PrefabUtility.UnloadPrefabContents(root)释放资源。如果顺序反了或者漏了Unload编辑器会出现临时实例泄漏影响后续操作。3.5 执行入口与汇总把场景替换和预制体替换组合起来就是最终的一键入口private void ExecuteReplace() { if (replaceUGUI uguiTargetFont null) { EditorUtility.DisplayDialog(提示, 请先指定目标 UGUI 字体。, OK); return; } if (replaceTMP tmpTargetFont null) { EditorUtility.DisplayDialog(提示, 请先指定目标 TMP FontAsset。, OK); return; } if (includeOpenScenes) { ReplaceInOpenScenes(); } if (includePrefabs) { ReplaceInPrefabs(); } Debug.Log([FastFontReplacer] 全部替换流程执行完成请留意上方日志并检查结果。); }预览模式GeneratePreview的原理和执行模式几乎一样只是把所有赋值操作替换成日志输出代码这里不重复贴了。核心差别只有一个预览模式下绝不调用SavePrefabAsset避免误改资源。4. 实测中踩过的坑与处理细节4.1 Prefab覆盖记录为什么执行完还是旧字体我第一次把工具跑完满心欢喜打开场景一看很多Prefab实例还是旧字体。原因就是前面提到的Prefab实例覆盖记录。场景里如果有一个Prefab实例它在导入场景后被手动改过字体那么实例上会存一条Font覆盖属性。这种情况下即使你改了Prefab资产这个实例依然会用自己覆盖的字体表现得毫不动摇。解决思路有两个取决于你的项目情况如果项目允许所有Prefab实例的字体统一跟随Prefab资产那么在替换工具执行完后专门跑一遍“清除字体覆盖”的逻辑。如果项目里有某些UI确实需要单独保持不同字体那就不能一刀切Revert应该把“保留覆盖字体”改成工具的例外规则。清除覆盖可以用PrefabUtility.RevertPrefabInstance但要注意它会把整个Prefab实例上所有被覆盖的属性全部还原不只是字体。如果有其他有意的差异比如某个实例的宽度特意调过也会一并丢危险性很高。更温和一点的办法是只针对字体相关属性做Revert。可以在编辑器的Prefab Overrides窗口里看到属性路径原理是基于PrefabUtility.GetPropertyModifications遍历Modification数据只把路径匹配字体字段的Modification移除。但这个方法对嵌套组件和数组元素的下标解析比较繁琐需要针对自己的Prefab结构调试。实际项目里大多数情况下使用“统一跟随Prefab资产”的规范即可个别特例手动处理反而更安全。4.2 动态字体与静态字体的选择直接决定成败换完字体之后最常遇到的反馈是“UI全变成方块了。”这种情况九成是字体导入设置的问题。中文字体文件体积通常很大如果导入时选了静态字符集Unicode或ASCII字体里只烘焙了包含的字符项目里的某个生僻字或者特殊符号不在字符集里渲染出来就是一个方块或者空白。我的建议是如果你替换的是中文字体优先用动态字体模式。在字体导入设置里Character Set选Dynamic同时勾选Include Font Data。这样Unity会在运行时用系统字体渲染字形不会因为字符集不完全而缺字。动态字体也有代价首次渲染新字形时会有一次纹理重建对UI加载会产生一点开销而且不同平台对字体的回退渲染效果有差异。不过对于大多数常规UI场景这点开销完全可控。如果项目对包体和内存极其敏感再考虑静态字体但一定提前确认字符覆盖范围。还有一点容易被忽略TMP的中文字体替换不只是选一个TMP_FontAsset那么简单。如果你用的是动态字体模式的TMP需要确保生成的TMP_FontAsset包含的字形覆盖所有界面文本如果用的是静态模式字形贴图的范围更要仔细检查。一键替换工具只负责把引用指向目标字体字体本身是否能用得靠导入设置保证。4.3 替换后文字不显示不一定是你工具写错了有一次我替换完字体Scene视图里大量Text直接消失。一开始以为是脚本把字体引用弄坏了排查了半天发现是目标字体的材质有问题。UGUI的Text组件默认使用UI/DefaultShaderUnity会为字体资源自动生成材质。但如果字体资源是因为某些异常情况导入的比如从别的项目复制过来、或者.meta文件缺失导致重新导入Unity不会自动帮它重建材质。这种情况下Text会判定为没有可用字体直接放弃渲染。遇到文字消失按照下面顺序排查选中目标字体资源确认Inspector里能看到Font Texture那张预览图。如果纹理是空的说明字体导入异常重新导入一次。检查Font资源下的Material子资源是否存在不存在的话手动创建一个默认字体材质拖回Font.material。运行项目后看Console有没有字体相关的报错比如Font texture is null之类的提示。在真机上测试确认不是编辑器显示缓存的问题。另外一个更隐蔽的问题是如果项目里原来使用的是系统默认字体某些版本在移动平台运行时可能找不到原名对应的系统字体导致回退到系统默认中文字体。这个现象很难在编辑器里复现只有真机能看出来。所以一键换完字体后不要只在编辑器里检查就完事打一个包到目标平台验证一下才是完整流程。4.4 嵌套Prefab、Prefab变体与Prefab Stage冲突处理Prefab时嵌套Prefab和Prefab变体是绕不开的复杂性。先说嵌套Prefab。一个页面Prefab里可能嵌套了十个子Prefab子Prefab里又各有自己的Text。如果你只扫外层页面PrefabGetComponentsInChildren会包含嵌套的子Prefab实例但你修改到的是“嵌套实例上的覆盖”而不是子Prefab资产本身。如果只跑一遍工具有些字体变化会以Override的形式挂在嵌套实例上而不是直接改到最小单位的Prefab里。好在FindAssets会扫描到所有Prefab资产包括这些子Prefab。如果你的工具遍历的是全项目所有Prefab那么子Prefab本身也会被处理一次所以绝大部分改动会正确落在资源层。真正要留意的只是那些没有被FindAssets覆盖到的特殊情况比如Prefab被放在了无效目录、或者已经被标记为只读。再说Prefab变体。变体Variant继承自Base Prefab如果变体本身没有字体覆盖改Base Prefab就能影响所有变体如果变体曾经手动覆盖过字体就算Base改了变体也依然保持自己的覆盖。工具很难自动判断“这个覆盖该不该清”我的经验是直接处理所有Prefab包括Variant让变体自己保存一份目标字体引用。这样改动直观不会出现继承关系的暗坑。还有最后一个操作纪律执行替换前一定要确保没有处于Prefab Mode。如果你正在某个Prefab的编辑界面里开着Prefab Stage再调用LoadPrefabContents加载其他Prefab偶尔会遇到“当前Prefab Stage冲突”的编辑器警告。我是在工具执行入口加了一步提示如果PrefabStageUtility.GetCurrentPrefabStage()不为空就先提醒用户关闭当前Prefab编辑模式再执行。这个检查虽然简单但能省掉很多环境相关的诡异报错。5. 扩展字体映射与一键回滚5.1 记录新旧字体映射清单一键替换最大的风险是换完之后发现字体效果不符合预期想回滚但没人记得原来是什么字体。所以我给工具加了一个日志导出功能。替换执行时把“哪个组件、原来的字体路径、新的字体路径”全部写入CSV文件。CSV格式类似这样PrefabPath,ObjectPath,ComponentType,OldFontPath,NewFontPath Assets/Prefabs/UI/Login.prefab,Canvas/Panel/Title,UnityEngine.UI.Text,Assets/Fonts/OldFont.ttf,Assets/Fonts/NewFont.ttf Assets/Prefabs/UI/Login.prefab,Canvas/Panel/Desc,TMPro.TMP_Text,Assets/Fonts/OldSDF.asset,Assets/Fonts/NewSDF.asset导出的时机有两个预览阶段和执行阶段都导出。预览阶段的CSV告诉你“如果你执行会改哪些东西”执行阶段的CSV记录的是“实际改了哪些东西”。一份留档一份给测试团队核对。先扫描再导出的逻辑和上面替换代码重叠度很高不过要注意一点不要在执行循环里频繁打开文件流写盘几百个Prefab跑下来IO开销会拖慢工具。正确做法是把所有日志行先存在Liststring里最后统一用File.WriteAllLines写入。5.2 回滚工具的实现思路回滚的本质就是把替换方向反过来目标是旧字体被替换的是新字体。最省事的实现是再写一个ReverseFontReplacerWindow或者给现有窗口加一个模式开关“执行反替换”。因为替换逻辑是一样的只是uguiTargetFont变成了日志里的旧字体tmpTargetFont变成日志里的旧TMP字体。如果CSV记录完整回滚时甚至可以只针对日志中出现过的Prefab路径操作避免全项目重新扫描。但有一个限制如果某次替换后又做了其他UI改动直接通过CSV做反替换可能会覆盖掉后续的新改动。所以我的建议是回滚操作前还是先生成一份新的预览清单看清楚要改哪些对象尤其是那些已经被后续工作再次修改过的Prefab要人工确认一下。如果你的项目不需要这么重的回滚还有一个轻量方案在替换前把所有会被修改的Prefab目录用版本控制工具整体标记一次。Git提交一个“换字体前”的commitSVN打一个快照。之后想回滚直接版本库回退即可。CSV日志更多是帮助定位“为什么这个对象没变”和“这个对象到底什么时候被改的”。5.3 还能往哪些方向扩展这套工具我在实际项目里已经跑了不止一次后来还顺手加过几个扩展点给同样需要的同事参考限定目录扫描AssetDatabase.FindAssets的第三个参数支持目录数组如果只想替换某个模块的子目录就不需要扫全工程速度快很多。按文本内容过滤只替换text属性里包含中文的Text。做海外版本的时候英文UI保持原字体只把中文UI切到中文字体这个功能很好用。替换后自动检查执行完再扫一遍如果还有旧字体引用就打印警告。相当于给整个操作加了一个自检环节。和版本控制联动替换前自动AssetDatabase.MakeEditable签出Prefab避免文件处于只读状态导致保存失败。这些扩展都不复杂核心逻辑还是那两件事扫对象、改字体引用。只要把第一版工具跑通剩下都是修修补补的事。最后再分享一点我自己的体会工具不在多好看而在敢用。一键换字体这种工具最重要的不是代码多精巧而是在执行前让你能预览、执行后有日志、出错时能回滚。做到这三点任何项目里你都可以放心按那一下按钮。如果你现在也正在被几百个Prefab的字体替换折磨直接照着这套思路攒一个小工具大概率半小时内就能告别手动改字体的日子。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑