Time.timeScale=0时如何保持UI动画继续播放

解读

国内项目里“暂停”需求非常普遍:战斗暂停、剧情暂停、打开设置面板时冻结世界逻辑,但UI 必须继续动——按钮呼吸、红点闪烁、倒计时数字滚动、Loading 圆圈旋转,一旦卡住玩家立刻感知。
面试官问这道题,不是考你会不会“把动画搬到 UI 线程”,而是看你是否吃透 Unity 的时间体系与更新回路,能否在 timeScale=0 的极端条件下,既保证 UI 动画流畅,又不破坏暂停逻辑、不引入新的性能坑,还能让美术继续用 Animator/UGUI 原有工作流。答不到“Unscaled Time”这一层只能拿 60 分;能把动画更新回路、性能、热更新兼容、设备差异都闭环,才能拿到 Offer。

知识点

  1. Unity 时间体系
    • Time.time、Time.deltaTime、Time.fixedDeltaTime 受 timeScale 影响
    • Time.unscaledTime、Time.unscaledDeltaTime、Time.realtimeSinceStartup 不受 timeScale 影响
  2. UGUI/Animator 更新回路
    • Canvas 的 Update 在 PlayerLoop 的 Update 阶段,与 timeScale 无关;但动画系统默认用 scaled time
    • Animator 的 Update Mode:Normal / Unscaled Time / Animate Physics
    • DOTween 的 SetUpdate(UpdateType.Normal | UpdateType.Unscaled | UpdateType.Late)
  3. 粒子、Spine、FairyGUI、AssetBundle 热更框架 对暂停的兼容策略
  4. 移动端省电策略:pause 后 CPU 降频,realtimeSinceStartup 仍递增但帧率可能骤降,需做 maxDelta 截断防穿模
  5. 多人协作规范:策划配表、美术挂 Animator,程序不能要求他们改流程 → 用 SOD 或 ScriptableObject 配置统一换 Update Mode

答案

分三层回答,先给结论再给落地细节,体现工程思维

  1. UGUI 自带动画
    把 Graphic 组件的 CanvasGroup.UpdateMode 或 Image.CrossFadeAlpha 的 ignoreTimeScale 参数设为 true;若用 Animator 做 UI 动画,把 Animator 的 Update Mode 改成 Unscaled Time,一条代码都不用写,美术无感知。

  2. 代码补间(DOTween 最常见)
    所有 UI 动画统一走 DOTween.To(...).SetUpdate(UpdateType.Unscaled),封装成 UIManager.DoTween(this, target, endValue, duration),强制走 Unscaled,防止新人漏写。

  3. 粒子/Spine/自定义 Shader 动画

    • ParticleSystem 把 Simulation Space 设为 Unscaled,或在 Pause() 后手动调用 ParticleSystem.Simulate(Time.unscaledDeltaTime, true, false)
    • Spine 的 SkeletonAnimation.timeScale 独立于 Time.timeScale,直接赋 1 即可
    • Shader 里用 _TimeParameters.y(即 unscaled time)做 UV 滚动,避免用 _Time.y
  4. 框架级防呆
    GamePause.SetTimeScale(0) 里统一遍历所有 Canvas.GetComponentsInChildren<Animator>(true),把 updateMode = AnimatorUpdateMode.UnscaledTime;暂停恢复时再还原,避免策划新加 UI 时漏设置。

  5. 性能与兼容性

    • 暂停后帧率可能掉到 15 fps(国产安卓省电),unscaledDeltaTime 可能 >0.1 s,动画位移要做 maxDelta 0.05 s 截断防穿模
    • 热更新 Lua 层用 Time.unscaledTime 代替 os.time(),避免 TimeManager 被 tolua 导出后仍读到 scaled 时间

一句话总结:“让 UI 动画链路里所有时间采样都改拿 unscaled 时间,就能在 timeScale=0 的世界中独善其身。”

拓展思考

  1. 可暂停的 UI 与不可暂停的 UI 分层
    把世界 UI(血条、伤害数字)放进 WorldSpace Canvas,设置 updateMode = UnscaledTime;纯逻辑 UI(背包、设置)保持默认。这样即使以后策划要求“只暂停世界,不暂停背包”,也能直接复用同一套规则。

  2. 网络帧同步项目
    帧同步层用 custom timeScale 驱动逻辑帧,渲染层仍用 Unity 的 timeScale=0 做表现暂停,但 UI 动画继续跑。此时需要双时钟:一个 scaled(逻辑)一个 unscaled(表现),并在 LockStepService 里把 UI 事件插到渲染队列,避免逻辑帧卡住导致 UI 点击无响应。

  3. WebGL 与 iOS 后台
    WebGL 在标签页切后台时 Time.unscaledTime 也会停,需用 jslib 插件 记录 performance.now() 做补偿;iOS 切后台进入 background 状态,Unity 完全停止,回到前台要手动刷新一次 Animator.Play(state, 0, offset),防止 UI 动画回跳。

  4. 面试反向提问
    答完后可以反问面试官:“咱们项目暂停时是否需要保持粒子特效?如果后期做 GPU Driven Particle,unscaled 时间是否走 Compute Shader 的 CustomTime?” 既展示深度,也探出团队技术栈,加分项