Why Your Pause Menu Breaks Everything (And How a Unity Coroutine Manager Fixes It)

Learn how to build a custom Unity coroutine manager that ignores Time.timeScale, enabling smooth pause menu animations, bullet time effects, and robust background tasks.

code Code download content_copy expand_less

Here's the thing—I spent a good afternoon figuring out why my beautiful pause menu animation completely froze the moment I hit pause. The culprit? I'd set Time.timeScale = 0 to freeze the game, and suddenly everything stopped. Not just the gameplay—everything. My menu buttons wouldn't animate, my UI transitions died mid-tween, and worst of all, I couldn't even unpause because the coroutine handling my "resume" logic was also frozen solid.

Been there? This is one of those problems that makes you question your entire approach to game timing. The default Unity coroutine manager system ties everything to a single global clock, and when that clock stops, your entire game grinds to a halt. But what if you could have multiple independent clocks? What if your UI could keep running smoothly while your gameplay is frozen? What if you could create that slick bullet time effect where enemies move in slow motion but your camera pans at normal speed?

That's exactly what we're going to build today.

The Real Problem: When Unity's Default Clock Breaks Your Game

Let me tell you exactly what's happening under the hood. A custom Unity coroutine manager solves the critical problem of coroutines being inherently tied to the MonoBehaviour that started them and being completely dependent on Unity's global Time.timeScale. If you pause the game by setting Time.timeScale = 0, all default coroutines stop, which is often not what you want for UI animations or certain background processes.

What does this unlock for you? It allows you to create powerful, independent timing systems. You can build pause menus that animate smoothly while the game is frozen, create cinematic Unity slow motion gameplay effects that only affect gameplay characters while the camera continues to move at a normal speed, or run complex, multi-stage background tasks that are not affected by level loads or scene changes.

Think of Unity's default coroutine system as a single, shared wall clock for your entire house. If you stop that clock, every single activity that relies on it also stops. A custom Unity coroutine manager is like giving a specific room, say your kitchen, its own independent timer. You can now pause the main house clock, but the kitchen timer for your baking will continue to run, completely unaffected.

What Even Is a Coroutine? (And Why MonoBehaviour Owns You)

Before we go deeper, let's make sure we're on the same page about what we're actually working with. Here are the key terms you need to understand:

Here's Why MonoBehaviour StartCoroutine Traps You

By default, you can only call StartCoroutine() from an instance of a MonoBehaviour. This is because the MonoBehaviour instance acts as the "runner" that keeps the coroutine alive and ticking. If that MonoBehaviour's GameObject is disabled or destroyed, all coroutines it is running are immediately stopped.

csharp
void Start()

{
// This coroutine is tied to the lifecycle of this specific component instance.
StartCoroutine(MyRoutine());
}

IEnumerator MyRoutine() { /* ... */ }

This one tripped me up in my early projects. I had a weapon reload coroutine attached to my player script, and when the player died and respawned, the reload logic would just... vanish. Took me a bit to realize the coroutine died with the GameObject.

code Code download content_copy expand_less

The Game-Changer: Running Coroutines That Ignore Time.timeScale

Here's where things get interesting. The core technique for a Unity coroutine manager is to manually advance the IEnumerator using Unity unscaledDeltaTime instead of relying on Unity's default yield instructions which respect timeScale.

csharp
// This is a conceptual example of manual iteration.

IEnumerator routine = MyRoutine();

void Update()
{
// Manually advance the coroutine. This is what a manager does internally.
routine.MoveNext();
}

What's happening here? Instead of letting Unity automatically handle when your coroutine resumes, you're taking control. You're saying, "I'll decide when this coroutine advances, using my own timing rules."

Custom Time Scaling: Your Secret Weapon for Coroutine Time Control

By creating your own "delta time" variable, you can control the speed of your custom coroutines. This allows you to have multiple "time groups" in your game—for example, a "player time" that can be slowed down and a "UI time" that always runs at normal speed.

csharp
// In a custom time manager
public float gameplayTimeScale = 1.0f;

// In your custom coroutine runner's Update loop
float customDeltaTime = Time.unscaledDeltaTime * gameplayTimeScale;
// Use customDeltaTime to advance your coroutines.

