Enemies and the SpawnManager — Day 4 of becoming a Game Developer
What is a game if you cannot shoot at hordes of enemies? In this article, we’ll be covering just that. In my previous articles, I’ve covered setting up the Player as well as being able to shoot Lasers.
It starts with Physics
Before we get too deep into our enemies and spawn manager we need to understand a crucial part of how objects interact with each other in Unity. We’ll want our objects to be able to interact with each other and we want to make certain things happen when objects collide. This is where we’ll start to use Physics in Unity.
Unity Physics is a deterministic rigid body dynamics system and spatial query system written from the ground up using the Unity data-oriented tech stack.
As you can see the player collides with the block and in some cases we don’t want that to happen. Let’s pretend that the white cube is another player. In real life, we cannot just walk through people. We can introduce physics using a RigidBody, which we can use to determine how other objects interact with an object where the RigidBody resides in.
The Enemy Setup
Before we can really dig into the RigidBody and its physics we need to set our enemy up first. As before, I’ve created a 3D Cube in the “Hierarchy” and named it Enemy. Next, I’ve created a new Material in the “Materials” folder and named it “Enemy_mat”. I’ve also created a new script in the “Scripts” folder and named it “Enemy.” I’ve then assigned both the Material and Script to my Enemy.
It was time to decide how we’ll be setting up the Physics and decided that the RigidBody will be added to the Enemy Object, and therefore the collision logic would be written in the Enemy Script.
When looking at the Enemy in the Inspector we can see a few things happening. We’ll be focusing on two things on the Enemy. First is the “BoxCollider”, and the “RigidBody”
OnCollisionEnter VS OnTriggerEnter — When to use them
On the “BoxCollider” you’ll notice there’s a checkbox named “Is Trigger”. It defines whether you can actually collide with the object or not. Let’s take a quick example of an NPC character and a coin you can pick up. You wouldn't want to walk through another person, but you’d want to collect the coin. Therefore the coin would have the “Is Trigger” checked, thus allowing the player to walk over the coin and making it so it looks like its being picked up. On the “RigidBody” I’ve set the “Use Gravity” checkbox to false since we don’t have gravity in space. I’ve also created and assigned tags to my player, the laser and enemy objects named accordingly.
Understanding this we can now configure our objects to interact with each other. I’ve set my player, laser prefab and enemy all to have the “Is Trigger” to be checked. At this point, I’m happy with my Enemy and decided to make it a prefab as well since we’ll be spawning them in with a spawn system.
We have so much happening in the Unity Editor, but we haven’t covered much code yet and we still have a while to go. We want the Enemy to move in a downwards direction and do something when we collide with our Player. When the Enemy reaches the bottom of the screen we would teleport it to the top of the screen at a random position. This adds a bit of randomness to the game and makes it harder for the Player if they don’t destroy the Enemies fast enough.
The Enemies are now dropping as intended. Maybe later I’ll change their speed every time they respawn to add more randomness. But for now, I’m satisfied.
Next, we’ll be coding the logic for the collisions between the Player, Lasers and Enemies. Since we’re using the “Is Trigger” checks, we use an “OnTriggerEnter” function in our Enemy Script to check if the enemy is colliding with a Laser or Player.
Our Player will have a health system which we’ll cover in the Player Script. We’ll be creating a private int variable called “_lives” and give it a value of 3. Next, we’ll be creating a “Damage()” function that other objects and scripts can refer to.
As you can see, I can now destroy the enemies and if they collide with me I lose a life. Currently, you cannot see I’ve lost a life but in the inspector, it will show 2 lives remaining.
Spawning Enemies with CoRoutines
I want to spawn a new enemy every 5 seconds. We’ll be doing that using an “IEnumerator”. We’ll be using an empty GameObject in the “Hierarchy” called “SpawnManager”. In my “Scripts” folder I created another script called “SpawnManager” and attached it to the SpawnManager object.
A “CoRoutine” always needs to end with a “Yield” which interrupts the “CoRoutine” so it comes back at a given point later. In our case, we want it to 5 seconds before spawning a new enemy. Therefore we can use something called “WaitForSeconds” and a number. When the time we declared has passed, it will cause the function to continue from wherever the yield forced it to stop. In this case, it would be the start of the while loop. This will continue until we break out of our loop when the player dies.
I’ve started the Routine in the Start() function using “StartCoroutine(SpawnRoutine());” syntax.
And there we go, everything is interacting with each other and we have an endless loop of enemies spawning as long as we have lives left.
Spawning Objects without the Clutter
Something that has been bugging me till now has been all of the objects spawning in the “root” of my hierarchy and it’s a pain to debug when you have a bunch of clones filling up the window. I’ve created a new empty object in the “Hierarchy” and called it “Containers” and in there another object called “Enemies” and “Laser”. In my scripts, where the enemies and laser spawn, I’ve referenced the containers using variables and now the enemies and lasers spawn inside those containers.
I think this pretty much covers this lengthy article. I hope I didn’t jump around too much today. I’ve covered two sections of the course today and I’m trying to document everything I learn but sometimes it’s harder. Today I’ve spent approximately 2 hours learning and spent 3 hours writing this article. Being partially dyslexic makes things a lot harder for me.