Rapid Prototyping in Unity

Joseph Youngquist
8 min readJul 15, 2022

--

What we’ll build in this article

Objective:

Build a prototype space shooter, that has enemies that spawn randomly and at random intervals, player that can shoot with a cooldown and have collision detection.

The Back Story:

We all want to build the next great game. I’m just learning so I’m pretty stinking happy that things are moving, shooting, and dying! In the process of learning you don’t have to make things that look AAA ready, a simple 3D Cube for player and enemies and a capsule are all we’re using here for now, we’ll add the pretties in the next article!

So what’s going on here?

We’re going to go on a very fast pace here and cover the following:

  • Shoot Lasers that get destroyed once off screen if they didn’t hit the mark
  • Have Lasers fire with a cooldown — no spam firing allowed!
  • Have Enemies that can impact with Lasers and the Player
  • Make a SpawnManager to handle the Instantiating of the Enemies at random X positions and at a random interval between a min and max wait-time.

If you do not know what a Prefab is in Unity then hit the manual here to learn about them and why you should use them.

Lasers and shooting them:

Making a new Laser Prefab

Right click in the Hierarchy window and select 3D Object > Capsule. We’ll make the position to (0, 0, 0) and we’ll scale it down to 0.2 Now to make a prefab, drag the capsule from the Hierarchy window down into your Project window. I like to keep my prefabs in a folder called Prefabs. Once you drop the capsule in the Project window, the GameObject will turn light blue to signify it’s a prefab instance.

To make the player fire lasers we need to edit the Player behavior which means we’re going to work on the Player.cs script. My previous article covered the player movement, this article we’re going to cover how to fire, limiting how fast we can fire, player lives ☠☠☠ and death 😱. Here’s the complete code for the player Player.cs script.

For firing we want to have a “cooldown” effect where the player cannot just rapid fire a bunch of lasers…you know the engines can’t take it!

On lines 9 and 10 is where we set the firing rate and we’re using a [SerializeField] for the variable so we can play test the rate in the editor without needing to recompile the game code. For example, I started off with a firing cooldown of half a second and I dialed it down until the rate felt right. From there I hardcoded the value but still left it as something that could be edited in the Unity Editor in case I wanted to play with the rates again.

The actual firing of the Laser is handled in the Update() method with the code on lines 40-43 That look to get the player’s fire button presses and we check to see if we _canFireLasers which is a Boolean value, which we’ll get to in a sec. If the conditions are true then we call the FireLaser() method.

Line 73 defines the FireLaser() private method. We first set the _canFireLasers to false and then we Instantiate a new laser with a position that is the Player's transform.position but we want to add a small offset to the y position of the laser so it looks like it’s coming from the top of the player rather than from inside.

The StartCoroutine(LaserCooldown()) is the magic for the cooldown effect. Coroutines can do so many things for game effects. Read the docs for StartCoroutine() and which variation you want to use, for example in my code above I went with actual IEnumerator routine vs the string option. With the String option there are some benefits and negatives, mainly the garbage collection overhead with the string option.

The IEnumerator LaserCooldown() routine sets essentially a “timeout” for when the rest of the code below the yield statement completes. In my case, I’ve got a WaitForSeconds() that’s taking in the _playerFireRate which is set to 0.15f seconds. Meaning every 0.15 seconds the player can fire a laser. I then StopCoroutine(LaserCooldown()) since we don’t need to keep it running. The next time the player fires, we start it up again.

We’ve attached a Laser.cs script to the Laser Prefab to handle the movement of the Laser:

On line 8 we have the speed of the laser and set as a [SerializeField] so we can tweak the speed in the Unity Editor. The Update() method moves this instance of the Laser up on line 13 and line 15 checks to see if the laser is off the screen (it missed) and if so, we Destroy(this.gameObject) this instance. Simple enough, next we get into the Enemy!

Every Game needs the “Bad Guy”