This is how you get that cinematic Unity bullet time effect where enemies move at 10% speed while your UI and camera remain responsive. You're essentially creating parallel time dimensions in your game.

code Code download content_copy expand_less

Building Your First Persistent Unity Coroutine Manager

To run coroutines that are not tied to any specific gameplay object, you can create a persistent singleton MonoBehaviour. This object lives for the entire duration of the game, providing a stable context for running global coroutines.

Here's the foundational pattern I use in all my projects:

csharp
public class CoroutineManager : MonoBehaviour

{
public static CoroutineManager Instance;

code
Code
download
content_copy
expand_less
void Awake()
{
    if (Instance == null)
    {
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        Destroy(gameObject);
    }
}

}

This singleton pattern ensures you have exactly one manager that survives scene loads. No more lost coroutines when you transition from your main menu to gameplay.

code Code download content_copy expand_less

Standard Coroutines vs. Custom Managers: The Honest Comparison

Let's be real about when you actually need a custom manager. Here's the breakdown I share with every student who asks:

Criteria Approach A: Standard MonoBehaviour.StartCoroutine Approach B: Custom Coroutine Manager
Best For Simple, component-specific timed events that should naturally pause with the game (e.g., a 3-second weapon reload). Global or long-running processes, UI animations, or any timed logic that must function independently of the game's Unity pause system or Unity Time.timeScale.
Performance Highly optimized by the engine. Very low overhead. Has a small, manageable overhead due to the custom Update loop and manual management of IEnumerator objects.
Complexity Extremely simple to use. It's a single, built-in method call. Requires creating a manager class and a system to track and update the running coroutines, making it significantly more complex to set up initially.
Code Example StartCoroutine(MyStandardRoutine()); CoroutineManager.Instance.Run(MyCustomRoutine());

Actually, wait—I should be clear here. For 90% of your gameplay logic, the standard MonoBehaviour StartCoroutine is perfect. I'm not suggesting you rebuild Unity's entire coroutine system. But when you hit that moment where you need a pause menu, or a slow-motion effect, or background tasks that survive scene changes? That's when this technique becomes absolutely essential.

Why This Changes Everything for Your Game Projects

After working on multiple Unity projects, here's what I've found are the real benefits:

Total Control Over Game Time

It breaks you free from the limitations of the global Time.timeScale, allowing for the creation of sophisticated pause systems and time-manipulation mechanics (like slow-motion or fast-forward).

Decoupled and Robust Timed Events

Coroutines can be started from non-MonoBehaviour classes (by routing them through the manager), and they can continue running even if the object that initiated them is destroyed. This one saved me countless times when dealing with UI managers that needed to persist across scenes.

Centralized Management

A custom manager gives you a single place to monitor, debug, and even globally stop all custom coroutines, which is impossible with the default system. Trust me, when you're hunting down that one rogue coroutine causing a memory leak, you'll thank yourself for having this central control point.

Enables Advanced Gameplay and UI

This is the key to building features like animated menus that work over a paused game, or creating a "bullet time" effect where the player moves in slow motion while enemies and projectiles are even slower.

Three Pro Techniques That'll Save You Hours of Debugging

Let me share the techniques I wish someone had told me about when I was starting out.

Pro Tip #1: Cache Your WaitForSeconds Objects

In standard coroutines, yield return new WaitForSeconds(t) creates a new object, causing garbage collection. For frequently used delays, cache the instruction.

csharp
// Good Practice: Avoid generating garbage in frequently called coroutines.

private readonly WaitForSeconds oneSecondDelay = new WaitForSeconds(1f);

IEnumerator FrequentRoutine()
{
while (true)
{
yield return oneSecondDelay;
}
}

This one small change reduced my frame stuttering issues dramatically. Every time you create a new WaitForSeconds, you're generating garbage that Unity has to clean up eventually.

Pro Tip #2: Create a Custom WaitForSecondsRealtime

You can create your own custom yield instruction Unity type that waits for a duration in unscaled time, making it usable in both standard and custom coroutine systems.

csharp
public class WaitForSecondsRealtime : CustomYieldInstruction
{
private float waitTime;
public override bool keepWaiting => (waitTime -= Time.unscaledDeltaTime) > 0;

code
Code
download
content_copy
expand_less
public WaitForSecondsRealtime(float time)
{
    waitTime = time;
}

}

