Question about pausing an if statement through a coroutine

Viewed 214

I've just started learning how to program in Unity. Have been trying to do some code every day for the last two-three weeks. I've gotten a bit stuck on a simple script. This script is as follows:

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

public class PlayerControllerX : MonoBehaviour
{
    public GameObject dogPrefab;
    public float timeWaited = 0.5f;


    public void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        // On spacebar press, send dog
        if (Input.GetKeyDown(KeyCode.Space))
        { 
            StartCoroutine(MyCoroutine());
        }
        

    }
    private IEnumerator MyCoroutine()
    {
        Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
        yield return new WaitForSeconds(1f);
        Debug.Log("Waited 0.5 seconds!");
        
       
    }
} `

I want there to be a 0.5 second break in between spacebar presses (Which would spawn a dog). I assume this doesn't work because the coroutine just plays from the start when pressing spacebar. However, I can't think of a way to fix this. Any advice would be very much appreciated!

3 Answers

Your code is starting a new coroutine every time spacebar is pressed. Even though you're technically waiting within each coroutine instance for 0.5 seconds, it does not prevent a new coroutine from starting. So, you're almost correct - coroutine restarts from the top - a new instance of it - not the same one. Also, the statement if (Time.time >= timeWaited) is always true after first few frames - it returns the total amount of seconds since the game started. (https://docs.unity3d.com/ScriptReference/Time-time.html)

Instead, let's use a standard approach to solve this problem - an accumulator value, that is incremented by Time.deltaTime each frame. (https://docs.unity3d.com/ScriptReference/Time-deltaTime.html)

Step 0: Define time to wait

You already have this in your code - it's called timeWaited. I will rename it here, for clarity. This value should be global to the script (in Unity terms) - this a class field in your MonoBehaviour-derived class:

float minimumTimeBetweenDogSpawns = 0.5f;

Step 1: Accumulator variable

You don't have this variable in your code, so I am introducing it here. This value should be global to the script (in Unity terms) - this a class field in your MonoBehaviour-derived class:

float elapsedTimeSinceSpacebarLastPressed = 0;  // feel free to make the name shorter, lol!

Step 2: Accumulate Time.deltaTime on each frame

Now, the Unity Game Engine (in fact, all game engines) keeps track of time that elapsed between each frame. This is a very important concept in game development, as it allows smooth movements of objects regardless of a frame taking a little bit longer or little bit shorter to run - since we know the elapsed time since last frame, we can use it in calculating object positions, how much a unit should advance by, etc. In our case, we are just using this value to get to a desired point - 0.5 seconds in your case, expressed by the minimumTimeBetweenDogSpawns variable.

So, how and where do we accumulate the elapsed time? Simple - just add it in Update:

void Update()
{
   elapsedTimeSinceSpacebarLastPressed += Time.deltaTime;
}

Step 3: Check if it's time to spawn a new dog!

So, what do we do with this elapsedTimeSinceSpacebarLastPressed? So far, it keeps growing... we need to check if it reached the value of interest - in our case, minimumTimeBetweenDogSpawns has been reached., but only if spacebar was pressed. Let's add an if statement in Update to do that. I'll expand on Update from Step 2:

void Update()
{
   elapsedTimeSinceSpacebarLastPressed += Time.deltaTime;

   if(Input.GetKeyDown(KeyCode.Space) 
      && elapsedTimeSinceSpacebarLastPressed >= minimumTimeBetweenDogSpawns)
   {
      // Do stuff! Spawn a dog! Run a coroutine! Have a ball!
    
      elapsedTimeSinceSpacebarLastPressed = 0;   // ...but don't forget to reset the accumulator!
   }
}

Did you catch that last line? Very important - reset the accumulator! This restarts the process, allowing us to spawn more dogs, but not before reaching the value specified by minimumTimeBetweenDogSpawns.

Notes

  • Technically the coroutine is not necessary, but can be useful - suppose that you wanted to do some time-consuming task - say, the dog spawns with an animation that takes approx. half a second, which would be a good reason for the delay in-between the spawns.

  • Contrary to some of the comments on the original post, the difference between machines in terms of speed does not matter for this approach! Time is time - all that might change between machines is how many frames run before the next spawn - but not the time elapsed! In a properly written game, we never care how many frames past - we rely on timing, which is why some version of deltaTime is provided by every game engine and framework out there.

I don't believe Time.time works like that, first of all. The Unity documentation reads:

The time at the beginning of this frame (Read Only). This is the time in seconds since the start of the game.

Time.time is the amount of time in seconds that the application has been running for. It is read-only.

The if statement in the coroutine is checking Time.time, and so it will always be above 0.5 and the dog will always be spawned.

Although a rather low-tech way of doing it, to fix the problem I would set a variable at the start and end of the coroutine, that way you can check another if statement against this variable. It also means that you could put this check next to the Input.GetKeyDown() check, and not have several coroutines running at once.

This would mean that the body of your script would look something like this:

public GameObject dogPrefab;
bool Waiting = false;

void Update() 
{
     if (Input.GetKeyDown(KeyCode.Space) && !Waiting)
     {
         StartCoroutine(MyCoroutine());
     } else
     {
         Debug.Log("Waiting");
     }
}

IEnumerator MyCoroutine()
{
    Waiting = true;

    Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
    yield return new WaitForSeconds(0.5f);
    Debug.Log("Waited 0.5 seconds!");

    Waiting = false;
}

(CoolBots already said most of this in the comments, but I can't comment so I just wanted to put this in anyway. I also have a bad habit of using a lot of coroutines, so as they said it's probably not necessary, but I personally would do it this way.)

You don't need a coroutine to do this. You can simply use your timeWaited variable and every time you press the button you spawn the dog and sum the variable with the passed time since the beginning of the game and your wait time.

Something like this:

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

public class PlayerControllerX : MonoBehaviour
{
    public GameObject dogPrefab;
    public float timeWaited = 0.5f;


    public void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        // On spacebar press, send dog
        if (Input.GetKeyDown(KeyCode.Space) && Time.time >= timeWaited)
        { 
            SpawnDog();
            timeWaited = Time.time + timeWaited;
        }
        

    }
    private void SpawnDog()
    {
       
         Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
        Debug.Log("Waited 0.5 seconds!");
       
    }
} `
Related