Doers of Stuff.org

A place to Do Stuff and see Stuff Done…

Creating A Cooldown System in Unity

In video games, a Cooldown system is just a fancy way of slowing things down. It allows for a touch more realism in your interactions (such as weapons reload) rather than just letting the technology take over (spamming the fire button). I’m sure as this journey goes on, I’ll encounter far more complex cooldown systems, but the simplest one really is simple.

Unity includes another built-in property called Time.Time. This time is nothing more than a timer that starts at the beginning of the game. Therefore, the simplest possible cooldown system involves nothing more than declaring a cooldown period and a variable set to the current Time.Time plus the cooldown period. After that, we do nothing more complex than add a condition to our check for the fire button keypress that compares the current Time.Time to the variable we just set. Once Time.Time is greater than that, the player can fire again.

using UnityEngine;

public class Player : MonoBehaviour
{
    ...

    [SerializeField] private float fireRate = 0.5f;
    [SerializeField] private float canFire  = -1;

    ...

    void Update()
    {
        MovePlayer();
        CheckBoundaries();

        if (  Input.GetKeyDown(KeyCode.Space)
           && Time.Time > canFire
           ) 
           {
              Instantiate(laserPrefab, transform.position + laserOffest, Quaternion.identity);
              canFire = Time.Time + fireRate;
           } 
             
    }

    ...

Easy-peasy. Every time we fire the laser, we reset canFire and don’t allow the player to fire again (no matter how many times the fire button is pressed)) until Time.Time is greater than canFire.

This is all pretty straight forward and not really that complicated. However, I feel like readability is starting a downward trend and we’re barely into this project. When the if statement was just checking if the spacebar had been pressed it wasn’t really that bad. But now we’re checking two things and not really explaining why those particular things are being checked. What will we do if it gets more complex? Maybe more conditions? Or maybe more ways to trigger fire action? What about a PowerUp that shortens the cooldown period? Then there is the action within the code block. Again, when all it did was instantiate a laser, it was fine. But now we’re starting to manage state here also.

I feel a need to pre-emptively refactor and replace the minutia of how to do things with better named functions and variables that tell us what we are doing.

First, let’s pull out the code in the if block and make a new function called FireLaser(), because that’s what we are actually doing. This function will contain everything related to actually firing off the laser. While we’re at it, let’s pull out the check for spacebar press as well.

    ...

    void Update()
    {
        MovePlayer();
        CheckBoundaries();

        if (  playerHasFired()
           && Time.Time > canFire
           ) { FireLaser(); } 
             
    }

    ...

    bool playerHasFired() { return Input.GetKeyDown(KeyCode.Space); }


    void FireLaser()
    {
        Instantiate(laserPrefab, transform.position + laserOffest, Quaternion.identity);
        canFire = Time.Time + fireRate;
    }

Our if statement is now considerably less clunky and less of an eyesore. However, I still don’t like the time keeping part. In fact, I’d like the cooldown to be a boolean of some sort, rather than a comparison. I feel like that would be more readable and maybe even a wee bit faster? Maybe not, but I feel like there are just a few bits or bytes in there somewhere who would thank me for making an easier check. But how to do that? We can’t just make canFire a boolean, because we have to have something telling us when it’s okay to re-enable the lasers.

Well, what are we really trying to determine with the Time.Time > canFire comparison? We’re trying to determine if the laser can fire or not. Furthermore, that is determined by the time since the laser was last fired. You might recall the Destroy() method we used to remove laser blasts that went off screen. It took a parameter that told the game object how many seconds should elapse before automatically removing itself. Is there something like this we can do?

Well, there is. We can use a coroutine. While we won’t go into the details here, coroutines are a nice way to peel off a bit of code and let it manage itself. In this case, we’re going to basically create a timer that automatically updates whether or not our laser is ready to fire again.

using UnityEngine;

public class Player : MonoBehaviour
{
    ...

    [SerializeField] private float      fireRate     = 0.5f;
    [SerializeField] private bool       laserCanFire = true;

    ...

    void Update()
    {
        MovePlayer();
        CheckBoundaries();

        if (  playerHasFired()
           && laserCanFire 
           ) { FireLaser(); }
    }

    bool playerHasFired() { return Input.GetKeyDown(KeyCode.Space); }

    ...

    void FireLaser()
    {
        Instantiate(laserPrefab, transform.position + laserOffest, Quaternion.identity);
        laserCanFire = false;
        StartCoroutine(LaserCoolDown());
    }

    IEnumerator LaserCoolDown()
    {
        yield return new WaitForSeconds(fireRate);
        laserCanFire = true;
    }
}

Again, skipping over some of the details, we first change the name of our variable from canFire to laserCanFire because I think it reads better in the if statement. Next, we change it from a float to a bool and initialize it as true rather than -1. Then, in the FireLaser() method, we set laserCanFire to false, rather than the current time plus an offset.

Next, we fire off the coroutine. The coroutine immediately yields control and sets a timer equal to the defined fireRate. Only when that time has expired, is laserCanFire reset to true and the coroutine finishes and exits. The laser cool down is now self-managing.

Just like the destroy timer on the laser bolts, the cooldown system for our laser gun is now “fire and forget.”

Leave a Reply

Creating A Cooldown System in Unity

by Robert time to read: 4 min
0