This is incredibly useful for UI animations that need to run during pause states. You can use it with regular StartCoroutine calls, and it'll automatically use Unity unscaledDeltaTime under the hood.

Pro Tip #3: Use a List to Track Active Coroutines

Your manager should maintain a list of all active coroutines. In its Update loop, it iterates through this list, calling MoveNext() on each one.

csharp
// Inside your CoroutineManager
private List runningRoutines = new List();

void Update()
{
// Iterate backwards so we can safely remove finished routines.
for (int i = runningRoutines.Count - 1; i >= 0; i--)
{
if (!runningRoutines[i].MoveNext())
{
runningRoutines.RemoveAt(i);
}
}
}

The backward iteration is crucial. If you iterate forward and remove items, you'll skip elements and potentially crash your game. I learned this the hard way during a game jam at 3 AM.

code Code download content_copy expand_less

How Superhot, Braid, and Zelda Use Custom Time Control

Let me tell you about some games I've studied that use this technique brilliantly. These aren't just theoretical examples—they're proven implementations that millions of players have experienced.

I've Seen This Technique Used Brilliantly in Superhot

The Mechanic: Time moves only when the player moves. When the player stands still, the game world slows to an extreme crawl, but the UI and menu systems remain fully responsive.

The Implementation: This is a prime example of custom coroutine time control. A TimeManager would control a global gameplayTimeScale. All gameplay-related coroutines (like enemy AI or projectile movement) would be driven by this custom time scale, while a separate UI coroutine manager would run on unscaled time.

The Player Experience: The player gets a unique and powerful tactical experience, feeling like a superhero who can control time. The responsive UI ensures the game never feels broken or unresponsive.

What I find fascinating about this approach is how seamlessly it makes the player feel powerful without feeling overpowered. The time control is the core mechanic, not a gimmick.

One of My Favorite Implementations of This Is in Braid

The Mechanic: The player can rewind time to undo mistakes. The entire game world, including enemies, platforms, and the player character, moves in reverse, but background music and some visual effects might continue forward.

The Implementation: This requires an advanced manager that doesn't just control time scale, but records and can replay game state. However, the core principle is the same: decoupling game logic from the standard flow of time to allow for custom manipulation.

The Player Experience: The time-rewind mechanic is a core puzzle-solving tool, creating a unique and mind-bending platformer experience that would be impossible with Unity's default time management.

After analyzing dozens of games, this stands out because it doesn't just use custom time—it makes time manipulation the entire point of the gameplay.

Here's How Zelda: Breath of the Wild Solved This Exact Problem

The Mechanic: When opening the inventory or map, the entire game world freezes in place. However, the UI is fully animated, with smooth transitions and responsive controls.

The Implementation: Opening the menu sets Time.timeScale = 0. All the UI animations and fade effects are driven by coroutines running in a custom manager that uses Time.unscaledDeltaTime, allowing them to execute over the paused game.

The Player Experience: The player can strategize and manage their inventory in a calm, pressure-free environment, and the high-quality UI animations make the experience feel polished and professional.

This is why I always recommend studying this game's approach to UI. The technical implementation is straightforward, but the player experience is flawless.

Blueprint 1: Your Simple Unscaled Coroutine Runner (Copy-Paste Ready)

Alright, let's tackle this together. I'm going to show you exactly how I approach building an unscaled coroutine runner. This is the exact method I use when I need UI elements to keep working during pause.

What We're Building

Scenario Goal: Create a singleton manager that can run any IEnumerator as a coroutine that is completely independent of Unity Time.timeScale.

Setting Up Your Scene

Unity Editor Setup:

  1. Create an empty GameObject named "CoroutineManager".
  2. Attach a new C# script named CoroutineManager to it.
  3. Create a test script named PausingTester and attach it to any other object.

Let Me Show You How I Approach This

Here's the exact implementation I use in my projects:

Step 1: Build the CoroutineManager Singleton

This will be the central runner for all your time-independent coroutines.

csharp
// In CoroutineManager.cs

using UnityEngine;
using System.Collections;

