Quick answer: Replace WaitForSeconds with WaitForSecondsRealtime for coroutines that should run during pause (UI animations, etc.).

A pause-menu fade-in coroutine stalls because Time.timeScale is 0. WaitForSeconds uses scaled time; never elapses while paused.

Use Realtime Variant

IEnumerator FadeInPauseMenu()
{
    for (float t = 0; t < 0.3f; t += Time.unscaledDeltaTime)
    {
        canvasGroup.alpha = t / 0.3f;
        yield return null;
    }
    canvasGroup.alpha = 1;
}

Iterate using Time.unscaledDeltaTime. Or use the built-in helper:

yield return new WaitForSecondsRealtime(0.3f);

Custom Wait Until

For complex waits during pause, write a custom yield instruction:

public class WaitWhilePaused : CustomYieldInstruction
{
    public override bool keepWaiting => PauseManager.IsPaused;
}

Sit until unpaused; then continue.

Async/Await Alternative

For modern code, async/await with UniTask uses unscaled timing naturally:

await UniTask.Delay(TimeSpan.FromSeconds(0.3), ignoreTimeScale: true);

Verifying

Pause game. Open menu — fade-in completes. Close menu — gameplay coroutines resume properly (they should be scaled-time aware).

“Scaled vs unscaled is the time-pause boundary. UI uses unscaled; gameplay uses scaled.”

Adopt a convention: any coroutine running on UI should be Realtime; game logic should not. Reduces accidents.