1
\$\begingroup\$

I'm generating a level, and I have an optional delay to show the steps level generation one by one.

If my generationDelay boolean is true, I want to use StartCoroutine to spread the generation out over time. If it's false, I want to call the same method but without StartCoroutine.

The problem is that the method is type IEnumerator.

I can make two methods one type of IEnumerator and one not. But I wonder if there is a way to use one method.

In the first script top i did:

public bool generationDelay = false;

Then in a method:

private void BeginGame()
    {
        mazeInstance = Instantiate(mazePrefab) as Maze;

        if (generationDelay == true)
        {
            StartCoroutine(mazeInstance.Generate(generationDelay));
        }
        else
        {
            mazeInstance.Generate(generationDelay);
        }
    }

Then the other script where the method Generate is: In the top:

public float generationStepDelay = 0.01f;

Then the method Generate:

public IEnumerator Generate (bool generationDelay) {

        if (generationDelay == true)
            WaitForSeconds delay = new WaitForSeconds(generationStepDelay);
        cells = new MazeCell[size.x, size.z];
        List<MazeCell> activeCells = new List<MazeCell>();
        DoFirstGenerationStep(activeCells);
        while (activeCells.Count > 0) {
            yield return delay;
            DoNextGenerationStep(activeCells);
        }
    }

I'm getting an error on the line:

WaitForSeconds delay = new WaitForSeconds(generationStepDelay);

"Embedded statement cannot be a declaration or labeled statement"

And an error on the line:

yield return delay;

"The name 'delay' does not exist in the current context"

The reason I want to use a bool variable is that when i'm using StartCoroutine it's kind of slow, even if I change the value of generationStepDelay to 0 it's kind of slow. And if I'm not using StartCoroutine and not using the Generate method as IEnumerator it will work fast.

So i wonder what should I do and how?

\$\endgroup\$
1
\$\begingroup\$

First of all: The errors you cite have nothing to do with the problem you're solving - they're just syntax mistakes.

When you define delay inside an if like that, the definition isn't visible to the rest of the code - it exists only inside the scope of the if itself (which is just that line defining the variable alone - not very useful, which is why the compiler tells you not to do that)

To conditionally assign a value, you first need do define a spot it can live in, unconditionally. Then fill that slot based on your condition:

WaitForSeconds delay = null;
if (generationDelay == true)
    delay = new WaitForSeconds(generationStepDelay);
// If we fail out of the if, delay still has a well-defined value of null.

Secondly, if you want to complete the whole operation in a single frame, you don't need to conditionally define your WaitForSeconds at all - you can simply ignore the IEnumerator's current yield value, like so:

void RunCoroutine(IEnumerator routine, bool allAtOnce) {
    if(allAtOnce == false) {
        // Start the coroutine normally, and let the Unity 
        // engine handle scheduling it with appropriate waits.
        StartCoroutine(routine);
    } else {
        // Race through all steps of the coroutine until MoveNext()
        // returns false when the method finishes / yields break,
        // and don't wait for anything in between!
        while(routine.MoveNext());
    }
}

Note that this is not safe to use for coroutines that do things like...

  • Wait on a WWW or ResourceRequest object until some data is loaded from the disc/network (you'll skip this wait and try to use the data before it's loaded)
  • Chain control to a nested coroutine (you'll ignore this new coroutine and resume the parent routine immediately)
  • Expect other game systems to do work between yields (eg. yielding WaitForFixedUpdate to let the physics step ahead)

It will also block the main thread until the coroutine finishes ALL of its work, which can cause your game to stutter or become non-responsive if the coroutine takes a long time.

To avoid these problems, you could just as easily stick to using vanilla StartCoroutine all the time, and just set your WaitForSeconds value to 0.0f if you don't want to delay. That way you'll wait the minimal amount of a single frame before resuming, giving the engine a chance to still run its other updates so the game remains responsive and you can show things like progress bars. And then you can still wait on file access/other coroutines/etc. without breaking anything.

If one step per frame is too slow, you can use a loop counter so you only yield every 5th or 10th or 100th loop. eg.

int batchIndex = 0;
while (activeCells.Count > 0) {
    if(++batchIndex > stepsPerBatch) {
        yield return delay;
        batchIndex = 1;
    }
    DoNextGenerationStep(activeCells);
 } 
\$\endgroup\$
  • \$\begingroup\$ Great, I will use the batchIndex approach. And just for the knowledge is it a bad thing to make two Generate methods ? One i call it public IEnumerator Generate() and the other public void GenerateWithoutStartCoroutine() and in the second one i will have the same code but without the yield and the WaitForSeconds and in the first script i will just check if the bool is true or false and will call for each method. \$\endgroup\$ – Dragon Flea Aug 18 '17 at 22:36
  • \$\begingroup\$ You could build two versions of your generate method, but then you're putting more maintenance work on yourself to keep the two versions in synch as your game evolves. And for what benefit, since you can run the IEnumerator all in one frame if you want to using the pattern shown above? \$\endgroup\$ – DMGregory Aug 18 '17 at 22:48
  • \$\begingroup\$ Thank you. The solution using the batchIndex is working great. \$\endgroup\$ – Dragon Flea Aug 18 '17 at 23:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.