public class CoroutineManager : MonoBehaviour
{
private static CoroutineManager _instance;
public static CoroutineManager Instance
{
get
{
if (_instance == null)
{
GameObject go = new GameObject("CoroutineManager");
_instance = go.AddComponent();
DontDestroyOnLoad(go);
}
return _instance;
}
}

code
Code
download
content_copy
expand_less
// The key difference: this method uses the manager's context to run the coroutine.
public Coroutine Run(IEnumerator routine)
{
    return StartCoroutine(routine);
}

}

What's clever here is that we're still using Unity's StartCoroutine, but we're running it on a persistent GameObject that never gets destroyed. The real magic happens in the next step.

Step 2: Create the PausingTester

This script will start a coroutine that uses unscaled time and a button to pause the game.

csharp
// In PausingTester.cs
using UnityEngine;
using System.Collections;

public class PausingTester : MonoBehaviour
{
void Start()
{
// Start the coroutine via the manager, not this MonoBehaviour.
CoroutineManager.Instance.Run(UnscaledLogRoutine());
}

code
Code
download
content_copy
expand_less
void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        // Pause or unpause the game.
        Time.timeScale = (Time.timeScale == 0) ? 1 : 0;
        Debug.Log("Time.timeScale is now " + Time.timeScale);
    }
}

IEnumerator UnscaledLogRoutine()
{
    while (true)
    {
        Debug.Log("This message appears every 1 second of REAL time.");
        // This custom yield instruction is the key.
        yield return new WaitForSecondsRealtime(1f);
    }
}

}

When you run this and press Space to pause the game, you'll see those debug messages keep appearing every second. That's your coroutine running independently of the game's time scale. From a developer's perspective, what makes this brilliant is its simplicity—it's just a few lines of code, but it solves a massive architectural problem.

code Code download content_copy expand_less

Blueprint 2: Creating Your Own Bullet Time Effect System

When I'm working on action games, this is my go-to implementation for creating Unity bullet time effect and Unity slow motion gameplay mechanics.

What We're Building

Scenario Goal: Create a system where a group of coroutines can be slowed down or sped up together, independent of the global Time.timeScale, to achieve a "bullet time" effect.

Setting Up Your Scene

Unity Editor Setup:

  1. Create an empty GameObject named "CustomTimeManager".
  2. Attach a new C# script named CustomTimeManager to it.
  3. Create a test script named TimeShiftTester and attach it to any other object.

Here's the Exact Method I Use When Building Time-Scaled Systems

Step 1: Build the CustomTimeManager

This manager will manually iterate through a list of coroutines, giving us complete control over their timing.

csharp
// In CustomTimeManager.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class CustomTimeManager : MonoBehaviour
{
public static CustomTimeManager Instance;
public float gameplayTimeScale = 1.0f;

code
Code
download
content_copy
expand_less
private List _routines = new List();

void Awake() { Instance = this; }

public void Run(IEnumerator routine)
{
    _routines.Add(routine);
}

void Update()
{
    float deltaTime = Time.unscaledDeltaTime * gameplayTimeScale;

    for (int i = _routines.Count - 1; i >= 0; i--)
    {
        // Manually handle custom waits.
        if (_routines[i].Current is CustomWaitForSeconds customWait)
        {
            customWait.Tick(deltaTime);
            if (!customWait.IsDone) continue;
        }

        if (!_routines[i].MoveNext())
        {
            _routines.RemoveAt(i);
        }
    }
}

}

These are the exact settings I use for time manipulation. Notice how we're calculating deltaTime using our custom gameplayTimeScale—that's the key to independent time control.

Step 2: Create a Custom Yield Instruction

This is where we build our own waiting mechanism that respects our custom time scale.

csharp
// In the same CustomTimeManager.cs file or a new one.
public class CustomWaitForSeconds
{
public float Seconds { get; set; }
public bool IsDone => Seconds <= 0;

code
Code
download
content_copy
expand_less
public CustomWaitForSeconds(float seconds)
{
    Seconds = seconds;
}

public void Tick(float deltaTime)
{
    Seconds -= deltaTime;
}

}

This class manually counts down time using our custom delta time. It's not a true CustomYieldInstruction, but it works perfectly within our manual coroutine system.

Step 3: Create the TimeShiftTester

Here's how you can adapt this for your own game's time control needs.

csharp
// In TimeShiftTester.cs
using UnityEngine;
using System.Collections;

