1. 为什么我会写这样一个包体优化工具做过几个移动端项目之后我对包体这件事的敏感度是被一次次提审和一次次发热反馈硬生生磨出来的。项目刚立项的时候美术资源随便丢PNG 一张两三千 KB图集想打多大打多大动画曲线全精度保留编辑器里跑得挺欢等到出包一看几百兆的包体应用商店那边的体积提示直接拉满玩家下载意愿掉一大截。更难受的是运行期内存贴图不压缩、图集不合并DrawCall 飙到几百低端机上跑起来风扇都开始转。后来我就想与其每次出包前手动去翻资源、挨个调导入设置、手动打图集不如把这件事做成一套Unity编辑器扩展一键扫描、一键处理、一键校验。这个工具的核心目标就三件事图片压缩、批量生成图集与图集变体、动画压缩。它解决的是包体过大、内存占用高、DrawCall 过多这三类问题适合所有用 Unity 做移动端或者小游戏方向的开发者尤其是美术资源量大、迭代频繁、又没有专职 TA 的团队。我先把结论摆前面这套东西不是靠某一个“黑科技”把包体砍一半而是靠一堆正确的小决策叠加起来。图片的导入格式、Max Size、压缩质量怎么设图集的 Packing Tag、Padding、Allow Rotation、变体怎么组织动画的压缩误差、关键帧精简、曲线保留策略怎么定。每一项单独看只省几 MB 或者几 MB 内存几十上百个资源叠起来就是几十兆包体和上百兆内存的差距。下面我把自己这套扩展的设计思路、关键实现和踩过的坑完整梳理一遍代码部分都给了可以直接改的骨架。2. 工具整体设计与三个模块的取舍2.1 为什么用编辑器扩展而不是运行时脚本第一件要想清楚的事为什么这套东西必须是编辑器扩展而不是运行时脚本或者后处理脚本。原因很简单包体优化本质上是构建期行为。图片的压缩发生在导入管线里图集的生成发生在构建前动画的压缩发生在资源导入或构建时。这些节点都发生在 Editor 域运行时你改不了已经打进包里的资源格式。Unity 有一套很成熟的编辑器扩展点AssetPostprocessor可以拦截资源导入MenuItem和EditorWindow可以做工具界面IPreprocessBuildWithReport可以在构建前做批量处理AssetDatabase系列 API 可以做批量资源操作。把这几个点串起来就是一个完整的构建期优化流水线。用运行时脚本去动态降质除了把 CPU 吃满之外几乎没有收益而且还会增加不确定性所以我从最开始就把范围锁死在编辑器扩展里。另一个取舍是自动改还是扫描加建议。一开始我想做全自动扫到不合理的设置直接改。实测下来这是灾难因为自动改会把美术同学精心调过的特殊资源一并改掉比如某些 UI 贴图故意用了不压缩格式以保证边缘锐利。后来我改成混合策略默认是扫描并生成报告报告里标出哪些资源有问题、建议改成什么确认之后可以选择性批量应用。安全第一效率第二这个顺序千万别反。2.2 三大模块的职责边界工具分成三个模块职责边界要划清楚不然代码会越来越乱。图片压缩模块负责单张 Texture2D 的导入设置优化。它扫描的是TextureImporter关注的点是纹理类型、压缩格式、Max Size、是否生成 Mipmap、是否可读、平台覆盖设置。这个模块的输出是“每张图应该用什么格式、多大尺寸”。图集模块负责 SpriteAtlas 的批量创建和变体生成。它关注的是 Packing Tag 的组织、图集分组策略、Padding 大小、是否允许旋转和紧凑打包、以及不同平台分辨率下需要的变体图集。这个模块的输出是“哪些 Sprite 该进哪个图集、图集的参数怎么定”。动画压缩模块负责 AnimationClip 的精度优化。它关注的是曲线压缩误差、关键帧密度、冗余关键帧剔除、以及Animation窗口里的压缩开关。这个模块的输出是“这个 Clip 的压缩误差应该设多少、哪些曲线可以直接砍掉”。三个模块互相独立可以单独跑但共享一套报告和日志系统。这样一个模块出问题时不会拖垮另外两个也方便后续按需扩展比如以后加模型网格压缩、音频压缩只要往这套框架里塞新模块就行。2.3 报告系统为什么值得单独做我见过很多人写编辑器工具就是一堆 MenuItem点完弹个框说“处理完成”然后没了。这种工具最大的问题是不可验证。你改了哪些资源、改前改后是什么、有没有误伤完全不知道。所以这套工具里我花了不少时间做一个报告系统。每次扫描和批量操作都会生成一份结构化的记录包含资源路径、原值、目标值、预计收益、是否实际修改。这份报告既能导出成 CSV 给主程看也能在工具窗口里直接预览和回滚。听起来是额外开销但实际用下来它救过我两次一次是误把一批法线贴图当普通贴图压了报告里对不上一眼就看出来了另一次是提审前想确认到底优化了多少直接翻报告就有数据不用重新跑一遍。提示报告一定要记录“原值”不然改错了想回滚都回不去。这是编辑器批量工具的底线设计。3. 图片压缩模块从导入设置里榨出每一MB3.1 纹理导入设置里真正影响包体的参数Texture 面板里参数很多但真正影响包体和内存的就那么几个。我把它们按影响权重大致排一下方便你判断优先级。参数影响方向典型收益备注Texture Type决定默认压缩策略大Sprite/UI/Normal Map 差异很大Compression压缩格式极大ASTC/ETC2 决定体积基本盘Max Size纹理最大边长极大减半尺寸等于体积降到约 1/4Generate Mip Maps是否生成多级渐远纹理中会额外增加约 33% 体积Read/Write Enabled是否保留 CPU 可读副本大开启会让内存翻倍Platform Override分平台格式大Android 和 iOS 策略不同这里最容易被忽视的是 Max Size 和 Read/Write。Max Size 2048 改成 1024面积降到四分之一压缩后体积也大体按这个比例降。Read/Write 开启会在内存里额外保留一份未压缩的副本一张 2048 的 RGBA32 图就是 16MB关掉直接省下来。很多项目里 Read/Write 是稀里糊涂被勾上的尤其是从某些脚本里GetPixels的代码带出来的习惯。Compression 这块要说清楚。移动端主力格式是ASTC比较新的设备和ETC2兼容性更好。ASTC 支持更多块尺寸比如 6x6、8x8块越大压缩率越高、质量越低。ETC2 基本就是 4x4 的 RGBA 和 RGB 变体。对于 UI 和图标这类对清晰度要求高、但颜色梯度平缓的图ASTC 6x6 通常够用对于画质向的贴图可能需要 4x4。Android 上如果还要兼容很老的机器可能得保留 ETC2 兜底。3.2 扫描逻辑怎么写才不误伤扫描的核心是遍历目标目录下所有 Texture2D读它们的TextureImporter然后按一套规则判定是否“有问题”。规则不能一刀切要按资源用途分类。我用的分类依据主要是路径关键词和当前 Texture Type。// 遍历并收集纹理导入信息简化骨架 public static ListTextureReport ScanTextures(string[] searchFolders) { var result new ListTextureReport(); var guids AssetDatabase.FindAssets(t:Texture2D, searchFolders); foreach (var guid in guids) { var path AssetDatabase.GUIDToAssetPath(guid); var importer AssetImporter.GetAtPath(path) as TextureImporter; if (importer null) continue; var report new TextureReport { Path path, TextureType importer.textureType, MaxSize importer.maxTextureSize, Compression importer.textureCompression, MipmapEnabled importer.mipmapEnabled, IsReadable importer.isReadable, }; result.Add(report); } return result; }判定规则我分了三类。UI 类路径含 UI、Icon、Atlas 之类或者 Texture Type 是 Sprite要求 Mipmap 关闭、Read/Write 关闭、Max Size 不超过 2048、压缩用 ASTC 或 ETC2。场景贴图类Mipmap 通常要开、Max Size 按重要度分档。法线贴图类名称含 Normal、_N必须用 Texture Type Normal Map否则压缩会把法线方向搞坏这是个大坑。注意千万不要按文件名里有没有 “Normal” 这种简单字符串去判定然后自动改因为 Normal 这个词太常见了。宁可标成“疑似”让人工确认一遍。3.3 批量修改导入设置的正确姿势批量修改导入设置有个坑直接给importer.maxTextureSize赋值之后必须调用importer.SaveAndReimport()否则不生效。但SaveAndReimport是同步阻塞的几百张图一起跑会卡编辑器很久还会触发多次全量重导入。我的做法是分批处理每批 50 张左右每批之间用EditorApplication.delayCall让编辑器喘口气同时在界面上显示进度。另外修改的时候可以先用AssetImporter.GetAtPath拿到 importer统一改完之后再一次性SaveAndReimport但实测下来 Unity 在SaveAndReimport时会按资源逐个处理所以主要还是靠分批来缓解卡顿。// 批量应用建议设置分批 进度 public static void ApplyBatch(ListTextureReport targets, int batchSize 50) { for (int i 0; i targets.Count; i batchSize) { var batch targets.Skip(i).Take(batchSize).ToList(); foreach (var t in batch) { var importer AssetImporter.GetAtPath(t.Path) as TextureImporter; if (importer null) continue; importer.maxTextureSize t.SuggestMaxSize; importer.textureCompression t.SuggestCompression; importer.mipmapEnabled t.SuggestMipmap; importer.isReadable false; importer.SaveAndReimport(); } EditorUtility.DisplayProgressBar(批量处理, t.Path, (float)i / targets.Count); } EditorUtility.ClearProgressBar(); }还有一点要提醒修改导入设置会触发资源重新导入如果这个资源被图集引用图集也会重新打包。所以图片压缩和图集这两个模块的执行顺序很关键正确顺序是先做图片压缩再做图集生成不然图集白打一遍。这个顺序问题我在工具里用执行阶段的约束固定下来了不允许乱序。3.4 一个容易被忽略的收益点尺寸对齐有个细节很多人不知道纹理尺寸最好是 2 的幂。不是所有平台都强制要求但很多移动 GPU 对非 2 的幂纹理处理效率更低也会影响 Mipmap 生成质量。更实际的是非 2 的幂纹理在压缩时可能会被自动补齐到最近的 2 的幂白白浪费空间。我见过一批 1300x1300 的图实际压缩时被补到 2048浪费了将近一半。所以工具里我加了个检查如果纹理宽高不是 2 的幂且和最近的 2 的幂差距很小就建议直接裁剪或缩放过去。具体的最近 2 的幂可以用位运算快速算或者用Mathf.NextPowerOfTwo。当然UI 里有些图的尺寸是设计稿定死的不能随便改这种就跳过只做提示。工具的价值在于帮你发现而不是替你拍板。4. 图集与图集变体DrawCall 和包体的双重账4.1 图集分组策略按用途而不是按文件夹SpriteAtlas 的组织方式直接决定 DrawCall 表现。最常见的错误是按文件夹分组A 目录的图打一个图集B 目录的图打一个图集。结果运行期一个界面同时用到 A 和 B 的资源两个图集来回切换DrawCall 就上去了。正确的分组依据是运行期同时出现的资源。同一个界面、同一套特效、同一个角色的资源应该打进同一个图集。实现上有两种方式一种是手动给 Sprite 设 Packing Tag在 Sprite 的 Inspector 里改 Packing Tag 字段另一种是用工具批量按规则设置。我的工具里做了个配置表你可以在工具窗口里定义“图集名 - 路径规则/Packing Tag”的映射然后一键把匹配到的 Sprite 的 Packing Tag 批量刷成目标值。这样调整分组策略时不用一张张手点。4.2 图集参数怎么调Padding、Rotation 与 Tight PackingSpriteAtlas 的参数不多但每个都有讲究。Padding内边距图集里每张 Sprite 之间留的空隙。设太小在某些采样情况下边缘会串色尤其是 UI 做九宫格拉伸的时候。设太大浪费图集空间。我的经验值是 2 到 4 像素UI 用 4普通 Sprite 用 2。这个值不是越大越安全因为它会显著影响打包密度。Allow Rotation允许旋转打包时允许旋转 Sprite 以达到更高密度。听起来很好但对 UI 是毒药因为旋转后的 Sprite 在某些 UI 布局和九宫格下会出现方向错误。所以我的规则是UI 图集关闭 Allow Rotation纯图标类图集可以开。Tight Packing紧凑打包按 Sprite 的透明区域实际轮廓打包而不是按矩形。密度更高但对 UI 不友好因为会改变 Sprite 的 UV 范围九宫格和切片可能出问题。UI 一律关闭。还有一个很多人忽略的参数是Filter Mode 和 Compression。图集本身的压缩格式会影响最终贴图质量通常和内部 Sprite 的格式保持一致即可但图集级别的 Compression 设置优先级更高需要在图集组件上确认。4.3 图集变体一份资源适配多种分辨率图集变体SpriteAtlas Variant是为了适配不同分辨率和不同设备画质档位。同一套 UI在高配机上用 2 倍图在低配机上用 1 倍图就是通过变体实现的。生成变体的方式有两种。一种是手动在 Project 窗口右键创建 Sprite Atlas Variant然后指定 Master 图集另一种是工具批量生成。批量生成的逻辑是给定一个 Master 图集按缩放比例生成若干变体每个变体指定自己的缩放系数和平台覆盖。// 批量生成图集变体简化骨架 public static void CreateVariants(SpriteAtlas master, float[] scales) { var masterPath AssetDatabase.GetAssetPath(master); var dir Path.GetDirectoryName(masterPath); foreach (var scale in scales) { var variantPath Path.Combine(dir, Path.GetFileNameWithoutExtension(masterPath) _x scale .spriteatlasv2); // 创建变体资产并设置 master 与缩放 var variant new SpriteAtlasAsset(); // 视版本 API 调整 // 设置 variant 的 master 引用和 scale AssetDatabase.CreateAsset(variant, variantPath); } AssetDatabase.SaveAssets(); }这里的 API 在不同 Unity 版本里有点差异SpriteAtlas 的 V1 和 V2 资产类型不同操作方式也不一样。V2Unity 2021 之后默认的操作很多要走SpriteAtlasAsset和序列化字段比较绕。我的建议是先在编辑器里手动创建一个变体看看它对应的资产文件和序列化结构再照着写代码比死磕 API 文档快得多。4.4 变体的实际收益与常见误用变体的收益是双面的高配机用高清图保证画质低配机用低清图省内存和带宽。但这里有个常见的误用把变体当成了动态切换的工具。变体是构建期的运行期换分辨率并不会自动切换变体需要你在运行时按设备加载对应的变体这涉及到 Addressables 或者自己管理图集加载逻辑。还有一点变体会独立占用包体。如果你给一套图集生成了 3 个变体包里就有 4 份数据Master 加 3 个变体除非你用 AssetBundle 或 Addressables 按需下发。所以变体不是越多越好通常 2 档高清和低清就够追求极致可以 3 档。提示如果项目规模不大变体的复杂度收益比可能不划算。先确认你的用户设备跨度是否真的很大再决定要不要上变体。5. 动画压缩把曲线精度降到刚好够用5.1 动画压缩到底在压什么AnimationClip 的数据主要是曲线Curve每条曲线记录某个属性在某帧的值。一段动画里可能包含成百上千条曲线位移、旋转、缩放、骨骼上的各种属性。默认情况下这些曲线是全精度、关键帧密集的一段 2 秒的动画可能有几十上百帧每帧每属性都存数据量非常可观。压缩动画主要靠两件事降低采样精度和剔除冗余关键帧。前者是把关键帧之间的插值误差放大到可接受范围后者是把那些对最终表现没影响的中间帧删掉。Unity 的 Animation 导入设置里有压缩相关选项可以设旋转误差、位置误差、缩放误差误差设得越大压缩越狠。这里的关键是“刚好够用”。误差设得太大动作会抖、会飘设得太小压不下去。找到这个平衡点就是动画压缩的核心工作。5.2 压缩误差怎么定从视觉容忍度反推误差值的单位是角度旋转或者距离位移、缩放。我的经验起点是旋转误差 0.5 到 1 度位置误差 0.01 到 0.05缩放误差 0.01 左右。这是从“人眼对旋转最敏感、对微小位移相对宽容”这个规律反推出来的。但这是起点不是终点。不同类型的动画容忍度差别很大角色主 Idle 动作稍大误差可能就能看出来背景飘落的花瓣误差稍微大点完全没感觉。所以工具里我做了按动画类型分档的配置把动画按使用场景分类不同类用不同误差档位。更聪明的做法是逐条曲线判定。Unity 的 AnimationClip 可以通过AnimationUtility.GetCurveBindings和GetEditorCurve拿到每条曲线的关键帧数据工具可以分析曲线的变化幅度如果一条曲线从头到尾值几乎没变比如某根骨骼全程没动那这条曲线可以直接删掉零误差、零影响、纯赚。这种“死曲线剔除”是动画压缩里性价比最高的操作。// 剔除几乎无变化的曲线简化骨架 public static void RemoveFlatCurves(AnimationClip clip, float threshold 0.0001f) { var bindings AnimationUtility.GetCurveBindings(clip); foreach (var binding in bindings) { var curve AnimationUtility.GetEditorCurve(clip, binding); if (curve null) { // 可能是常量曲线直接移除 AnimationUtility.SetEditorCurve(clip, binding, null); continue; } float min float.MaxValue, max float.MinValue; foreach (var key in curve.keys) { min Mathf.Min(min, key.value); max Mathf.Max(max, key.value); } if (max - min threshold) { AnimationUtility.SetEditorCurve(clip, binding, null); } } }这段代码在实际项目里帮我砍掉了大量“僵尸曲线”。很多动画在制作过程中某根骨骼被临时 K 过两下后来不用了但曲线还在这些曲线占的空间不小而且没有任何作用。5.3 关键帧精简与曲线预计算除了删死曲线还能做关键帧精简。思路是如果一条曲线在两个关键帧之间的变化是线性的那中间的关键帧就是多余的可以删掉。Unity 的曲线压缩算法本身就是这么干的但你可以先手动做一轮预处理把关键帧间隔里明显冗余的删掉让后续压缩更有效。还有一种更激进的方式是烘焙成采样动画。如果你不需要精确的曲线数据比如动作是程序采样的可以把曲线烘焙成固定帧率的采样然后关掉曲线直接存采样点。这在某些场景下能省很多但灵活性会下降不适合需要螺旋、曲线插值的场景。我的取舍是先删死曲线再让 Unity 的压缩算法处理最后检查视觉表现。不轻易上烘焙因为烘焙会让后期改动画的灵活性大打折扣。5.4 动画压缩的验证闭环动画压缩最怕的就是压过头动作看起来“有点不对”但是说不清。所以我给工具加了个验证环节压缩前后都生成一份动作预览或者至少输出压缩率、被删曲线数、误差参数。更重要的是把压缩后的动画和原动画放在一起比对有条件的话让美术同学过一遍。还有一个实际经验动画压缩不要和动作迭代同时进行。美术还在调动作的时候你去压等于给他们的修改增加麻烦改完还得重压。正确时机是在动作定稿、进入包体优化的阶段再统一压一遍这时候变化少、风险低。注意动画压缩会直接改 AnimationClip 的序列化数据属于破坏性操作。执行前一定要有版本控制兜底或者先把原始 Clip 备份到另一个目录。6. 实操全流程从扫描到出报告的完整一遍6.1 前置准备与目录约定真正跑之前先做两件事。第一确保项目在版本控制下且工作区干净。整个流程会批量改资源没有版本控制兜底改错了只能哭。第二约定扫描目录。不要让工具扫全工程Assets 下还有 Plugins、第三方资源包扫进去容易误伤。我的习惯是把扫描目标限定在Assets/GameRes之类自己项目的资源根目录下。工具窗口里我给了几个输入扫描根目录支持多个、资源分类规则可选、是否包含子目录、是否只扫描未压缩资源。这些看起来是细节但它们直接决定误报率。我的经验是默认把规则收紧先只处理确定有问题的再逐步放宽比一开始就全量扫要稳得多。6.2 第一步全量扫描与报告生成扫描阶段不改任何资源只收集数据。工具会遍历所有 Texture2D、SpriteAtlas、AnimationClip对每一类执行对应的分析规则最后汇总成一份报告。报告里我最看重的三个字段是当前设置、建议设置、预计收益。预计收益不需要特别精确可以用一个粗略的估算公式纹理体积约等于宽乘高乘每像素字节数再按压缩比折算。这一步通常要跑几分钟取决于资源规模。工具里显示进度条避免看起来像卡死。跑完之后报告直接展示在窗口里可以按收益排序先处理收益高的。这种“先看总量再动手”的方式比盲目批量改要理性得多。6.3 第二步图片压缩批处理确认报告之后先处理图片。选择“应用图片建议”工具会按前面说的分批逻辑处理。这里有个实操细节处理过程中不要切窗口做别的重活因为每次 SaveAndReimport 都可能触发资源重新导入编辑器会卡。耐心等进度条走完。处理完之后工具会自动刷新一次报告显示改动前后的对比。如果发现某类资源被改得不对可以立刻回滚——这也是为什么前面强调报告要记原值。6.4 第三步图集批量生成与变体图片处理完再打图集顺序不能反。图集生成分两种入口一种是新建图集从零创建 SpriteAtlas 资产并把匹配的 Sprite 加进去另一种是刷新已有图集把仍然存在的 Sprite 保留、已删除的移除、新增的加进去。刷新这个功能特别有用因为项目迭代中资源增删频繁手动维护图集列表很痛苦。变体生成我放在最后因为它依赖 Master 图集已经打好了。按配置的缩放档位生成变体然后指定各变体的平台覆盖。生成完之后建议手动打开一个变体看一眼确认它引用到了正确的 Master别生成了一堆空壳。6.5 第四步动画压缩与最终校验动画放在最后因为它的改动最直接影响视觉表现需要预留验证时间。执行顺序是先删死曲线、再应用压缩误差、最后保存并重新导入。跑完之后挑几段关键动画在预览窗口里过一遍看有没有明显异常。全部处理完工具生成一份总结报告包含处理的资源数、包体预计减少量、内存预计减少量、被改动的资源清单。这份报告既可以给团队看也可以作为版本发布的记录。到这一步整个流程才算闭环。7. 常见问题与排查技巧实录7.1 图片压缩后出现模糊或条纹最常见的现象某张 UI 图压缩后边缘出现色块或者渐变区域出现条纹。原因通常有两个压缩格式选得太激进比如该用 4x4 却用了 8x8或者这张图本身就不该用有损压缩比如带文字的图、纯图标。排查方法是先临时换回 RGBA32 或高质量格式确认是不是压缩导致的如果是就针对这张图单独提高质量。还有一个隐蔽原因Max Size 设得太小导致放大采样。如果一张图实际需要显示得比 Max Size 还大压缩后会被放大小采样看起来就是糊的。这种要看实际的显示尺寸需求来定 Max Size。7.2 图集打包后 DrawCall 没降反升出现这种情况八成是图集分组不合理。同一界面用到的资源被分散到了多个图集里导致反而增加了切换。或者更糟图集变体的切换逻辑在运行时频繁来回切。排查方式是打开 Frame Debugger 或者 Profiler看实际的 DrawCall 归属找到那些“本该在同一个图集却不在”的 Sprite调整 Packing Tag。另一个可能原因是图集太大了比如打成了一个 4096 的巨型图集虽然资源都在一个图集里但因为超大图集的采样问题或者显存压力表现反而更差。这种情况要考虑把图集拆成合理大小。7.3 变体不生效或变体内容为空变体不生效通常是因为运行时的加载逻辑没有指定变体。变体是构建期资产你需要确保实际加载的是变体而不是 Master。检查 Addressables 配置或者你自定义的加载表中实际引用的是哪个图集。变体内容为空通常是Master 图集没有正确打包或者变体创建时没有正确关联 Master。前者检查 Master 是否有有效内容后者检查变体的 Master 引用字段。生成变体的代码里最容易漏的就是这一步序列化字段设错了生成出来的就是一个空壳。7.4 动画压缩后动作抖动或漂移抖动一般是误差设太大尤其是旋转误差。旋转是累积的0.5 度的误差在长动作里可能累积成明显偏差。出现抖动就把旋转误差往下调比如从 1 度降到 0.5 甚至 0.2。漂移通常是位置误差太大尤其是根骨骼位移位置误差要严格控制很多项目根骨骼位移干脆不压。排查的时候可以只看被压缩的曲线群逐组放开误差观察哪里先出问题。找到那条敏感曲线后要么单独放宽容差要么直接排除不压。7.5 编辑器卡死或处理时间过长批量操作编辑器卡死前面提过主要是SaveAndReimport和资源重导入导致的。除了分批还可以考虑只对有改动的资源执行重导入改动前后值一样的直接跳过。另外把处理放在EditorApplication.update的帧回调里分帧执行也能缓解卡顿。问题现象可能原因快速排查图片模糊/条纹压缩激进或损失格式不当换高质量格式对比DrawCall 不降反升图集分组不合理Frame Debugger 看归属变体不生效运行时未指定变体检查加载表/Addressables动画抖动旋转误差过大逐步调小误差观察编辑器卡死重导入过于集中分批 跳过无改动资源8. 我在这套工具上踩过的几个真实坑8.1 法线贴图被当成普通贴图压缩这是我早期最惨的一次。批量脚本按路径扫结果把一批法线贴图按普通贴图处理了压缩格式用了默认的有损格式。法线贴图对每个通道的精度都敏感一旦被有损压缩光照方向就会错乱场景里所有带法线的模型光照看起来都不对但又不至于爆掉属于那种“隐隐觉得哪里不对”的问题。花了大半天才定位到是压缩导致的。后来我加了强制规则Texture Type 是 Normal Map 的资源一律不允许批量改压缩格式只允许改 Max Size 和 Read/Write。并且报告里把这类资源单独标出来人工确认。8.2 图集刷新把手工组织的图集打散了图集刷新功能刚做出来的时候我想当然地按 Packing Tag 重新生成图集内容。结果把一些美术同学手工调整过布局顺序的图集打散了虽然功能上没差但美术那边看到图集内容全变了以为出了问题。后来我改了策略刷新默认只增减不动已有条目的顺序除非明确选择“重新组织”。这件事让我意识到编辑器工具的“自动”行为需要克制。你的工具在逻辑上正确不代表使用者能接受尤其是那些有视觉预期的操作。8.3 动画压缩和美术迭代撞车有次在美术还在调一套技能动作的时候我跑了动画压缩。过了两天美术改完动作重新导入之前的压缩全白做而且两边的曲线状态混在一起很难分辨哪些是压过的。后来我定了个规矩动画压缩只在版本冻结、进入提审准备时才跑平时不碰。8.4 忘了处理图集变体的平台覆盖第一次做变体的时候我生成了变体但没设平台覆盖结果所有平台都吃了全量变体包体不降反升。变体的目的是按平台或设备提供不同资源如果不配平台覆盖Unity 默认会都打进去。后来我在生成变体的逻辑里强制要求指定平台覆盖没指定的直接报错提示。9. 最后一点个人经验这套工具从最初的几十行脚本长到现在的规模最大的体会是编辑器工具的价值不在于功能多而在于把“正确但繁琐”的事情变简单同时不剥夺人的判断权。图片压缩、图集、动画压缩这三大块每一块的原理都不复杂难的是在项目里稳定地、可重复地执行并且出了问题能追溯、能回滚。我现在的工作流是每次大版本提审前先跑一遍扫描生成报告评估总量然后按图片、图集、动画的顺序跑批处理最后过一遍关键表现。整个过程一两个小时比早期手动优化一周还稳。工具本身的代码量不算大框架清晰的话后续加音频压缩、模型压缩也只是加模块的事。如果你也打算做类似的工具我的建议是先把报告和回滚这两个地基打好再去堆功能。不然工具越强大一次误操作的代价就越高。