14 KiB
移植与验证手册
配套包:
DC优化归档/| 验证过的环境:Unity 团结引擎(Tuanjie)+ UI Effect 5.11.3 + URP 改动量:2 个文件、126 行新增、2 行删除。业务代码 0 改动。
第一章 动手前的前提核对(3 项,缺一不可)
1.1 UIEffect 必须是"本地源码包",不能是 PackageCache 只读包
打开目标工程的 Packages/manifest.json,找 com.coffee.ui-effect:
// ✅ 可以改:file: 指向工程内源码目录
"com.coffee.ui-effect": "file:../my_libs/unityLib/UIEffect-main/Packages/src",
// ❌ 改了白改:版本号形式会被拉进 Library/PackageCache/(只读,Unity 重新解包会覆盖)
"com.coffee.ui-effect": "5.11.3"
如果是 ❌ 形式,先转成本地包:
- 从 github.com/mob-sakai/ParticleEffectForUGUI 同作者的 UIEffect 仓库 下载对应版本源码;
- 放到工程内目录(如
<项目>/unity/my_libs/unityLib/UIEffect-main/Packages/src); manifest.json改成file:相对路径;- Unity 重新编译,确认
Library/PackageCache/下的com.coffee.ui-effect@*不再被引用。
T5 的实际路径:
d:\Project\T5\T5_game\unity\my_libs\unityLib\UIEffect-main\Packages\src需要改的两个文件都在它的Runtime/下。
1.2 版本核对:确认锚点代码存在
补丁靠"上下文行"定位,版本差异大就会 apply 失败。打开目标工程的两个文件,确认下面两段存在:
Runtime/UIEffectContext.cs —— 找到 useViewMatrix 属性(补丁插在它后面):
public bool useViewMatrix => m_TransitionFilter != TransitionFilter.None
|| m_GradationMode != GradationMode.None
|| m_DetailFilter != DetailFilter.None
|| m_EdgeMode == EdgeMode.Shiny;
Runtime/UIEffectBase.cs —— 找到 GetModifiedMaterial() 里的这两行(补丁要替换它们):
var rootId = (uint)(transitionRoot != null ? transitionRoot.GetHashCode() : 0);
var hash = new Hash128((uint)baseMaterial.GetHashCode(), effectId, samplingScaleId, rootId);
两段都在 → 走方式 A(git apply)。
useViewMatrix 不存在(更老的版本)→ 走方式 C(手工贴),并按 §3.3 的降级写法处理 rootId。
1.3 记录基线 DC 数字
改之前先量一次,否则改完无法证明有效:
- 打开目标面板(选 UIEffect 用得最多的那个,T5 用的是
UI_LevelGiftPanel); Window → Analysis → Frame Debugger,Enable;- 记下左侧事件树里
Canvas.BuildBatch下的Draw Mesh数量(或右下角 Batches 数字); - 截图存档。
第二章 三种移植方式
方式 A:git apply(推荐,最快最安全)
补丁里的路径是 a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/...,
即相对目标工程 git 根目录。T5 的 git 根是 <项目>/T5_game(不是 <项目>),别站错目录。
# 站到目标工程的 git 根目录
cd <目标工程 git 根目录>
# 先干跑,看能不能对上(不写文件)
git apply --check --3way "d:\Project\T5\DC优化归档\01_UIEffect材质共享降DC\uieffect_configId.patch"
# 没问题再真打
git apply --3way "d:\Project\T5\DC优化归档\01_UIEffect材质共享降DC\uieffect_configId.patch"
# 确认只动了这 2 个文件
git status --porcelain
目标工程 UIEffect 放在别的路径(比如 Assets/Plugins/UIEffect/)时,用 --directory 或 -p 调整剥层级,例如:
# 补丁里 a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/X.cs
# 目标工程实际是 Assets/Plugins/UIEffect/Runtime/X.cs → 剥掉前 5 层再拼目标前缀
git apply -p5 --directory="Assets/Plugins/UIEffect" --3way "<补丁路径>"
--check 报错 does not match index → 目标文件已被改过或版本不同 → 转方式 B/C。
方式 B:直接覆盖两个 .cs(兜底,要求版本一致)
$dst = "<目标工程>\unity\my_libs\unityLib\UIEffect-main\Packages\src\Runtime"
Copy-Item "d:\Project\T5\DC优化归档\01_UIEffect材质共享降DC\UIEffectContext.cs" $dst -Force
Copy-Item "d:\Project\T5\DC优化归档\01_UIEffect材质共享降DC\UIEffectBase.cs" $dst -Force
覆盖前务必先备份原文件(或确认在 git 里可回退):这两个文件是完整成品,覆盖会连带丢掉目标工程对它们的任何其它本地修改。
.meta文件不用管:这是"修改已存在的文件",不是"新增资源",guid 不变,prefab/场景引用不受影响。
方式 C:手工贴代码块(版本不同时的唯一选择)
三处改动,照着贴:
C-1 UIEffectContext.cs 文件头加 using
using System;
using System.Collections.Generic;
using System.Reflection; // ← 新增这一行
using Coffee.UIEffectInternal;
using UnityEngine;
C-2 UIEffectContext.cs 在 useViewMatrix 属性之后插入 121 行
完整代码见本包 01_UIEffect材质共享降DC/UIEffectContext.cs 的 267~385 行(configId 属性 + Mix / HashValue / HashFields / GradientHash + s_ConfigFields / s_FieldCache 两个静态缓存)。
整块是纯新增,不改任何原有代码,粘到 useViewMatrix 与 private Texture2D _gradationRampTex; 之间即可。
C-3 UIEffectBase.cs 的 GetModifiedMaterial() 换 key(T5 里约 196~203 行)
Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial");
var samplingScaleId = (uint)(Mathf.InverseLerp(0.01f, 100, actualSamplingScale) * uint.MaxValue);
// 使用特效配置哈希作为材质共享 key,相同配置的特效共享同一材质,从而可以合批。
// root 仅在特效需要视图矩阵(transition/gradation/detail/shiny)时才影响材质。
var configId = context.configId;
var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u;
var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
if (!MaterialRepository.Valid(hash, _material))
注意三个容易漏的细节:
baseMaterial.GetHashCode()也要一起改成baseMaterial.GetInstanceID()(GetHashCode对 Object 不是稳定 ID);- 只改
hash那一行、不改rootId,会让 transition 类特效在不同 root 下错误共享材质(视觉错位); effectId属性保留不删,它是 public API,UIEffectReplica还在 override 它。
第三章 移植后必做的 5 项检查
3.1 编译通过 + 无警告
Unity Console 干净。报 UIEffectContext does not contain a definition for 'useViewMatrix' → 见 §3.3。
3.2 业务代码有没有绕过 context 直接改材质(最重要)
材质从"每实例独有"变成"同配置共享"后,任何直接改 effectMaterial 的代码都会污染所有共享者。
# 在目标工程业务脚本目录执行,命中即需逐个确认
cd <目标工程>\unity\Assets
Select-String -Path (Get-ChildItem -Recurse -Filter *.cs).FullName `
-Pattern "effectMaterial|GetComponent<UIEffect>|UIEffect\[\]" | Select-Object Path, LineNumber, Line
判定规则:
- 只读(
var m = e.effectMaterial; if (m == null))→ 安全; - 写入(
e.effectMaterial.SetColor/SetFloat/SetTexture/...)→ 危险,必须改成"改 UIEffect 组件的序列化字段(走 context)"或"给该实例单独指定不同配置"; - T5 实测:业务代码 0 命中,无需处理。
3.3 useViewMatrix 不存在的降级写法(老版本插件)
var configId = context.configId;
// 老版本没有 useViewMatrix:保守起见让 root 始终参与,共享范围变小但结果一定正确
var rootId = transitionRoot != null ? (uint)transitionRoot.GetInstanceID() : 0u;
var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
或者自己在 UIEffectContext 里补一个等价属性(判断条件见 §1.2 的代码块)。
3.4 UIEffectReplica / UIEffectPreset 兼容性
已核对源码(UIEffectReplica.cs 的 context override),补丁后两条路径行为不变:
| Replica 工作模式 | context 来源 | 补丁前共享依据 | 补丁后共享依据 | 结论 |
|---|---|---|---|---|
| 跟随场景内的 target | target.context(同一对象) |
effectId = target.effectId |
同一个 context → configId 必然相同 | 仍共享 |
| 跟随 preset(或 target 不在场景) | 自己的 base.context |
effectId = preset.GetHashCode() |
preset 配置相同 → configId 相同 | 仍共享 |
如果目标工程用了 Replica,改完抽查一处 Replica 的显示是否正常即可(T5 只有 1 个 prefab 涉及)。
3.5 IL2CPP / 高 stripping 下的一致性(出真机包前做)
configId 依赖反射遍历字段。字段本身被 Unity 序列化系统引用,正常不会被裁剪,但换平台后建议验一次:
// 临时加在 UIEffectBase.GetModifiedMaterial 里,真机跑一次看日志
Debug.Log($"[UIE] configId={configId} hash={hash} name={name}");
同一配置的元素在真机上应打出相同的 configId,且与编辑器一致。验完删掉这行日志。
第四章 控制台报错处理
4.1 Non-matching Profiler.EndSample
Non-matching Profiler.EndSample (Every EndSample call must have a preceding BeginSample call...)
成因:ShaderVariantRegistry.cs 的 RegisterVariant / GetVariant 里有提前 return 分支没配对 EndSample。
处理:文件在 Runtime/Internal/Utilities/ShaderVariantRegistry.cs。检查每个 Profiler.BeginSample(...) 之后的所有 return 路径,return 前补 Profiler.EndSample();。
T5 的 5.11.3 版本该文件配对本身已正确(
RegisterVariant在 301 行 Begin,307/327/333 三处 End 覆盖全部分支;GetVariant在 338 行 Begin,358/370 两处 End),无需修改,所以本包没有归档这个补丁。 T11 当时是旧版本才需要补。
4.2 NullReferenceException 出现在 UIEffect 相关调用栈
九成是删了 UIEffect 组件但业务代码还在 GetComponent<UIEffect>() 后用。
血泪教训(T5 实测踩过):删任何 UI 组件前,先 grep 业务代码有没有引用它,确认无引用再删。
本补丁不删组件,正常不会触发;若你在做 DC 优化时顺手清理了"看起来没用的" UIEffect,请务必回查。
第五章 验证方法(怎么证明 DC 真的降了)
5.1 Frame Debugger 对比(最直接)
- 打开同一个面板(跟 §1.3 基线同一个);
Window → Analysis → Frame Debugger→ Enable;- 看
Canvas.BuildBatch下的Draw Mesh数量,与基线对比; - 关键验证点:在事件树里点几个原本各自独占一批的灰度图标,看右下角 Material / Texture 是否变成同一个(材质名相同即共享成功)。
预期:同配置特效密集的面板,批次数量级下降(T5 等级礼包面板 101 → 3040)。
如果只降了一两个批次,说明该面板里 UIEffect 配置本来就各不相同(属正常,见 §7 原因 1)。
5.2 材质实例数对比(更硬的证据)
Play 模式下执行(临时挂个测试按钮或用 Console 执行):
var all = Resources.FindObjectsOfTypeAll<Material>();
Debug.Log($"UIEffect 材质数: {all.Count(m => m.shader != null && m.shader.name.Contains("UIEffect"))}");
补丁前:数量 ≈ 场景内 UIEffect 组件数;补丁后:数量 ≈ 不同配置的种类数。
5.3 视觉回归(必做)
逐个检查这几类特效显示是否正常——它们走的是 rootId 参与哈希的分支,是本次改动唯一有行为差异的地方:
- Transition(过渡/溶解)
- Gradation(渐变)
- Detail Filter(细节滤镜)
- Edge Mode = Shiny(发光边缘)
普通灰度(ToneFilter.Grayscale)不涉及 root,风险最低。
第六章 回滚
cd <目标工程 git 根目录>
# 方式 A/B 打的补丁:一条命令还原这两个文件
git checkout -- "unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs" `
"unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs"
路径不同的按实际改。回滚后回 Unity 编译即可,不涉及资源/序列化数据,无副作用(材质缓存 key 是运行时的,不落盘)。
第七章 "改完了 DC 还是没降"的 7 个原因
按命中概率排序,逐条排:
- 配置本来就各不相同:一个面板里 10 个 UIEffect 用了 10 种参数组合 → 10 个材质,属预期。想再降只能统一美术配置(比如灰度就用同一套参数)。
- 中间夹了不能合批的元素:合批要求"同一 Canvas + 同材质 + 同纹理 + 层级相邻"。UIEffect 元素之间插了别的图(不同图集)→ 批次被打断,即使材质相同也合不了。
- Mask / RectMask2D 切割:每个 Mask 内部独立成批,跨 Mask 永不合批。
- 不同图集(SpriteAtlas):材质相同但纹理不同 → 不合批。Frame Debugger 里看纹理名
sactx-...-atlas-<hash>,hash 不同就是不同图集。 - Canvas 被拆成多个(子物体挂了
Canvas组件 /SubCanvas)→ 各自建批。 - TextMeshPro 与 Image 混排:TMP 用字体图集,跟普通 Image 不同纹理,天然分批。
- 补丁没生效:改的是
Library/PackageCache/下的副本(§1.1 没做对),或 Unity 没重新编译(改完必须等编译完成、必要时Assets → Reimport All)。
排查工具与看图技巧见 05_排查方法论/DC与帧率排查经验.md。
第八章 升级插件时的注意事项
UIEffect 版本升级会覆盖这两个文件,补丁丢失。所以:
- 补丁改动务必提交进 git,commit message 写明"UIEffect configId 降 DC 补丁"(T5 是
c61b9012,T11 是fa5f0cdb); - 升级插件后重跑本手册第一章的锚点核对,再按方式 A/C 重新打;
- 本包
01_UIEffect材质共享降DC/里的两个成品.cs是 5.11.3 版本,不要用它覆盖更高版本的插件文件。