public class TimeShiftTester : MonoBehaviour
{
void Start()
{
CustomTimeManager.Instance.Run(TimeScaledLogRoutine());
}

code
Code
download
content_copy
expand_less
void Update()
{
    if (Input.GetKeyDown(KeyCode.Alpha1))
    {
        CustomTimeManager.Instance.gameplayTimeScale = 0.1f; // Slow motion
    }
    if (Input.GetKeyDown(KeyCode.Alpha2))
    {
        CustomTimeManager.Instance.gameplayTimeScale = 1.0f; // Normal speed
    }
}

IEnumerator TimeScaledLogRoutine()
{
    while (true)
    {
        Debug.Log("This message appears every 1 second of GAMEPLAY time.");
        yield return new CustomWaitForSeconds(1f);
    }
}

}

I've configured this dozens of times, and here's my go-to setup: press "1" for slow motion (10% speed) and "2" for normal speed. When you run this, you'll see the debug messages appear faster or slower based on the gameplayTimeScale. Trust me, you'll thank me later for this tip—the ability to control game speed independently from your UI and effects is absolutely game-changing.

code Code download content_copy expand_less

Here's What You'll Gain From Mastering This

After working on multiple Unity projects where I've implemented these systems, here's what you can expect:

Complete Independence From Unity's Global Time: You're no longer at the mercy of Time.timeScale. Your UI animations, background tasks, and special effects can all run on their own timing, creating a much more professional and polished game experience.

Robust Architecture for Complex Games: Once you have this system in place, adding features like pause menus, slow-motion effects, or time-rewind mechanics becomes straightforward. You're building on a solid foundation instead of fighting Unity's defaults.

Better Debugging and Control: Having a centralized manager means you can easily monitor which coroutines are running, how many there are, and stop them all at once if needed. This alone has saved me hours of debugging time.

Professional-Grade UI Systems: Your pause menus will animate smoothly, your loading screens will function properly, and your players will never feel like your game is "broken" when time effects are active.

Your Next Steps to Master Time Control

Let me tell you what I recommend as your learning path from here:

  1. Start with Blueprint 1: Implement the simple unscaled coroutine runner in a test project. Get comfortable with how WaitForSecondsRealtime works and experiment with pausing and unpausing your game.
  2. Build a Pause Menu: Create a simple pause menu that uses your custom coroutine manager for all its animations. Make buttons fade in, text scroll, or UI elements bounce—all while Time.timeScale = 0.
  3. Experiment with Blueprint 2: Once you're comfortable with unscaled coroutines, dive into the custom time-scaled system. Try creating a simple slow-motion effect that activates when the player dodges or takes damage.
  4. Study Real Examples: Play games like Superhot, Braid, or any action game with bullet time. Pay attention to which elements slow down and which don't. That's intentional design backed by systems like what we've built.
  5. Iterate and Refine: As you work on your own projects, you'll discover specific needs for time control. Build wrapper functions, create reusable yield instructions, and customize the manager to fit your game's architecture.

Wrapping Up: The Clock Is Yours Now

Here's the thing—Unity's default coroutine system works great for simple, straightforward timing. But the moment you want sophisticated pause mechanics, Unity slow motion gameplay, or any kind of time manipulation, you need to take control of the clock yourself.

A Unity coroutine manager that runs independently of Time.timeScale is your key to building professional, polished game features. It's the difference between a pause menu that awkwardly freezes everything (including itself) and one that smoothly animates while your game world is frozen. It's what lets you create those cinematic moments where time slows down but your UI remains responsive.

I've used these exact systems in my own projects, and they've transformed how I approach game timing. You're not just learning a technique—you're gaining architectural control over one of the most fundamental aspects of your game.

Go build something awesome with it.

Ready to Start Building Your First Game?

If you're excited about creating polished, professional game mechanics like the custom time control systems we've covered, you need to see the full picture of how these pieces come together in a real game project.

I've put together a comprehensive course that takes you from Unity basics to building complete, professional game experiences. You'll learn not just coroutines and time control, but all the interconnected systems that make modern games work—physics, animation, UI, game state management, and more.

Check out the Mr. Blocks Course on Outscal, where we build a complete game together, implementing exactly these kinds of professional techniques step by step.


Key Takeaways

Common Questions

What is a Unity coroutine manager and why would I need one?+

