2

What I need is to increment player stats every few second and be able to interrupt this process with any key pressed down.

Below is my coroutine. It's running endlessly, any advice?

IEnumerator PlayerAction(PlayerStats playerStats, int incrementor1) 
{
    bool loop = true;
    while (loop)
    {
        if (Input.anyKeyDown)
        {
            loop = false;
            Debug.Log("Action is break");
            yield break;
        }
        else
        {
            yield return new WaitForSeconds(2);
            playerStats.Energy += incrementor1;
            GameClock.Tic++;
            Debug.Log("My first courutine is working!");
        }
    }
}
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Have you researched for ways to stop a coroutine? How have you already tried to approach this problem? – Serlite Jan 06 '17 at 01:07
  • Yes, and I didn't find the answer. I moved check to the update and tried to use StopCourutine(), but it looks like a bad solution. – Андрій Драган Jan 06 '17 at 08:52
  • 1
    @Kardux OP wants to break out of the `while` loop if you look carefully in the ` if (Input.anyKeyDown)` code. Bad title but now fixed. – Programmer Jan 06 '17 at 14:34
  • Since you utilize `yield return`, the problem may also be related to your consumer code. Your function code will only be executed when you actually iterate the returned `IEnumerator`. Can you show some of that? – grek40 Jan 06 '17 at 14:55
  • @grek40 This problem has been solved. That's because OP used `yield return new WaitForSeconds(2)` which misses the frame when `if (Input.anyKeyDown)` is true. Check OP's comment under my answer. – Programmer Jan 06 '17 at 14:57
  • @Programmer probably yes (we won't really know until its accepted). Even so I want to point out that `yield return` creates a state machine and the timing of its execution is largely depending on the caller code so unless steady iteration of the return is ensured, any attempt at capturing input will fail, no matter the `WaitForSeconds` detail. – grek40 Jan 06 '17 at 15:01
  • @Programmer but maybe my whole point doesn't make much sense in context of Coroutine. – grek40 Jan 06 '17 at 15:02
  • @grek40 Its not accepted because OP is new and has not been back since the thank you left under the answer. `Input.anyKeyDown` reports true **once** in a frame. When `yield return new WaitForSeconds(2);` is called, Unity waits on that line of code until after 2 seconds has passed. Let's say that you pressed a key to exist within that time it is waiting for 2 seconds, `if (Input.anyKeyDown)` won't even be checked since it is still waiting. After waiting for 2 seconds then if (Input.anyKeyDown)` is checked again. By that time, `Input.anyKeyDown` is now false. – Programmer Jan 06 '17 at 15:12
  • The solution is to use any way to count time instead of `yield return new WaitForSeconds(2);` which stops there. I tested everything I just said even before posting and you can prove it **only** if you have Unity installed. The reason I care and I am making these comments is that people didn't understand the question and problem. They ended up down-voting such as great question or asking more info info which is not needed. – Programmer Jan 06 '17 at 15:15

2 Answers2

2

It's running endlessly, any advice?

Yes. Don't use yield return new WaitForSeconds to wait. You will miss the Input event (if (Input.anyKeyDown)) during this frame.

Another way to make counter in a coroutine function is to use Time.deltaTime; to increment a variable, then wait with yield return null; which only waits for a frame instead of seconds, when you use yield return new WaitForSeconds.

This is what I explained above should look like:

IEnumerator PlayerAction(PlayerStats playerStats, int incrementor1)
{
    const float secToIncrement = 1f; //When to Increment (Every 1 second)
    float counter = 0;

    while (true)
    {
        //Check if we have reached counter
        if (counter > secToIncrement)
        {
            counter = 0f; //Reset Counter

            //Increment Player stats
            playerStats.Energy += incrementor1;
            GameClock.Tic++;
            Debug.Log("My first courutine is working!");

        }

        //Increment counter
        counter += Time.deltaTime;

        //Check if we want to exit coroutine
        if (Input.anyKeyDown)
        {
            Debug.Log("Action is break");
            yield break;
        }

        //Yield in a while loop to prevent freezing 
        yield return null;
    }
}

Make sure to call a coroutine with the StartCoroutine function like StartCoroutine(PlayerAction(yourPlayerStat,2));

Programmer
  • 121,791
  • 22
  • 236
  • 328
0

I guess your are missing the input detection in coroutine, my advice to detect input in update and used a variable to check it, something like below:

bool isAnyKeyDown = false;//detect is anykeypressed

void Update(){
  if (Input.anyKeyDown){
          isAnyKeyDown = true;
   }

}

IEnumerator PlayerAction(PlayerStats playerStats, int incrementor1) 
{
    bool loop = true;
    while (loop)
    {
        if (isAnyKeyDown)
        {
            loop = false;
            Debug.Log("Action is break");
            StopCoroutine("PlayerAction");
            isAnyKeyDown = false;
            yield break;
        }
        else
        {
            yield return new WaitForSeconds(2);
            playerStats.Energy += incrementor1;
            GameClock.Tic++;
            Debug.Log("My first courutine is working!");
        }
    }
}
Muhammad Faizan Khan
  • 10,013
  • 18
  • 97
  • 186