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:
- A Rigidbody with Interpolation enabled slides forward slightly for a frame after the pause.
- A custom script using
Time.unscaledDeltaTimecontinues to update. - A character controller keeps moving because its script reads input and applies velocity using
Time.unscaledDeltaTime. - A coroutine with
yield return new WaitForSecondsRealtimecontinues counting down.
What timeScale Actually Does
Setting Time.timeScale = 0 multiplies Time.deltaTime and Time.fixedDeltaTime by zero. This means:
Updatestill runs, butTime.deltaTimereturns 0.FixedUpdatestops running entirely (Unity does not schedule physics ticks whenfixedDeltaTimeis 0).- Animations driven by Animator with update mode Normal pause because they read scaled delta.
- Coroutines using
WaitForSecondspause.
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:
- Gate the code on
PauseManager.IsPausedand skip updates while paused. - Switch to scaled time so
timeScaledoes the work automatically.
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.