A Unity coroutine manager is a custom system that runs coroutines independently from Unity's default timing mechanisms. You need one when you want coroutines to continue running while the game is paused (Time.timeScale = 0), or when you want to control the speed of specific coroutines without affecting the entire game. This is essential for animated pause menus, time manipulation effects like bullet time, or background tasks that should persist across scene changes.

How is Time.unscaledDeltaTime different from Time.deltaTime?+

Time.deltaTime is affected by Time.timeScale, so when you pause the game by setting Time.timeScale = 0, Time.deltaTime becomes zero and everything stops. Unity unscaledDeltaTime always represents the actual real-world time between frames, completely ignoring Time.timeScale. This is what allows you to create animations and coroutines that keep running during pause states.

What happens to my coroutines when I destroy the GameObject they're running on?+

When you destroy a GameObject or disable a MonoBehaviour, all coroutines started by that MonoBehaviour StartCoroutine call immediately stop. They don't finish gracefully—they just terminate. This is why you need a persistent singleton manager for coroutines that should survive beyond the lifetime of a specific GameObject.

Can I use a custom coroutine manager with Unity's built-in yield instructions like WaitForSeconds?+

Yes and no. If you're using Unity's StartCoroutine on your manager (like in Blueprint 1), Unity's built-in instructions work fine. However, if you're manually iterating through coroutines (like in Blueprint 2), you'll need to create your own custom yield instruction Unity types because you're bypassing Unity's automatic coroutine system entirely.

How do I create a Unity pause system that keeps my UI animated?+

Set Time.timeScale = 0 to pause the game, then run all your UI animations through a custom Unity coroutine manager that uses Time.unscaledDeltaTime. This way, gameplay freezes but your UI continues to animate smoothly. Use the WaitForSecondsRealtime custom yield instruction for timing your UI animations.

What's the performance impact of a custom coroutine manager?+

There's a small overhead from manually managing the coroutine list and calling MoveNext() in your Update loop, but it's negligible for most games. Unless you're running hundreds of custom coroutines simultaneously, you won't notice any performance difference. The benefits of coroutine time control far outweigh the minimal cost.

When should I use standard StartCoroutine versus a custom manager?+

Use standard StartCoroutine for simple, component-specific timing like weapon cooldowns, respawn delays, or short animations that should naturally pause with the game. Use a custom manager for UI animations during pause, time manipulation effects, or any logic that needs to run independently of the game's time scale or persist across scene changes.

How do I implement a Unity bullet time effect like in Max Payne or Superhot?+

Create a custom time manager (like Blueprint 2) with a gameplayTimeScale variable. Set this to a low value like 0.1f for slow motion. Run all gameplay-related coroutines and movement through this custom time scale, while keeping UI and camera effects on unscaled time. This creates the signature Unity bullet time effect where gameplay slows but the player experience remains smooth.

Can I stop or cancel coroutines running on a custom manager?+

Yes, if you store the Coroutine reference returned by your manager's Run() method, you can call StopCoroutine() on it (if using Unity's StartCoroutine internally). For fully manual systems, maintain a dictionary mapping coroutines to IDs and remove them from your update list. This centralized control is one of the key advantages of having a custom Unity coroutine manager.

What's the difference between CustomYieldInstruction and my own custom wait class?+

CustomYieldInstruction is Unity's official base class that works seamlessly with Unity's built-in coroutine system—it has a keepWaiting property that Unity automatically checks. A custom class like CustomWaitForSeconds in Blueprint 2 only works with manual coroutine management where you explicitly check the wait condition yourself. If you're using Unity's StartCoroutine, inherit from CustomYieldInstruction. If you're manually iterating, create your own simple wait classes.

How do I make sure my coroutine manager persists across scene loads?+

Use DontDestroyOnLoad(gameObject) in your manager's Awake() method, and implement the singleton pattern to ensure only one instance exists. This prevents Unity from destroying the manager when you load a new scene, allowing your coroutines to continue running seamlessly across your entire game session.

Why should I cache WaitForSeconds instead of creating new ones each time?+

Every time you write yield return new WaitForSeconds(1f), you're creating a new object in memory. If this happens in a loop or frequently-called coroutine, you're generating garbage that Unity's garbage collector must clean up, causing frame rate stutters. Caching a single WaitForSeconds instance and reusing it eliminates this garbage generation, making your game run smoother.