Implementing enemy wave system — Part 2
The article here demonstrates the implementation of the large pseudo-code written in the previous part.
- Create a scriptable object that holds the changing data variables and write public methods to retrieve values of these variables.
A scriptable object gets created via a script. Go ahead and make a C# script, name it as per choice and then make two changes that will convert it into a scriptable object.
- First, instead of Monobehavior inheritance, make it a “Scriptable Object”. Second, add an attribute named “[CreateAssetMenu]” before the class starts to get it into the project window.
Now, make a folder named waves or levels as per choice in the project window and see the magic with a right-click.
2. Create 5 assets of this object that hold data for each level. (5 LEVELS)
Here, you should see the 5 different levels and get the boarding pass analogy in place.
The next process will revolve around creating a system that just scans them and reacts accordingly :)
3. In the spawn manager, create an array of this scriptable object type and make it serialized so that assets can be assigned in the inspector.
Drag the levels in the array wihout fail.
4. In the spawn manager, introduce a coroutine that handles spawning of the scriptable game objects until the array’s last index position.
- Declare a variable named starting Index/level/wave as per choice.
int startingIndex = 0;
Two major things to observe here.
- First, we are initiating the coroutine only while the spawning is allowed which gets disabled upon the player’s death. But, it is again checked with an if statement, because a player may die just right after the for loop condition was checked.
- The micro time passed between these two checks makes a world of difference because if the second check was not in place, even if the player would die, the message of the next level will be displayed regardless, which we do not want to :)
- Second, we are yielding a coroutine in the end instead of the usual waitforseconds( ) because both acts are interconnected.
- If you think a little deeper, the level check coroutine can not function until the previous level gets completed and the next level will not begin until the level check passes the number of levels. Both depend on each other to make things work. The only thing connecting them in between is the player’s death.
Also, observe that we are passing the level as an argument to the yielded coroutine which is basically the boarding pass of a given level :)
These are very concrete distinctions that shall be cemented in the reader’s brain.
5/6 The same coroutine shall initiate another coroutine that takes a data asset as its argument, runs it until the last enemy spawns of the data asset’s enemy count. Upon completing the level, the coroutine should increment the level and pass back the next level number to the main coroutine that sends the relative data asset.
There are a few things happening here and hence let us walk through each of them.
Please revert back to the image below as soon as something does not click in the following steps.
- The parameter of this coroutine expects a data asset which we have created in the 2nd step.
- We have provided it with a data asset and in the first loop, we are getting the total number of enemies written in the asset via its’ method to retrieve the enemy count.
- This means that the loop will not exit until the entire count is delivered in the game.
- As soon as we get in, we are again checking if the player is still alive by the if statement.
- Here, we instantiate the item that contains the type of enemy.
- Since there are two different enemy prefabs in my project, there is a check for the name of the item and then it passes the written speed from the data set to the enemy script component by retrieving via a public method.
- The associated enemy script takes care of the translation of enemy prefab in the game and has a speed variable that gets set via this step.
public void setEnemySpeed(float value)
_speed = value;
- Then, it waits for 1 second before instantiating another enemy item.
- After all enemy items of each enemy type are delivered, the loop ends and it waits for 5 seconds before going back to the first coroutine.
- The break keyword makes sure that the loop does not get into an infinite state after the full count of enemies of the first data asset/level/wave is over.
- However, before returning to the first coroutine, the index counter gets an increment of 1 that basically says this coroutine is expecting the next data asset/wave/level.
7. When the main coroutine sends the next data asset, it must display the level name on the screen. However, the display shall end within a few seconds.
To accommodate this, we first need to get a UI element on the canvas of type “Text” and then get a reference to it along with the UI manager object.
private Text _WaveText; [SerializeField]
private UI_Manager uI_Manager;
Now, the task is to first enable the waveText element when called, display some text, wait for a few seconds so that the player can read it and, disable the component before closing out.
- I am adding 1 to the value because the index begins at 0 in the spawn manager which will display level/wave 0. Not cool :)
- I am also setting the text property with a concatenation operator.
- This coroutine then calls another coroutine that waits for 2 seconds and then disables the text component.
8/9. If all data assets get scanned, initiate the game exit procedure from the same coroutine that increments levels. The exit procedure will also display a message to the screen about player’s win.
In order to know if the player won, there is just one check. If all waves are done. The spawn manager takes care of incrementing new levels so let us go back there.
- If the current index value after increment is more than the total length of the available waves/levels/data assets, we first stop the whole spawning mechanism and then initiate the procedure(a coroutine again :) )for a shining exit for our player.
- The game manager has been referenced in the script just like other references and it triggers a method written in the game manager script.
That should be it. I know this article is longer than my other articles but could not summarize it better.
Here is the final result :)
Thank you very much