COO
COO

Reputation: 9

(C# Unity) yield return new WaitForSeconds(); preventing the rest of code

These are the relevant lines:

 public IEnumerator BattleStart(string toucher, string touched, Unitdetails playerStatsScript, Unitdetails enemyStatsScript)
    {
        //Accumulating values
        playerStats = playerStatsScript;
        enemyStats = enemyStatsScript;
        Debug.Log(playerStats.unitName + " vs " + enemyStats.unitName);

        //setting up battle and GUI
        battleState = whoseTurn.Start;
        DialogueText.text = "You encountered " + enemyStats.unitName + ".";
        World.SetActive(false);
        Battle.SetActive(true);
        //Adding the player and enemy, placed as UI.
        GameObject playerPrefab = Instantiate(NametoObject[playerStatsScript.unitName], playerPos, Quaternion.identity);
        GameObject enemyPrefab = Instantiate(NametoObject[enemyStatsScript.unitName], enemyPos, Quaternion.identity);
        Debug.Log("This area of code reached. StartToPlayer:  " + TimerManager.StartToPlayer);

        yield return new WaitForSeconds(TimerManager.StartToPlayer);
        //note that we need this WaitForSeconds to exist. We are making this a regular method to focus on other errors.
        StartCoroutine(PlayerTurn());
        Debug.Log("This area of code reached.");
    }

Explanations: I am creating a (2D-based) turn-based combat game, and the lines of code written allows me to have multiple types of enemies, so, you will see that I have the parameters, the first two just to get the names of the player and the enemy - this script was designed as if there are many players, even though there is only one, don't change that please.

If you observed the code, I took a little bit of inspiration from Brackeys' tutorial, although I did not directly copy it, they have common elements.

Here is some useful information to know about this code:

  1. Unitdetails is a script, mostly contains only variables, but doesn't really have a function. It is held by entities in the world (by the way, the world and battle are in the same scene).
  2. World is a parent gameObject holding all the elements in the world.
  3. Battle is a parent gameObject holding all the elements in the battle.
  4. battleState - mostly irrelevant and was never really used, but I don't think anything is wrong with it.
  5. NametoObject is a dictionary, and is (string, gameObject). The player and enemies have unit names, which the battle system script translates into prefab gameObjects that will be spawned.
  6. TimerManager is a script that holds variables - and that's pretty much all it does. This is to keep my codes organized, and so that I can modify it in the Unity editor, so if I feel like the text dialogue is too long or too short, I can modify it accordingly.

TimerManager.StartToPlayer is a float, and I assigned the value "2" to it (The Debug.Log confirms that it is working as expected)

However, when I finally reach yield return new WaitForSeconds(TimerManager.StartToPlayer), it gets disastrous. The codes don't get executed.

From what I know, all these codes are correct. There might be something wrong with the Unity editor. I have asked ChatGPT for help several times, and it wasn't so reliable, because it kept giving me "solutions" that I've already done.

When I removed yield return new, only then the codes started to run.

The reason why I added the wait is to give the player some time to read the dialogue, instead of hopping straight into the player's turn.

Important things to note: -The game didn't crash, and Time.timeScale was 1. If you think those two were the problem, then unfortunately you are wrong. -I created the project twice, having a similar script, and the problem was replicated - so definitely not an editor problem. -Definitely not the nested coroutine's fault. When I removed the StartCoroutine(PlayerTurn()); code, it still worked just like it should. -The gameObject that holds this script never gets deactivated or interrupted. Like I said, this coroutine just stops after yield return new WaitForSeconds(TimerManager.StartToPlayer); - confirmed with Debug.Log and others

And mysteriously, when I remove

        Battle.SetActive(true);
        //Adding the player and enemy, placed as UI.
        GameObject playerPrefab = Instantiate(NametoObject[playerStatsScript.unitName], playerPos, Quaternion.identity);
        GameObject enemyPrefab = Instantiate(NametoObject[enemyStatsScript.unitName], enemyPos, Quaternion.identity);```

NOTE THAT BATTLESYSTEM IS NOT PART OF THE DEACTIVATED GAMEOBJECTS
the WaitForSeconds works properly again.

Upvotes: 0

Views: 356

Answers (2)

COO
COO

Reputation: 9

I have already solved the problem. So, please do not answer this anymore. What I discovered is when the gameObject got deactivated, the coroutine also goes down with it. However, if I make a coroutine inside of that coroutine, the new nested coroutine will not be affected by the deactivation of the gameObject.

Upvotes: 0

TheG
TheG

Reputation: 66

As JohanP said, the problem here will be that you're not starting the coroutine with StartCoroutine.

If you're new to Unity and/or C# in general, the whole idea of a coroutine may seem a bit strange, so I'm going to try and give a basic explanation, as well as link to some much more in-depth information if you're interested.

Let's look at the coroutine you've written. Its return type is IEnumerator. Without getting too deep into the inner workings of C#, this is a type that the function is expected to return, hence why the compiler will get mad if you remove the yield return statement.

So when you call your function/coroutine as you have done, it runs, gets to the return statement, and then stops. This is expected, just like if you were to write any other function and have it return out.

The way coroutines are designed to work, the point, if you will, is to allow for multiple things to execute "at once". I use speech marks as this isn't really the case. Unless you go out of your way to implement threading, execution of your code is single-threaded. What this means is only one thing can be happening at one time. This is why having a long loop, or an endless loop, will cause your game to crash/hang, as the rest of the code isn't being executed due to it being stuck at that loop. With a coroutine, each section of code is iterated though until a yield statement is hit. At this point execution is paused until the engine is ready to resume and run the next code block.

This is something that the engine implements, so we must pass in the coroutine we wish to execute using the StartCoroutine method.

You can visibly see the benefits/workings of coroutines by following this example:

void Start()
{
    StartCoroutine(Example());
}

Ienumerator Example()
{
    while(true)
    { 
      yield return null;
    }
}

Running this will do (seemingly) nothing. However, move that while loop into an Update method and your Unity will crash where it gets stuck in an infinite loop. (You don't actually have to try this! ;) )

The coroutine is being suspended at the yield return null statement, and then picked up again the next frame, preventing the game from hanging.

As you already know, there are multiple yields that can be used in coroutines, such as WaitForSeconds. You can also write you own custom ones.

This is quite a basic explanation of coroutines. The way Unity handles them kind of exploits Ienumerator and yield for its own purposes. If you're interested in learning more I'd start here.

Upvotes: 0

Related Questions