Question on Laser Defender part 100 (WaveConfig for Path and Speed)

Hi,

At about 7 mins into this lecture (Part 100: WaveConfig for Path and Speed)
Ricks showing us how to add a SetWaveConfig Method.

My understanding is, the EnemySpawner script instantiates an enemy GameObject and then invokes the SetWaveConfig (WaveConfig waveconfig) method (which it gets from the EnemyPathing component attached to the enemy instance) to pass on reference of the current wave, which the EnemyPathing script needs.

Though my code works, I was surprised the EnemyPathing script didn’t error when its start() method got executed as the code there is looking for a WaveConfig reference.

My question is, when the EnemySpawner instantiates a enemy, How does the EnemySpawner get to invoke the SetWaveConfig method ( to assign the waveConfig reference) before the EnemyPathing start() method gets called?

Hope that makes sense !

Cheers,

Jeremy

Imagine this as a queue.
You run a particular block of code, in this code you instantiate a new script which has to run a block of code when it initializes, this new block of code will run after the first block of code finishes. (This gets a little more complicated with Coroutines). This means that when instantiating an object you can manipulate the script before it runs anything.

You can do a very simple experiment to understand this a little better; move this line of code “newEnemy.GetComponent().SetWaveConfig(waveConfig);” below the yield method in the EnemySpawner script so it ends up looking like this.

yield return new WaitForSeconds(waveConfig.GetTimeBetweenSpawns());
newEnemy.GetComponent<EnemyPathing>().SetWaveConfig(waveConfig);

Now you’ll get the error you were expecting, that’s because you waited, so you gave time to the EnemyPathing script to run before setting the needed variables for it to run correctly.

Thanks for the reply,

That does make sense, though I am now getting a bit confused with the unity Yield WaitforSeconds return statement. I understand it is instantiating a waitforesconds object in which to count based on the time parameter, but what is the yield statement returning to as the enemy spawn loop hasn’t finished yet?

If you are new to coding I suggest you don’t dig too deep into coroutines just yet because that’s a very deep hole and you might end up even more confused.

But just to make things a little simpler, don’t think of returning, think of waiting, yield waits until a certain condition is met, usually it’s time, but it can be other things, that should things more easily to comprehend. It returns a IEnumerator type that tells the code “wait here until this condition is met, then we can continue where we left”.

If you want to dig a little deeper into this matter check this documents out, they might enlighten you with your previous and current question:

https://docs.unity3d.com/Manual/ExecutionOrder.html
https://docs.unity3d.com/2019.3/Documentation/Manual/Coroutines.html

I’m not new to coding in c# and understand most the basics such as value\reference types, classes, structs, constructors, generics, interfaces ( such as IEnumerator etc.) and have used yield in my own projects, but I am new to programming with Unity and do find some of its behaviour difficult to follow sometimes.

I tried use visual studio to step through code to see what it is doing, but for this project it stepped through theand didn’t sync with what was displayed on screen very well.

I’ll check the docs.

Thanks,

1 Like

This is an interesting question and I had to do a bit of logging of that code to see what was happening. I am no expert on coroutines or internals but from what I understand Unity doesnt have true multithreading and under the hood all game code is running on a single thread…Coroutines are a way of letting us run things concurrently and but they are not running in parallel. So when you are inside the SpawnAllEnemiesInWave coroutine you have basically taken control of this single thread and the engine is not going to do other stuff till you yield it back to the engine so it can continue running other stuff outside the coroutine.

So keeping the above in mind although the newEnemy object has been instantiated in the coroutine its start method is not going to be called by the engine till you have yielded the thread back the engine. This is why we are able to call the SetWaveConfig method on its pathing component before its Start method gets called which will happen right after we have yielded from the coroutine.

 private IEnumerator SpawnAllEnemiesInWave(WaveConfig waveConfig)
    {
        for (int enemyCount = 0; enemyCount < waveConfig.GetNumberOfEnemies(); enemyCount++)
        {
               var newEnemy = Instantiate(
                waveConfig.GetEnemyPrefab(),
                waveConfig.GetWaypoints()[0].transform.position,
                Quaternion.identity); // new object created  
                    
            newEnemy.GetComponent<EnemyPathing>().
                   SetWaveConfig(waveConfig); // calling method on new object  
                                             // or component on object
     
             yield return new WaitForSeconds(waveConfig.
                                      GetTimeBetweenSpawns()); // yielding the one and 
                                                           // only thread back to engine...This is when 
                                                           //  engine will resume its thing and will call start on the new object created above etc. 
 }
    }

In the screenshot below you can see the logging calles I added, notice that the Enemypath start is always called after the SpawnAllEnemiesInWave coroutine yields never before.

image

1 Like

Yep, that’s pretty much what is going on in the background, it’s a single thread.

I think the entity component system is multithread, not sure if it’s real or it is “faking it”, haven’t played with that at all, but here’s a nice FREE tutorial if you want to dig into that https://www.udemy.com/course/learnecs/

CoderMonk, thanks for this, that’s helped me dig a little deeper.

I’ve realised where I was getting confused. The yield statement is a c# .net implementation and the coroutine is a unity implementation and I was trying to determine the behaviour of the yield return without understand what the coroutine statement was doing.

This is my current reasoning.

Unity refers to the coroutine as a way in which to execute an Enumerated method in a single frame update. The single frame is the important part of this because the yield return is the way the coroutine is signalled to move onto the next frame and continue execution where the loop left off (after the yield statement) until the loop completes then the coroutine releases control.

If the yield returned null the coroutine would still work (but probably pointless) as it would just make the coroutine move to the next frame on every frame.

In our game code we are returning a new object of type WaitForSeconds instead of using null to make the yield return delay its return to the coroutine so we can manage enemy spawning speed.

Moving onto the EnemyPathing instantiation start() method.

I cant find where it is actually documented, but based on what you have shared I think what is happening is the EnemyPathing start() method wont execute until the yield statement has performed a return. That will signal the coroutine to move to next frame and that is when unity gets the EnemyPathing script to execute its start() method. The delay from instantiation to start () being invoked allows us a window to script any pre-configuration settings the object needs to work.

Which is why if I were to change the order (as below), the EnemyPathing start() would report a reference error.

yield return new WaitForSeconds (waveConfig.GetTimeBetweenSpawns());

newEnemy.GetComponent().SetWaveConfig(waveConfig);

Ok thanks but I need to first figure out regular old unity first and maybe actually make a few games before worrying about that “new” ecs stuff hehehe

Yea glad it helped and this delayed calling of start was not obvious to me either so I learned something new as well.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms