如何降低高频AnimationEvent的GC
解读
在国内 Unity 项目面试中,高频 AnimationEvent 引发 GC 是性能热点题。面试官想确认两点:
- 你能否量化定位 AnimationEvent 的 GC 来源(主要是每次 new 的 AnimationEventArgs 与反射调用);
- 你能否在不改美术流程的前提下,用引擎级或框架级手段把 GC 压到 0.1 MB/帧以下,并保证多端兼容。
回答时先给数据(UWA、Profiler 截图记忆点),再给落地代码,最后补风险与回退方案,符合国内大厂“先指标后方法”的面试套路。
知识点
- AnimationEvent 对象池:事件实例复用,避免每帧 new。
- 反射缓存:Unity 2019.4 之前 AnimationEvent 通过反射找函数,Delegate 缓存 + 静态字典可彻底消除反射 GC。
- il2cpp 额外开销:il2cpp 下字符串转函数指针会生成临时 UTF16 字符串,需预绑定函数指针并关闭“SendMessage”回退。
- Timeline/Playables 替代:Timeline 的 PlayableBehaviour 在 2019 LTS 之后无 GC 分配,可作为高频事件终极方案。
- 安卓 JIT 禁止:国内安卓渠道 64 位强制 il2cpp,不能用动态 Delegate.CreateDelegate,必须提前 AOT 注册。
- UWA 阈值:国内发行验收要求“动画更新单帧 GC < 0.05 MB”,高于此值会被打回。
答案
“我在上一个 MMO 项目里,角色普攻每秒 12 次,每次 3 个 AnimationEvent,Profiler 里看到单帧 GC 0.8 MB,全是 AnimationEvent.stringParameter 和 objectParameter 的装箱。
解决分三步:
- 对象池 + 结构体事件:把事件参数改成值类型,用 Queue<AnimationEvent> 池复用,帧末 Clear 池,保证 0 分配。
- 预绑定函数表:在 Awake 阶段用反射一次性把函数指针存进静态 Dictionary<int, Action<AnimationEvent>>,key 用 Animator.StringToHash,运行时直接委托调用,反射 GC 归零。
- il2cpp 兼容:把函数标记 [Preserve] 并在 link.xml 全量保留,防止裁剪丢失;同时关闭 Animator.sendMessage 回退,避免生成临时字符串。
上线后安卓低端机 GC 从 0.8 MB/帧降到 0.02 MB/帧,UWA 报告一次性通过,且美术无需改动画。”
拓展思考
若 AnimationEvent 频率继续提升到 60 Hz 以上(如音游节拍),可完全弃用 AnimationEvent,改用 Timeline 的 Custom PlayableTrack:
- 在 ProcessFrame 里用 timeReference 精确回调,零 GC 且支持可预测性重采样;
- 通过 PlayableDirector.timeUpdateMode = DSP 时钟,保证音频与动画样本级对齐,解决国内安卓碎片化导致的音画不同步问题;
- 最终把事件系统拆成数据驱动的 PlayableAsset,策划可在 Timeline 里拖拽,美术流程无感,符合国内“程序不动美术”的协作红线。