2D Space Shooter 5: Points!
Continued from part 4.
Today we’re going to wrap up our prototype by adding perhaps the most exciting aspect of any shooter: the score.
Before we can earn points for destroying enemies, we need some way of destroying them in the first place! We’re able to fire lasers, but right now they just pass through the enemies. Here’s where our “Enemy” tag will come in handy. Open the Laser script and add the following:
OnTriggerEnter is a built-in Unity function that gets called every time a trigger collider touches another collider (that is not a trigger). As you can see, it takes the other Collider (which is generally named “other”, but that is not a requirement) as a parameter. Here, we get the transform of the object our other collider is attached to and compare its tag to the one we created earlier. We could just as easily look for a tag named “Player” or “Banana”, but our Lasers are only interested in Enemies. If the tag of the other GameObject is a match, we destroy it.
If you switch back to Unity and run the game, you’ll see that nothing happens when the lasers hit an enemy. This is because we’re missing two requirements of OnTriggerEnter. Our Laser has a collider, but it isn’t a trigger. Let’s fix that.
The other requirement of OnTriggerEnter is that at least one of the objects involved in the collision have a RigidBody component. It doesn’t matter which one, so we’ll add it to our laser. Back in Unity, we’ll see the following:
This is actually pretty funny, but not quite what we’re looking for. The issue here is that by default, RigidBody components are affected by gravity. Since our game takes place in outer space and we’re handling movement programmatically, we really don’t want this. With your Laser prefab selected, go to the RigidBody component and uncheck the box for “Use Gravity”.
Now it’s working pretty much the way we want, but our lasers keep going after we destroy the enemy. We can fix this with one line of code:
In any script, “gameObject” (note the capitalization) always refers to the GameObject the script is attached to. This is often used to “self-destruct” an object, as well as to reference other properties or components of the same object.
Now that we can destroy enemies, we need a way to keep track of how many we’ve blasted. In the hierarchy, add a new Empty GameObject and call it “GameManager”. Give it a new C# script of the same name, and add the following code:
Take special note of line 4. In order to reference the Text class, we need to include the UnityEngine.UI namespace. Without this line, we’d get an error on line 8 that “The type or namespace name ‘Text’ could not be found”.
Now that the logic exists to keep score, all we need to do is add a Text component to the game and point our GameManager script to it. In the hierarchy pane, right click and add UI > Text. This will actually add three items to our hierarchy: a Canvas, an EventSystem, and our Text element (which will be a child of the Canvas). Select the Text element, and rename it “ScoreText”. You’ll notice that instead of a standard Transform, it has something called a Rect Transform. This is standard for UI elements, but it’s a bit more complicated than what we’ve seen so far.
We’re going to make our score appear in the top left of the screen. Currently, its anchor is set to middle center. Click the anchor and set it to top left, then set the Pivot to (0, 1), and the Pos X and Pos Y to (0, 0).
Lastly, go down to the Text component and set its Font Size to 50, Horizontal Overflow and Vertical Overflow to “Overflow”, and Color to white.
The final step is to call the GameManager’s UpdateScore function whenever an Enemy is destroyed. But how does the GameManager know about each Enemy? It doesn’t need to! As there is only one GameManager, it’s much simpler for the Enemy script to know about it and, since the UpdateScore method is public, it can be accessed from inside other scripts.
Jump back to the Enemy script and add the following:
Adding a private instance of GameManager allows us to find the component in the scene immediately when the Enemy spawns (the Start method) and cache its value so we can use it later (in this case when the Enemy is destroyed). OnDestroy is called automatically when the attached gameObject is destroyed, no matter what other script might trigger the event. Your final Enemy script will look like this:
Run the scene and…
And there we have it. Our prototype is complete! Give yourself a round of applause. You’ve earned it.
This is where our tutorial ends, but do check back. I’ll be posting updates with ideas of what we can add to make it into a more polished product.