【Unity】Implementing Pause Correctly in Unity - Time.timeScale and UI That Keeps Moving

Created: 2026-07-05Last updated: 2026-07-13

You set Time.timeScale = 0 to pause the game, and now your menu animations froze too — a classic pause-implementation pitfall. This guide covers how timeScale works, exactly what stops and what doesn't, how to build UI that animates during pause, and where the timeScale approach reaches its limits.

You hit the pause button and set Time.timeScale = 0f — so far so good. But then the pause menu's fade-in freezes too, and the screen looks stuck. You panic and remove the animation, and now "the game is paused but the particles are still moving"... Pause implementation is a classic trap that catches everyone who doesn't know how Unity's "time" actually works.

This article explains how Time.timeScale works, gives you a precise breakdown of what stops and what doesn't, shows how to build UI that keeps moving during pause, and covers the cases where you shouldn't use the timeScale approach at all.

Pause concept art: a world frozen in time, with only the pause button glowing

What You'll Learn

  • What Time.timeScale really is (a "faucet" for in-game time)
  • A precise breakdown of what stops and what doesn't at timeScale = 0
  • How to implement a PauseManager with event notifications
  • How to build UI that runs during pause (Unscaled Time / WaitForSecondsRealtime)
  • The limits of the timeScale approach, and when to switch to a flag-based one

Sponsored

What Time.timeScale Really Is: The Faucet for In-Game Time

Unity runs two clocks side by side: in-game time and real time. Time.timeScale is the faucet (flow valve) connecting the two.

Diagram of how Time.timeScale works: real time flows through a faucet into in-game time — timeScale=1 is normal speed, 0.5 is slow motion, 0 stops it completely

The per-frame elapsed time Time.deltaTime is actually "real elapsed time × timeScale". In other words —

  • timeScale = 1: normal speed (same flow as real time)
  • timeScale = 0.5: slow motion (half the flow)
  • timeScale = 0: in-game time stops completely (faucet closed)

This is why pause can be written in a single line: everything driven by Time.deltaTime stops in unison the instant you close the faucet.

What Stops and What Doesn't

The root of the confusion is the misconception that "timeScale = 0 stops everything." What actually stops is only whatever rides on in-game time — anything running on real time keeps going.

Breakdown diagram of what stops vs. what keeps running at timeScale=0. Stops: deltaTime-based movement, physics, Animator, particles, WaitForSeconds. Keeps running: Update calls, input, unscaledDeltaTime, WaitForSecondsRealtime, audio
Stops (in-game time)Keeps running (real time)
Movement and rotation using Time.deltaTimeThe Update calls themselves (deltaTime just becomes 0)
Physics and FixedUpdate (no longer called)Player input (keys, mouse, buttons)
Animator (when Update Mode is Normal)Time.unscaledDeltaTime / unscaledTime
Particles, Invoke, WaitForSecondsWaitForSecondsRealtime, Animator (Unscaled Time)
Audio (stop it with AudioListener.pause)

The two entries in the top right are especially important. Update keeps getting called, so you always have a place to write "pause-only" logic, and input stays alive, so the player can still unpause. Conversely, audio does not stop on its own — you have to stop it explicitly.

Note: FixedUpdate is the exception — rather than "deltaTime becoming 0," the calls themselves stop. Since physics time isn't advancing, the next physics update simply never arrives. For the relationship between Update and FixedUpdate, see the Update vs FixedUpdate article.

Sponsored

Core Implementation: A PauseManager with Event Notifications

Just "closing the faucet" is only half an implementation. Take it one step further and broadcast pause-state changes via an event — then UI visibility toggles, audio stops, and so on can each be handled by the systems themselves, and your PauseManager never bloats (a two-layer design: do the work directly, announce it via events).

using System;
using UnityEngine;

public class PauseManager : MonoBehaviour
{
    // Event that broadcasts pause-state changes (argument: true while paused)
    public static event Action<bool> OnPauseChanged;

    public static bool IsPaused { get; private set; }

    // Remember the pre-pause flow rate (supports pausing during slow motion)
    private static float previousTimeScale = 1f;

    public static void SetPaused(bool paused)
    {
        if (IsPaused == paused) return;
        IsPaused = paused;

        // 1. Open or close the faucet for in-game time
        //    The point: restore "the pre-pause flow rate," not a hardcoded 1
        if (paused)
        {
            previousTimeScale = Time.timeScale;
            Time.timeScale = 0f;
        }
        else
        {
            Time.timeScale = previousTimeScale;
        }

        // 2. Audio doesn't stop automatically, so stop it explicitly
        AudioListener.pause = paused;

        // 3. Notify every system of the change (subscribers handle UI toggling etc.)
        OnPauseChanged?.Invoke(paused);
    }
}

The caller (say, a pause button or the Escape key) just calls PauseManager.SetPaused(!PauseManager.IsPaused). The pause menu UI subscribes to OnPauseChanged and toggles its own visibility (for how to subscribe, follow the OnEnable/OnDisable rule from the events and delegates article).

