Thursday, June 8, 2017

unity - How can I switch between starting a coroutine vs running the routine all at once?



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 activeCells = new List();
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?



Answer



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);
}

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...