What fun is a space shooter without evil bad guys to shoot? For our prototype game, we’re using a 3d Cube, scaled down to about 0.45 on the x, y, z that we then dragged from the Hierarchy down into our Prefabs folder in the Project window. We then made an Enemy script that follows. But before we get into the code, we need to talk a little about physics…not like Quantum Electrodynamics (But I do love to talk about QED) — we need to talk like Classical Physics, things bumping into each other or AKA collision detection. We want to be able to detect when our Laser hits an Enemy or when the Enemy crashes into the Player. To do this in games is expensive if you’re comparing one mesh object to another so Game Engines like Unity provide for a highly optimized solution called Colliders. We also don’t want things bouncing off of each other, we want to “trigger an event”. Below are the two components we need to adjust for our collisions.

We need to check the “Is Tigger” on the BoxCollider and we need to turn off “Use Gravity” since we’re handling the speed and movemnt of the Enemy with our Enemy.cs script. You can see the Enemy's Movement Speed in the editor just above the Rigidbody component.

With the “Is Trigger” checked, our collisions will be “events” that are raised for us to listen to. Below is the full code for our Enemy behavior. There’s a method that will re-position enemies that get past the player and then returns it to a random X position at the top of the game window. The code we’re wanting to focus on is the OnTriggerEnter(collider other) code.

The code staring on line 34 is where the interesting things are happening. Within this method we detect if the other gameObject is the Player or a Laser we placed a “tag” (Read the docs) to easily see which GameObject was detected in the collision. If it was the Player or a Laser then we want to do something about it.

If the Player was struck then we need to make the Player loose a life and we need to destroy the Enemy. Before we can do anything to the Player we need to get a reference to it. We do this by using the GetComponent<Player>() method to get the Player component attached to the other GameObject in this case the Player. If we’ve got a player reference that isn’t null then we can safely call the RemoveLife() method that reduces the player’s lives left by one and if there are no more lives left then the Player GameObject is Destroy'ed.
[Pro Tip: Always, always, always “Null check” your GameObject references.]

If the Laser is the other object that collided with the Enemy then we destroy both, the laser first and then this.gameObject which is the instance of the Enemy Prefab that got hit. Later, we’ll add a scoring system to the game.

The next, and frankly the coolest, item we need to work on is how to get Enemy's into the game — SpawnManager

The `SpawnManager`

How things are Instantiated in my Space Shooter is managed by the SpawnManger. Right now only the Enemy objects are being spawned into the game, but in my next articles we’ll be adding power-ups for the player to collect and the cool thing is this single GameObject will manage it all! Below is the complete code for the Enemy portion of the game as it stands now.

Lines 7 — 17 we setup the prefabs that we’ll want to be able to spawn into the game and to communicate with. We’ve got an EnemyContainer that we’ll use to hold all of the spawned Enemy instances in to keep the Game Hierarchy tidy. And in the Start() method we “Null Check” these references so we don’t have a game crashing bug. And then we start our SpawnEnemies() Coroutine.

In the IEnumerator SpawnEnemies() we enter an infinite loop, which for most programs are terrible things but in games they are used a lot, in a wise way. If an infinite loop isn’t in check then it will crash your program eventually, but since we give the computer some time to breath with the yield on line 46 we’re good. We also have a way to get out of this loop, when _spawningAllowed is false then we exit the loop. On line 50 we have the means for when the _spawningAllowed is changed.

Lines 38 — 46 are the sauce that spawns Enemy GameObjects into the game at random X positions and we wait to run the loop again between a range of time, the _spawnRateMin and _spawnRateMax times to wait. This way we get a surpise on when a new Enemy will appear for us to blow out of the galaxy…

Next Steps…

This ends our little prototype game, in the upcoming articles I’ll be moving from these placeholder GameObject primitive shapes to full on sprites. I could continue to flesh out the game mechanics with the prototype but for this small of a game and the complete lack of my artistic abilities, we’ll move along to get some fancy graphics in place and continue with the simple game mechanics of adding power-ups, a game menu, keeping track of the game score and player lives. I’ll probably add some additional things like keeping track of how many laser shots the player fired and how many enemies were hit to give the player an accuracy stat that may add into the final score as some kind of multiplier.

--

--

Joseph Youngquist
Joseph Youngquist

Written by Joseph Youngquist

Veteran to Digital Media publishing, Software Engineering and Architecture starting on a pivot to Unity Development

No responses yet