Mini Experiment: Pause During Slow Motion

The meaning of previousTimeScale clicks within a minute of experimenting. Bind some key to set Time.timeScale = 0.25f (pretend it's a special-move slow-motion effect), then pause and resume.

  • The "restore to 1" implementation: The moment you resume, the slow motion snaps off and the effect is ruined
  • The "restore the pre-pause value" implementation (the code above): The game resumes still in slow motion, as if nothing happened

While you're at it, notice that the pause menu's fade speed doesn't change even during slow motion. The fade runs on unscaledDeltaTime (real time), so the split between "in-game time = scaled / real time = unscaled" becomes literally visible.

Also note that if (IsPaused == paused) return; at the top of SetPaused doubles as mash protection. However fast you hammer the pause key, the state just flips cleanly back and forth — no double notifications, no corrupted speed backup.

If you saw all three things in this experiment — the game resuming still in slow motion, the fade moving through a frozen world, the state flipping cleanly under mashing — your pause feature is done. And when you later add hit-stop or slow-motion effects that touch timeScale, this "restore the previous value" foundation keeps paying off.

Building UI That Runs During Pause

Now, back to the problem from the intro. The pause menu's fade-in froze because that animation was riding on in-game time. The fix: keep the world frozen, but run the menu on real time.

Example diagram of a frozen world with a moving menu: the game world (enemies and projectiles) is frozen solid, but the pause menu in front slides in and keeps animating

There's one fix per mechanism you're using.

  • Animator animations: On the Animator component, change Update Mode from Normal to Unscaled Time. That alone keeps them playing during pause
  • Coroutine waits: Replace WaitForSeconds with WaitForSecondsRealtime
  • Script-driven UI: Replace Time.deltaTime with Time.unscaledDeltaTime
// A fade-in that keeps running during pause (uses unscaledDeltaTime)
using UnityEngine;

public class PauseMenuFade : MonoBehaviour
{
    [SerializeField] private CanvasGroup canvasGroup;

    void Update()
    {
        if (!PauseManager.IsPaused) return;

        // deltaTime is 0, but unscaledDeltaTime keeps advancing in real time
        canvasGroup.alpha = Mathf.MoveTowards(
            canvasGroup.alpha, 1f, Time.unscaledDeltaTime * 4f);
    }
}

Note that uGUI button click detection itself runs on real time, so your pause menu is clickable with no extra work.

Sponsored

Caveats: When to Walk Away from timeScale

The timeScale approach is a sledgehammer that "freezes the entire world." That's exactly why it can't be used in games where the world must not freeze wholesale.

  • Multiplayer: You can't stop the world's clock for your own convenience. Pausing just opens a menu — the game keeps running
  • Worlds that keep moving during pause: For requirements like "enemies drift slowly behind the menu" or "I only want to freeze some objects," a full stop is far too blunt

In these cases, switch to the approach where each system checks an IsPaused flag and stops only the logic it wants stopped. The PauseManager above already has IsPaused and OnPauseChanged, so deleting the Time.timeScale line is all it takes to repurpose it as a flag-based manager. Enemy AI halts itself with if (PauseManager.IsPaused) return;, while background effects ignore the flag and keep moving — a selective pause.

The decision comes down to one question: "Should this logic run on in-game time, or on real time?" Get that classification right, and you'll never be lost with either the timeScale approach or the flag approach.

Bonus: Good to Know for Later

Once pause is working, these topics come into view next.

  • Integrating with game-wide state management: Pause is one part of a "Playing ⇄ Paused" state transition. Fold it into the GameState from the GameManager pattern article and it's managed consistently alongside the title screen and game over.
  • Turning notifications into an event channel: When the subscribers to your pause notification (enemy AI, BGM, timers...) start multiplying across scenes, Case 2 in the event channels article is exactly this scenario.
  • Applying it to slow-motion effects: Intermediate values like timeScale = 0.3f work great for a slow-motion special-move shot. A classic technique here is scaling Time.fixedDeltaTime along with it (0.02f * timeScale) to keep the physics granularity consistent.

Summary

Pause implementation is all about understanding how time works.

  • Time.timeScale is the faucet for in-game time. Since deltaTime = real time × timeScale, setting it to 0 stops all time-dependent logic in unison.
  • Update and input don't stop (deltaTime just becomes 0). FixedUpdate stops being called. Stop audio explicitly with AudioListener.pause.
  • Build the PauseManager as a timeScale toggle plus event notification set. Leave the UI and audio responses to the subscribers.
  • Move anything that should run during pause onto real time: Unscaled Time for Animator, WaitForSecondsRealtime for coroutines, unscaledDeltaTime for scripts.
  • For multiplayer or selective pause, use the flag approach. Sort everything with the one-line question: "in-game time, or real time?"

"The world is frozen, but the menu glides in smoothly" — now you can build that satisfying pause screen in your own game.