Files
2026-09-10 17:29:24 +08:00

241 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UIPrefabCheck 能力清单与优化方案
> 源文件:`Assets/Editor/UIPrefabCheck.cs`(1434 行,本目录含只读副本,MD5 `74D4A63A33869AE560BA6B522E2DABFC`)
> 菜单入口:`Tools/UI/Prefab 和图片优化扫描工具`、`Tools/UI/界面性能巡检`
> 评估时间:2026-09-10 | **结论:工具本身设计扎实(只读、分页、LSH 相似图、dHash 带 alpha 包围盒),但对"降 DC"这个目标存在 4 个关键能力缺口,其中 2 个改动量很小、收益很大。**
---
## 第一章 它现在是什么(两个类,7 个标签页)
一个文件里两个独立部分:
| 类 | 行号 | 职责 | 运行时机 |
| --- | --- | --- | --- |
| `UIRuntimePerformanceAudit` | L17-478 | 界面性能巡检(自动逐个 Show/Close 面板 + 手动录制实际操作) | **必须 Play Mode** |
| `UIPrefabCheck : EditorWindow` | L480-1433 | Prefab / 图片 / 图集静态扫描(6 个页)+ 承载上面那个巡检页 | 编辑模式即可 |
### 1.1 七个标签页逐项能力
| 标签页 | 绘制方法 | 扫什么 | 判定标准 / 阈值 | 能回答什么问题 |
| --- | --- | --- | --- | --- |
| 引用明细 | `DrawReferencePage` L662 | 每个 prefab 里的 `Image.sprite` / `RawImage.texture` / `SpriteRenderer.sprite` | 图片不在 `gui/` 或 `PSD2UI/UI/` 下 → 标"非标准 UI 资源目录"(`IsStandardUiPath` L1431) | 某张图被哪个 prefab 的哪个节点用了、属于哪个图集(可点击定位图集) |
| 多预制体共用 | `DrawSharedPage` L696 | 按 `AssetPath` 分组,`PrefabPaths.Count >= 2` | — | 哪些图被多个 prefab 共用(共用度高 = 更该进同一个图集) |
| 疑似无用图片 | `DrawUnusedImagePage` L722 | `UnusedImageRoots`(L487-491:`gui` + `PSD2UI/UI`)下的 `t:Texture2D` | 既不在 `prefabDependencies`(仅来自 prefabRoot 下 prefab 的递归依赖,L895),也没有静态代码引用(`HasStaticCodeReference` L1148:guid / 全路径 / Resources 相对路径的**字面**子串) | 哪些图可能是老项目遗留的废图 |
| 节点统计 | `DrawNodeStatisticsPage` L760 | 每个 prefab 的 `Transform` 总数 | 超过 `GetSuggestedNodeCount` L942 的硬编码值(UI面板 150 / UI组件 50 / 特效 80 / 角色 100 / 通用 80)→ 标"建议关注"或"建议拆分" | 哪些 prefab 层级过深/节点冗余(影响 Rebuild 与合批) |
| 大图标记 | `DrawLargeImagePage` L796 | `UnusedImageRoots` 下的 `t:Texture2D` | 源文件 > 500 KB(L484)**或**分辨率 ≥ 1024×1024(L485) | 哪些图源文件太大 |
| 相似图 | `DrawSimilarImagePage` L831 | `UnusedImageRoots` 下的 png/jpg/jpeg(L1282) | dHash 64bit(**带 alpha 包围盒裁剪** L1311-1320,再采 8×9 亮度网格 L1323-1333)+ 宽高比差 ≤ 0.05(L1363)+ 汉明距离 ≤ 6(L486);用 8 字节分桶 LSH 避免 O(n²)(`FindSimilarImagePairs` L1346) | 哪些图是重复/近似重复(老项目遗留的典型症状) |
| 界面性能 | `UIRuntimePerformanceAudit.DrawEmbedded` L83 | 从 `GameInitWrapper` 源码正则提取 `AddItem<T>`(L176)发现所有注册面板 | 自动巡检:预热 10 帧 + 采样 60 帧(L19-20),输出 打开耗时 / 平均 / P95 / 最大帧耗时 / GC 峰值 / 关闭后残留对象数;手动录制:轮询可见面板,记录实际停留期间的帧耗时 | 哪个面板打开慢、哪个面板停留时拖帧、哪个面板关不干净 |
### 1.2 值得肯定的设计(不要在优化时破坏)
1. **全程只读**:`PrefabUtility.LoadPrefabContents` + `finally UnloadPrefabContents`(L908-916),不改 prefab、不改图集、不写 Undo。
2. **dHash 前先做 alpha 包围盒裁剪**(L1311-1320)——对 UI 图至关重要:UI 图周围大量透明,不裁剪的话所有图都"相似",误报会淹没结果。这一步做得比很多商业工具细。
3. **LSH 分桶代替两两比对**(L1346-1375):1715 张图两两比对是 147 万对,分桶后只比对同桶候选,扫描时间从分钟级降到秒级。
4. **分页 100 条/页 + 筛选 + 可点击 Ping**(`DrawPager` L1403、`DrawAtlasLink` L974):结果多也不会卡 GUI。
5. **CSV 导出带 UTF8 BOM**(L1426、L458):Excel 直接打开不乱码。
6. **性能巡检有"关闭后残留对象"检查**(`FinishClosing` L416-427):能发现"面板关了但子对象还活着",这正是 T5 FullScreenCover 复活问题的同类症状。
---
## 第二章 它对"降 DC"能帮什么、不能帮什么
### 2.1 能帮(已在 T5 验证过的用法)
- **找图集分散问题**:引用明细页每行都显示图集归属,`FindAtlases` 用 `atlas.CanBindTo(sprite)`(L968)判断,比读 meta 可靠。一个面板里图的图集名越杂,批次越多。
- **找重复图**:相似图页直接指出"这两张图其实是一张",合并后既降包体又降批次。
- **找废图**:疑似无用图片页(但判定弱,见 §3 B1)。
- **量化面板性能**:界面性能页给出 P95 帧耗时,可作为优化前后对比的基线。
### 2.2 不能帮(4 个关键缺口)
1. **测不出 Draw Call / 批次数**。`PanelResult`(L37-50)只有帧耗时和 GC,**降 DC 的成果无法用这个工具量化**——改完补丁只能靠 Frame Debugger 手工数,做不了"全项目面板 DC 排行"。
2. **看不出"谁在断批"**。工具只扫 `Image` / `RawImage` / `SpriteRenderer` 三种组件(L912-914),**完全没扫 `Mask` / `RectMask2D` / `UIEffect` / `UIEffectReplica` / 嵌套 `Canvas` / TMP 字体资产**——而这些才是断批的真正元凶。
> T5 真实案例:`LevelGiftItem.prefab` 里 1 个 `UIEffect` + 6 个 `UIEffectReplica`(参数全是默认 None、无视觉效果),运行时每条克隆出 7 个独立材质,直接把面板顶到 101 批。**这个工具当时没能报出这个问题**。
3. **不做合批预估**。已经有"每张图属于哪个图集"的数据,却没有按 prefab 聚合出"这个面板涉及几个图集 / 几张散图 / 预估最少批次",无法批量排出"最该优化的面板 TOP N"。
4. **不看纹理导入设置**。大图页只看**源文件大小**和分辨率,但真机成本是**运行时显存**,由 `TextureImporter` 的压缩格式 / maxSize / mipmap / isReadable 决定。一张 1024×1024 的图:RGBA32 未压缩 = 4 MB 显存,ASTC 6×6 = 约 0.5 MB——工具现在看不出这个差别,而 T5 光 png 就有 302 MB / 1715 张。
---
## 第三章 优化清单(按优先级,含代码位置与改法)
### A 级:能力补齐(对降 DC / 瘦身目标收益最大)
#### A1 给性能巡检加 DC / 批次 / SetPass 指标 ★首推
- **现状**:`StartRecorder` L443-448 只起了一个 `ProfilerRecorder`(`ProfilerCategory.Memory` 的 `GC Allocated In Frame`);采样在 `Tick` L384-393。
- **改法**:照 `gcRecorder` 的同一套写法,多起 3 个 Render 类别的 recorder:
```csharp
drawCallRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count");
batchRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Batches Count");
setPassRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "SetPass Calls Count");
```
在 `Tick` 的 Sampling 分支里按现有风格累积(`Math.Max(..., recorder.LastValue)` 取峰值;再加一个累加/计数得平均值);
`PanelResult`(L37)加 `MaxDrawCalls` / `AvgDrawCalls` / `MaxBatches` / `MaxSetPass` 四个字段;
`DrawEmbedded` 的结果表头(L140-149)与 `ExportCsv` 的列(L455-457)同步加列;`Finish` L440 处一并 Dispose。
- **备选**:`UnityEditor.UnityStats.drawCalls / batches / setPassCalls` 更简单,但受编辑器自身渲染和 Scene 视图影响更大,建议以 `ProfilerRecorder` 为主。
- **诚实提示(务必写进界面 HelpBox)**:编辑器 Play Mode 测得的 DC 含编辑器渲染开销,**只能用于同环境下的前后对比**,不能当真机绝对值;真机结论仍需 Profiler 连真机复测。这与 `05_排查方法论/DC与帧率排查经验.md` §2.1 的立场一致。
- **收益**:降 DC 从此可量化、可排行、可回归。做完这一项,"改了补丁到底降了多少"不再靠肉眼数 Frame Debugger。
- **工作量**:约 30-40 行,半天内含验证。
#### A2 新增"合批体检"页(静态分析,不需 Play Mode) ★首推
- **现状**:`FindAtlases` L962 已能给出每张图的图集归属,但数据只逐行展示,没有按 prefab 聚合;`ScanPrefab` L906 只遍历三种图形组件。
- **改法**:
1. `ScanPrefab` 里补扫:`Mask` / `RectMask2D` 数量、`UIEffect` / `UIEffectReplica` 数量(用 `GetComponentsInChildren<Coffee.UIEffects.UIEffectBase>(true)` 可一次拿到两者)、嵌套 `Canvas` 数量(`GetComponentsInChildren<Canvas>(true).Length - 1`)、TMP 文本用到的字体资产 distinct 数;
2. 新增 `BatchHealthRecord`:`PrefabPath`、涉及图集数、散图数(`Atlases == "未加入图集"`)、Mask 数、UIEffect 数、嵌套 Canvas 数、字体种类数、**预估最少批次数**;
3. 预估公式(标注为估算,非实测):
`预估批次 ≈ distinct图集数 + 散图数 + distinct字体图集数 + UIEffect配置种类数 + Mask分区数 + 嵌套Canvas数`
4. 新增第 8 个 `ViewTab`,按"预估批次"降序排列,支持导出 CSV。
- **收益**:一次扫描就能列出"全项目最该优化的面板 TOP N"以及每个面板的**具体病因**(是图集分散、还是 UIEffect 断批、还是 Mask 太多)。这正是丢失的 `UIDCProbeTool` 的核心价值,而且**静态就能算、可批量、可导表做优化前后对比**,比原来那个只能在运行时看单个 Canvas 的版本更实用。
- **工作量**:约 150 行,一天内。
- **依赖注意**:引用 `Coffee.UIEffects` 命名空间会让这个 Editor 脚本依赖 UIEffect 包。若担心耦合,可改用 `GetComponentsInChildren<Component>` + `GetType().Name` 字符串判断(`"UIEffect"` / `"UIEffectReplica"`),零编译依赖。
#### A3 新增纹理导入设置审计页(服务"源文件瘦身")
- **现状**:`ScanLargeImages` L1185 只看 `GetAssetFileSize`(L1215,源文件字节数)和 `image.width/height`。
- **改法**:对每张图取 `AssetImporter.GetAtPath(path) as TextureImporter`,输出并判定:
| 检查项 | 判定 | 浪费量级 |
| --- | --- | --- |
| `mipmapEnabled` | UI 图(`textureType == Sprite`)开 mipmap = 白扔 | 约 +33% 显存 |
| `isReadable` | 非必须读取却开着 = 内存里存两份 | ×2 显存 |
| `maxTextureSize` | 远大于 prefab 里实际显示尺寸 | 按平方倍 |
| 平台 override 格式 | Android/iOS 用 RGBA32 / ARGB32 未压缩 | 4-8 倍于 ASTC |
| `npotScale` | 非 2 的幂图被放大到 2 的幂 | 最多 4 倍 |
| `sRGBTexture` / `alphaIsTransparency` | 设置与用途不符 | 视觉问题 |
同时给出**预估运行时显存**(宽 × 高 × 每像素字节数,按 format 查表),并与源文件大小并列显示——两者差异常常是数量级的。
- **收益**:T5 有 302 MB png / 1715 张 + 65 MB TGA + 11 MB PSD,瘦身真正的肥肉在导入设置而不是源文件。这一页直接指向"改哪个设置省多少 MB"。
- **工作量**:约 120 行,一天内。
#### A4 扩大扫描范围(配置化)
- **现状**:`DefaultPrefabRoot` L482 固定为 `gui/panel`;`UnusedImageRoots` L487-491 硬编码两个目录(`gui` + `PSD2UI/UI`)。T5 的 `Resources_moved` 共 739.6 MB,其中 `effects` 254.5 MB、`res3d` 202.3 MB、`font` 93.8 MB、`animation` 45 MB **全都扫不到**。
- **改法**:把两个常量改成可编辑字段 + `EditorPrefs` 持久化(参考 `UnusedResourceAuditTool.cs` 的做法),并支持多目录(`string[]`);prefabRoot 也支持多个。
- **收益**:从"只能体检 UI 目录"变成"能体检全工程"。
- **工作量**:约 40 行。
---
### B 级:判定准确性(降低误报/漏报)
#### B1 "疑似无用图片"漏判动态引用 ★与另一工具冲突,必须处理
- **现状**:`HasStaticCodeReference` L1148-1158 只做三种**字面**子串匹配:guid、资源全路径、`/Resources/` 之后的相对路径(去扩展名)。`DrawUnusedImagePage` 的 HelpBox(L724-726)已诚实声明"配置表、字符串拼接、反射和运行时下载资源无法可靠识别"。
- **漏判的典型形态**(T5 实测存在):
- 配置表驱动:`achievement.json` 里 `"icon":"achievement_icon_1"`——代码和路径里都没有这个名字;
- 代码拼接:`Config_Const.Config_path_ui_task + "btn_go"`(T5 有 21 处此类模式);
- 插值前缀 + 表数字后缀:`AtlasUtils.cs` 里 `$"rarity_{rarity}"`、`$"img_card{rarity}"`、`$"phase_{phases}"`——资源实际叫 `rarity_1`,语料里永远只有 `rarity_{rarity}`;
- Addressables 条目 / Label、场景文件引用、材质与图集 packables 引用。
- **关键冲突**:`Assets/Editor/UnusedResourceAuditTool.cs`(菜单 `Tools/只读资源巡检/未引用资源候选分析`)**已经实现了完整的 5 层动态识别链**(代码字面量 → Resources 拼接 → 语料子串 → 图集/控制器/字体/Spine 配置名 → 插值/拼接前缀+数字后缀),还带 Addressables、场景根、打包可见性分析和三阶段备份流程。两个工具会对同一张图给出**矛盾结论**(这里说"无用",那里说"疑似动态引用")。
- **改法(推荐第 2 种)**:
1. 最小改动:在 `DrawUnusedImagePage` 的 HelpBox 里加一句指引——"完整分析请用 `Tools/只读资源巡检/未引用资源候选分析`",并把该页改名为"疑似无用图片(弱判定,仅供参考)";
2. 彻底改动:把 `UnusedResourceAuditTool` 里的语料构建与前缀命中逻辑抽成一个共享静态类(如 `DynamicReferenceCorpus`),两个工具都调它,结论自然一致。
- **工作量**:方案 1 约 5 行;方案 2 约 1 天(含抽取与回归)。
#### B2 依赖面太窄导致误报
- **现状**:`prefabDependencies` L586 只在 `Scan` L895 里由 **prefabRoot 下的 prefab** 累积(`AssetDatabase.GetDependencies(path, true)`)。默认 prefabRoot = `gui/panel`。
- **后果**:被**场景文件**、**其他目录的 prefab**(如 `gui/item`、`effects`)、**材质**、**SpriteAtlas 的 packables** 引用的图,都会被判成"疑似无用"。
- **改法**:根集合补上 `t:Scene`、全工程 `t:Prefab`(或至少 `gui` 全域)、`t:Material`、`t:SpriteAtlas`、Addressables 条目——即 `UnusedResourceAuditTool.CollectRoots` 的那套。或者干脆按 B1 方案 2 合并。
#### B3 `warningsOnly` 在 4 个页无效
- **现状**:只有 `Matches`(L1053,引用明细页)和 `RefreshFilteredNodeRecords`(L1174-1176,节点统计页)判了 `warningsOnly`;`RefreshFilteredSharedRecords` L1100、`RefreshFilteredUnusedImages` L1160、`RefreshFilteredLargeImages` L1227、`RefreshFilteredSimilarImagePairs` L1391 都只判 `filter`。
- **后果**:勾上"仅显示需确认项"后,这 4 个页列表毫无变化,也没有任何提示,容易被误认为"这些页没有问题项"。
- **改法**:要么给这些页的 record 补 `NeedsConfirm` 语义并参与过滤,要么在这些页顶部显示一行说明"本页不受'仅显示需确认项'影响"。约 15 行。
---
### C 级:工具自身性能与测量可信度
#### C1 手动录制每帧全量遍历所有 GameObject ★自测量污染,必须修
- **现状**:`TickManualRecording` L258 每帧调用 `GetVisibleRegisteredPanels` L289,后者执行 `Resources.FindObjectsOfTypeAll<GameObject>()`(L292)——遍历**全工程所有 GameObject**(含资产、prefab 内的对象,不只是场景对象)。
- **后果**:**工具自己在拖慢被测帧**。手动录制测出来的 P95 帧耗时里包含了这次全量遍历的开销,面板越多、工程越大,污染越严重。这是"测量工具影响测量结果"的经典陷阱,会让所有手动录制数据偏高。
- **改法(三选一,按效果排序)**:
1. **最优**:按作者自己在 L301-306 留的 TODO 做——在 `MFrame.PanelMgr` 的 `Show` / `completeHandler` / `Close` 里加生命周期事件(`OnPanelShowRequested` / `OnPanelShown` / `OnPanelClosed`),工具订阅事件。既零轮询开销,又能拿到真实的"请求 Show → 可见"耗时,不再是一帧近似值;
2. **次优**:轮询降频——加一个 `manualPollIntervalSeconds`(如 0.1 s),未到间隔就跳过本轮 `GetVisibleRegisteredPanels`;帧耗时采样仍每帧进行,不受影响;
3. **最省事**:把 `Resources.FindObjectsOfTypeAll<GameObject>()` 换成 `Object.FindObjectsByType<GameObject>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)`(只返回场景对象,且跳过排序),并预先把 `Entries` 的 prefab 名做成查找表。
- **工作量**:方案 2 约 8 行;方案 3 约 5 行;方案 1 约半天(要动框架代码,需谨慎,属于"改 UI 框架"范畴,动手前应单独确认)。
#### C2 代码语料是 O(n×m) 全文扫描
- **现状**:`ReadAllProjectCode` L1136 把 `Assets` 下**所有 .cs** 拼成一个大字符串(T5 有 4659 个文件,语料是 MB 级);`HasStaticCodeReference` L1150-1157 对每张图做 **3 次** `string.IndexOf` 全文搜索。1715 张图 = 5145 次 MB 级全文扫描。
- **改法**:一次性用正则从语料里提取所有字符串字面量和标识符 token,塞进 `HashSet<string>`;再对每个资源名/路径做 O(1) 查表 + 前缀匹配。`UnusedResourceAuditTool.BuildCodeCorpus` 已经是这个做法,可直接复用。
- **收益**:扫描时间从"十几秒到几十秒"降到"1-2 秒"。
#### C3 dHash 对全尺寸图取像素
- **现状**:`TryGetDifferenceHash` L1310 `image.GetPixels32()` 取**整张解码图**的像素,再双重循环遍历每个像素找 alpha 包围盒(L1312-1319)。
- **后果**:一张 4096×4096 的图 = 64 MB 托管内存 + 1677 万次循环。相似图扫描在含大图的目录上会明显卡顿甚至触发 GC 尖峰。
- **改法**:`ImageConversion.LoadImage` 解码后,先 `Graphics.Blit` 到一张 128×128 的 `RenderTexture` 再 `ReadPixels` 回小 `Texture2D`,在小图上找包围盒并采样。dHash 最终只取 8×9 = 72 个点(L1323-1333),128² 的精度完全够,而内存从 64 MB 降到 64 KB。
- **注意**:包围盒精度会略降(小图上 alpha≤10 的判定 L1316 会因缩放插值而边缘模糊)。稳妥做法是**双阶段**:先在 128² 上算粗略包围盒,再按该包围盒比例回到原图取那 72 个采样点(只取 72 个像素,不再全图遍历)。
- **工作量**:约 30 行。
#### C4 逐 prefab 递归依赖
- **现状**:`Scan` L890-896 对每个 prefab 调 `AssetDatabase.GetDependencies(path, true)`(递归全依赖)。566 个 prefab = 566 次递归依赖解析。
- **改法**:先收集全部 prefab 路径,一次 `AssetDatabase.GetDependencies(string[] pathNames, bool recursive)` 批量调用(Unity 支持数组重载);或改为 `GetDependencies(path, false)` 只取直接依赖 + 自行按 `prefabDependencies` 传递闭包。
- **收益**:扫描时间下降(幅度取决于依赖深度)。
---
### D 级:UI / 一致性小问题
| 编号 | 现状(代码位置) | 问题 | 改法 |
| --- | --- | --- | --- |
| D1 | `ExportCsv` L1419-1428 | 导出按钮(L624)在任何标签页都可见,但**永远只导引用明细**(`records`)。站在"大图标记"页点导出,拿到的却是引用明细表 | 按 `activeTab` 分派导出内容,或把导出按钮移进各页;至少在非引用明细页禁用并提示 |
| D2 | `GetSuggestedNodeCount` L942-952 | 节点数建议值全部硬编码(150/50/80/100/80),不同项目的 UI 复杂度差异很大 | 提到常量区并允许界面覆盖 + `EditorPrefs` 持久化;或按本工程实际分布给分位数建议(如 P90) |
| D3 | 两个 MenuItem L599 / L607 | 两个菜单打开的是**同一个** `UIPrefabCheck` 窗口,只是 `activeTab` 不同。从"界面性能巡检"进去后点"扫描",会切到静态页,容易迷失 | 用 `EditorPrefs` 记住上次所在页;或在标题上反映当前页 |
| D4 | `DrawUnusedImagePage` L732 | "扫描疑似无用图片"按钮每次都重跑 `ReadAllProjectCode`(见 C2),连点两次就是两次全量读盘 | 语料缓存 + 失效判断(按 .cs 文件最后修改时间) |
| D5 | `Record.Warning` L959 | 唯一的告警是"非标准 UI 资源目录",判据是路径前缀(`IsStandardUiPath` L1431)——这是**目录约定**告警,不是性能告警 | 配合 A2 增加真正的合批告警(散图、跨图集、UIEffect 断批) |
---
## 第四章 如果只做三件事
按"收益 / 工作量"比排序:
| 优先级 | 项目 | 收益 | 工作量 |
| --- | --- | --- | --- |
| 1 | **A1** 性能巡检加 DC / 批次 / SetPass 指标 | 降 DC 成果从"肉眼数 Frame Debugger"变成"可量化、可排行、可回归" | 30-40 行 |
| 2 | **A2** 新增静态"合批体检"页 | 一次扫描列出全项目最该优化的面板 TOP N + 具体病因(图集分散 / UIEffect / Mask / 嵌套 Canvas) | 约 150 行 |
| 3 | **C1** 修手动录制的每帧全量遍历(方案 2 或 3) | 消除工具对被测帧的污染,让手动录制数据可信 | 5-8 行 |
其次:**B1**(避免与 `UnusedResourceAuditTool` 结论打架)、**A3**(导入设置审计,服务包体瘦身)、**C2/C3**(扫描提速)。
---
## 第五章 跨项目可移植性(归档前必须知道)
这个工具**不是通用工具**,有框架绑定:
| 部分 | 依赖 | 能否直接搬到别的项目 |
| --- | --- | --- |
| 6 个静态扫描页(引用明细/共用/无用图/节点/大图/相似图) | 只用 `UnityEditor` + `UnityEngine.UI` + `UnityEngine.U2D`;唯一框架引用是文件头 `using MFrame;`(L10) | **可以**,删掉 `using MFrame;` 后基本即用(需按项目改 `DefaultPrefabRoot` L482 与 `UnusedImageRoots` L487) |
| 界面性能页(自动巡检) | `PanelMgr.Instance.Show/Close`(L366、L410)+ 从 `GameInitWrapper` 源码正则解析 `AddItem<T>`(L172-176) | **不能**,必须改成目标项目的面板管理器 API 与注册方式 |
| 界面性能页(手动录制) | `ManualPanelNameLookup` 由 `DiscoverPanels` 建立,同样依赖上面两项 | **不能**,同上 |
移植时的最小改动清单:
1. 删 `using MFrame;`(L10),确认剩余部分能编译;
2. 改 `DefaultPrefabRoot`(L482)与 `UnusedImageRoots`(L487-491)为目标项目的 UI 目录;
3. `IsStandardUiPath`(L1431)里的两个路径前缀同步改;
4. 界面性能页:替换 `DiscoverPanels`(L167)的面板发现方式与 `PanelMgr.Instance.Show/Close` 调用(L366 / L410 / L432),或在不需要时整块删掉 `UIRuntimePerformanceAudit`(L17-478)与 `ViewTab.界面性能`(L567)及其分派(L644-647)。
---
## 第六章 作者留下的 TODO(原文摘录,L301-306)
工具里已有一段明确的改进计划,做 C1 方案 1 时直接照它执行:
```
// TODO(精确手动录制耗时):在 MFrame.PanelMgr 中增加 UI 生命周期事件。
// 1. 在 Show(name, data, completeHandler) 入口记录 startTime,并触发 OnPanelShowRequested(name, startTime)。
// 2. 在资源加载完成且 PanelBase._OnShow 执行完成后的 completeHandler 中,触发 OnPanelShown(name, startTime, elapsedMs)。
// 3. 在 Close(name) 成功后触发 OnPanelClosed(name)。
// 4. 本工具订阅上述事件,以 name 关联会话,便可得到真实的"请求 Show 到可见"耗时,
// 不再依赖当前轮询 GameObject 激活状态所得的一帧近似值。
```
> 注意:这一项要动 `MFrame` 框架代码(`PanelMgr` / `PanelBase`)。T5 有过教训——**改 UI 框架前必须确认现有全屏遮挡机制(FullScreenCover)不被破坏**,且改动要能被业务面板无感继承。动手前单独确认,不要顺手做。