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.