Quick answer: Time.timeScale = 0 does pause physics, but Rigidbody Interpolation runs on unscaled time and will visibly drift a frame after pause. Disable interpolation during pause (or use manual Physics.simulationMode = SimulationMode.Script) and audit the project for Time.unscaledDeltaTime usage that bypasses pause.

You add a pause menu, set Time.timeScale = 0, and the game freezes as expected — except for one rigidbody that keeps visibly drifting, or a coroutine that keeps ticking, or an animation that keeps playing. “Pause” in Unity is not one switch. It is a cluster of switches and whichever one you forgot is the bug.

The Symptom

You call Time.timeScale = 0. Most things stop. But:

What timeScale Actually Does

Setting Time.timeScale = 0 multiplies Time.deltaTime and Time.fixedDeltaTime by zero. This means:

But: anything reading unscaledDeltaTime, unscaledTime, or realtimeSinceStartup is immune. And the Animator update mode Unscaled Time is also immune. The “bug” is usually that something in your project chose to opt out of scaled time and you forgot about it.

The Fix

Step 1: Make a single PauseManager.

public static class PauseManager
{
    public static bool IsPaused { get; private set; }

    public static void Pause()
    {
        IsPaused = true;
        Time.timeScale = 0f;
        AudioListener.pause = true;
    }

    public static void Resume()
    {
        IsPaused = false;
        Time.timeScale = 1f;
        AudioListener.pause = false;
    }
}

Every pause flow must go through this. If you have two scripts setting timeScale, you will eventually get a race condition where the game appears paused but is not.

Step 2: Audit unscaled time usage.

Search your project for unscaledDeltaTime, unscaledTime, and realtimeSinceStartup. Each hit is a place where pause will not work by default. You have two choices:

Step 3: Freeze rigidbodies that use Interpolation.

public class PausableRigidbody : MonoBehaviour
{
    private Rigidbody _rb;
    private RigidbodyInterpolation _savedInterp;

    void OnEnable()  { _rb = GetComponent<Rigidbody>(); }

    public void OnPause()
    {
        _savedInterp = _rb.interpolation;
        _rb.interpolation = RigidbodyInterpolation.None;
    }

    public void OnResume()
    {
        _rb.interpolation = _savedInterp;
    }
}

Interpolation lerps the visual transform between physics frames based on unscaled time, which is why paused bodies still drift visibly. Disabling interpolation during pause locks them in place.

Step 4: Use manual physics simulation for games that need it.

// In Start or a bootstrap script
Physics.simulationMode = SimulationMode.Script;

// In your own update loop
void Update()
{
    if (!PauseManager.IsPaused)
    {
        Physics.Simulate(Time.fixedDeltaTime);
    }
}

Manual simulation gives you full control: physics only steps when you call it. This is the most reliable pause behavior, at the cost of managing the fixed-timestep loop yourself.

The Animator Gotcha

Animators have an Update Mode property with three values: Normal (scaled), Animate Physics (in FixedUpdate, scaled), and Unscaled Time (ignores timeScale). A designer sets one Animator to Unscaled Time because they want the UI to keep animating during pause, and six months later you ship a pause bug because that mode was applied to an enemy too.

Audit your Animators: search project assets for Animator controllers and check the Update Mode on the Animator component itself (not the controller). Decide which ones need Unscaled Time and document the list.

Verifying the Fix

Build a test scene with a rolling ball, a moving platform, a particle system, an Animator, and a UI button. Press pause. Every non-UI object should freeze in place visibly. Press resume. Everything should continue from exactly where it was. If any object continues to move during pause or teleports on resume, you have another place using unscaled time.

“Pause in Unity is not a feature, it is a convention. Pick one switch (timeScale) and make every other time-dependent system in your project respect it.”

Related Issues

For broader Unity physics problems, see Unity physics jittery movement. For pause-related UI issues, see Unity UI button not responding to clicks. For coroutine timing problems, see Unity coroutine not starting or stopping early.

Keep a single PauseManager and route every pause action through it. Two scripts setting timeScale independently is the source of every pause bug you will ship.