This 2D space shooter tutorial features spaceships, spinning sprites, steadily increasing difficulty, some simple lighting, retro sound effects, and a cool backing track. The whole thing should take no longer than a couple of hours to work through. Be sure to play the game (below) so you will be able to understand what we are going to build.
The game is only properly playable in desktop Web browsers because it uses a keyboard and mouse to control. You can load it up and just see the game running in most mobile browsers as well.
About the game
The game takes place in 2096 in a region of the Milkyway ruled by the Humanoid and Martian peoples of Earth. Having just fought, and won, a long and bitter war with the evil Unrealious Enginios, the current issue is the vast amount of space debris. Fly around destroying pilotless spaceships (and the junk they drop when shot) and be rewarded with a magnificent high score for all to see. GOOD LUCK!
Use the WASD keys for rotate, thrust and reverse thrust. Use the left mouse button to shoot.
Text formatting conventions
I hope to make this tutorial as easy as possible, so a complete beginner can follow along. To help I have a few text formatting conventions. If the text is something you will see on screen then it is formatted in this orange color. For example, the text on a button to click or the menu option to select will likely appear in this color.
If there is a mention of the code within the text, it will be formatted like this. Perhaps if we are discussing a single line or part of a single line of code. If the text represents a file or folder name it will also be formatted the same as code within the text.
Blocks of code should easily stand out from the text as I am using a plugin to highlight them, like this:
1 2 3 4 5 |
if(showingBlockOfCode){ // Format like this } |
Note that sometimes even sticking to a convention can cause a little ambiguity. For example, what about when I am referring to a file name that is on the screen? Which formatting should I use? In such cases, I use the formatting type that I think makes the situation as clear as possible.
Downloading the assets
Although we won’t use all the assets straight away, having them all to hand from the start is useful. We can import and prepare them all and when we write code that depends on them we can focus on that code and not get distracted.
The graphics
The game has 5 images. The player spaceship, wreckage spaceship, wreckage from a wrecked ship, player’s lasers, and the pretty space background. If this doesn’t make sense then play the game. Here they are.
You can download the first four by right-clicking on the image and select Save image as…. The background can be downloaded by following the link in the thumbnail above. It will make the project easy to follow, however, if you name them the same as mine. From left to right, the names are, player-ship, enemy-ship, enemy-ship-part1, lazers and Full Moon - background.
Or, you can download the sprite sheet from the link below and choose different image frames to those that I used. As I just mentioned, keep the names the same.
1 2 3 |
Copyright/Attribution Notice: <a href="http://opengameart.org/content/space-ship-building-bits-volume-1">Cool spaceship graphics</a> Stephen Challener (Redshrike), <a href="http://opengameart.org/content/space-ship-building-bits-volume-1">hosted by OpenGameArt.org</a> |
Of course, you can make your own graphics if you prefer.
The sound
All the sound effects I made myself and you are free to download and use them. They are linked below, just right-click and select, Save link as… to save them. If you want to keep things simple, don’t change the file names.
A sound to play when the wreckage is destroyed.
A sound to play when the player shoots.
A sound to play when the player loses a life
The nice space-battle tune can be downloaded here. As the tune is in use in the playable 2D space shooter, a full attribution to the creator is as follows. The file you need to download is named Gran Battala.mp3.
1 2 3 4 5 |
Copyright/Attribution Notice: Cool Backing track: Author: matthew.pablo URL:http://opengameart.org/content/heroic-demise-updated-version License(s):CC-BY 3.0 ( http://creativecommons.org/licenses/by/3.0/legalcode ) |
The font
To write the score, high score etc, we need a font. The font I chose is called Prisma.
I chose it because it was a bit retro and a bit psychedelic. You can choose any font you like or download the Prisma font here.
1 2 3 |
Copyright/Attribution Notice: Prisma font http://www.steffmann.de/ |
Creating a project and Importing the assets into Unity
Run Unity and create a new project by clicking on New. Call it 2d Shooter and also make sure you select the 2D option.
Organizing the assets
Next, locate the Project tab and create some new folders to organize the assets we are about to import. Right-click in the Project tab and select Create > Folder. Name the new folder Sound. Now repeat the step and create each of the following additional folders that we will need during the project:
- Font
- Lighting
- Material
- Physics Material
- Prefabs
- Scripts
- Graphics
Here is what your Project tab should look like when you have done this.
Adding the assets to the folders
We won’t use all of the folders straight away but at least they are ready for when we need them. To add assets in Unity you can right-click in the Project tab and select Import New Asset…. If you right click on the appropriate folder in the Project tab and then select Import New Asset…, then the asset will be added directly into that folder, avoiding the need for you to do any further organization.
With this in mind import the following assets into the following folders:
- Import player-ship.png, enemy-ship.png, enemy-ship-part1.png, lazers and Full Moon - background.png into the Graphics folder
- Import wreckage-destroy.wav, shoot.wav, lose-life.wav and Gran Battala.mp3 into the Sound folder
- Import Prisma.ttf into the Font folder
Here is what your Project tab should look like now.
Adding the static objects (text and background)
In this section, we will actually start to see our 2D space shooter take shape. We will add the space background and set it to a new layer to keep it in the background. We will also add a Unity Canvas object and fill it with all the text that is required for the player’s HUD.
Adding a background to the game
To add a background to the game, simply drag Full Moon – background from the Project tab, into the Hierarchy tab. The Scene tab should now look like this.
Reordering the background, to the back
To make sure our background remains in the background, in the Inspector tab, by the Sorting Layer property, choose Add Sorting Layer…. Click the + icon and type Background. Now, at the very top of the Inspector tab, click the Layers drop-down and choose Edit layers:
Drag the Background layer so it is above the Default layer as shown in the next image:
Finally, for this part, select the Full Moon – background object in the Hierarchy tab, and then in the Inspector tab change its Sorting Layer property to Background. Now, any other objects we create will always be on top of the background.
Adding a Unity GUI
Adding GUI text for the player HUD (Heads Up Display) is a little bit more in-depth but it is not complicated at all. There are some great GUI tutorial videos on the Unity site that go into much more depth than I do here if you want to understand all the details. These next steps will just get our HUD done, ASAP.
Hover the mouse pointer in the Hierarchy tab, right-click and select UI > Canvas. Select the new Canvas object in the Hierarchy tab and right-click it. Select UI > Text. and you will have created Text object called Text as a child of the Canvas object. Rename text to playerscore.
Right-click the Canvas object again and select UI > Text. You will now have another Text object as a child of the Canvas object. Rename the new Text object to ships.
Right-click the Canvas object yet again and select UI > Text. You will now have yet again created a Text object as a child of the Canvas. Rename the new Text object to level.
For the last time, I promise, right-click the Canvas object and select UI > Text. Rename the new Text object to highscore. Check your Hierarchy tab looks like this:
Configuring the Canvas object
We need to change a few properties of Canvas. Select the Canvas object in the Hierarchy tab, then In the Inspector tab, configure the following 3 things.
- Render Mode needs to be set to Screen Space – Camera
- Drag and drop Main Camera from the Hierarchy tab over to the Render Camera property in the Inspector tab.
- Set the UI Scale Mode property to Scale With Screen Size
Configuring the Text objects
Now select playerscore in the Hierarchy tab. We need to give it an anchor point so Unity can run the game on different screen resolutions without messing up the position of the text. We will anchor playerscore to the top left. Here is how. In the Rect Transform component, click on the icon highlighted:.
You can now select the top-left position as shown:
The last thing we need to do with the playerscore object is set the Font Size to 20, the Text property to playerscore and the Color to white. You might wonder why we didn’t configure the Text property to something more practical like SCORE: 0? The reason for this is that this will be taken care of in the code. At this stage, we are simply going to add a Text property which helps us easily distinguish between the different Text objects we have created.
Now, configure ships by setting the anchor point to bottom-right, Font Size to 20, the Text property to ships and Color to white.
Now, for level set the anchor point to top-right, the Font Size to 20, the Text property to level and the Color to white.
Configure highscore by setting the anchor point to top-left, the Font Size to 20, the Text property to highscore and the Color to white.
Now, revisit each Text object individually and drag the Prisma font from the Font folder in the Project tab onto the Font property of each of the Text objects. We can now move the Text objects into their appropriate positions.
You can enter Unity move mode by pressing the W key. Move all the Text objects into position. This is what my Game tab looks like at this stage.
That is the HUD all done. When we have configured and scripted the other game objects we will add references to these Text objects so our 2D space shooter can update them as the game progresses.
Adding the spaceships, debris, and bullet
We will do this in a few stages. We don’t just need the objects themselves, we need to specify the physical behaviour in regard to gravity and other game objects as well as how they react to light. Furthermore, we need to build these objects in such a way that they are reusable. This will mean we can spawn and destroy as many instances as we like, throughout the course of a game. Let’ get started.
Creating a New Physics 2D Material
We want our enemy ships and their debris to float around in space and bounce off of each other. Bouncing metal might not be entirely real but it is quite fun. What we will do is create a new Physics 2D Material with the properties that we want these objects to have. Then when we build these objects we can add this material to them. Let’s call our Physics 2D Material, BouncyMetal.
Right-click on the Physics Material folder in the Project tab. Select Create > Physics 2D Material. Name it Bouncy Metal. Configuring Bouncy Metal is simple. Make sure it is selected and in the Inspector tab configure the following properties:
- Bounciness to 1
- Friction to .4
When the game is running you can lay around with these settings.
Creating a new Material
Unity Material is different from Physics 2D Material. We will create a new Material that defines how our game objects interact with lights in the scene.
Right-click the Material folder in the Project tab. Select Create > Material. Name the new Material, Sprite Light. We can configure Sprite Light in a few of clicks. Make sure it is selected in the Project. In the Inspector tab select the Shader dropdown and choose Sprites > Diffuse. That’s it. Any sprite that we add this Material to will react to light.
Now to put Bouncy Metal and Sprite Light to good use.
Creating the actual game objects
One at a time, drag enemy-ship, enemy-ship-part1, lazers and player-ship, from the Project tab, into the Hierarchy tab. The items in the Sprites folder of the Project tab are mere sprites. By dragging them into the Hierarchy tab, Unity has made new game objects from them. It is these new game objects (in the Hierarchy tab) that we will configure.
We need to add and configure some Components to each of these game objects. You can add components by first selecting the appropriate game object (in the Hierarchy tab) and then clicking the Add Component button in the Inspector tab. Most of the components are really straightforward but one, the Polygon Collider 2D, is a bit more fiddly. I have provided a link to a video in case this causes you bother.
The player-ship object
Let’s start by configuring player-ship. Make sure it is selected in the Hierarchy tab. In the Inspector tab, notice there are some components already. Identify the Sprite Renderer component, in particular, the Material field. Here is a picture to help.
Once you have located the Material field drag Sprite Light from the Material folder of the Project tab and drop it on the Material field of the Sprite Renderer component in the Inspector tab. You have now made our ship respond to the Unity lighting system. We will add some lights soon.
If we want our game objects to respond to physics forces (like moving and bumping) then they need a Rigidbody 2D component. Make sure player-ship is selected in the Hierarchy tab and then click the Add Component button in the Inspector tab. Choose Physics 2D > Rigidbody 2D.
Let’s configure the new Rigidbody 2D component by changing the Gravity Scale field to 0. In the real world things fall(Duh!). We want our ship to float and thrust around, as if in space. By setting Gravity Scale to zero we prevent our spaceship from falling off the bottom of the screen – never to be seen again.
We have one more component to add to player-ship. Click the Add Component button and select Physics 2D > Polygon Collider 2D. This component defines the area around the game object where Unity will detect collisions. Let’s take a closer look.
Press W to put Unity into move mode. Use the arrows in the Scene tab to move player-ship into a clear space so we can examine it. You might need to zoom in a little. You can do so by holding the Ctrl key and zooming with the mouse wheel. This next image shows player-ship in the Scene tab. Notice the green Polygon Collider 2D wrapped around it.
If we play the game with this then we will get inaccurate collisions and the game will feel unsatisfying and unfair. We need to make the Polygon Collider 2D neatly wrap around the spaceship object. Perhaps a bit more like this next image.
To achieve this you need to click the Edit Collider button on the Polygon Collider 2D component in the Hierarchy tab. You can then drag, mold, and shape the collider to fit the object as shown above. Some people find the Unity interface for doing this extremely intuitive and can easily achieve this without further guidance. Others find it awkward and a bit strange. If you struggle to shape the collider, take a look at this video at around the 4 minutes 25 seconds point.
When you have shaped your collider you are ready to read on. Note that it doesn’t have to be perfect, you could even leave it completely as the game will still work, just with less accurate collision detection.
One last thing for the collider. Check the Is Trigger checkbox. This tells the game engine to send us a message to let us decide what to do when a collision occurs. This means it will not automatically respond to collision events. This is just what we need for the player’s spaceship. The enemy spaceship and the debris, on the other hand, will bounce around the screen entirely controlled by the physics system.
Now we need to add a tag. In Unity, tags are used to be able to identify different types of game objects in our scripts. For example, if two objects collide, the action we need to take will be very different depending on what type of object they are. Tags solve this problem. At the start of a project, you might not always be able to predict if you will need a tag for a particular object. It doesn’t hurt to add one to all likely candidates.
Here is how to tag the player-ship object.
- Select Add Tag in the Inspector tab
- Click the + icon
- Type Player in the New Tag field
- The player-ship object is no longer selected at this point so, select the player-ship object in the Hierarchy tab
- Click the Untagged button at the top of the Inspector window
- Click Player
The player-ship object now has a tag called Player. When we write some scripts we will see how this is useful to us.
For the rest of the game objects, I will go into less detail because we are doing the same steps as we did for player-ship. Just make sure you have the appropriate object selected and read the details as there are a few subtle differences compared to player-ship.
The enemy-ship object
- Add the Sprite Light Material to the Material field of the Sprite Renderer component
- Add a new Rigidbody 2D component and set Gravity Scale to 0
- Add a new Polygon Collider 2D component and edit it to wrap the ship more accurately.
- Drag Bouncy Metal from the Physics Material folder in the Project tab to the Material field of the Polygon Collider 2D component.
- Add a new tag called Enemy
The enemy-ship-part1 object
- Add the Sprite Light Material to the Material field of the Sprite Renderer component
- Add a new Rigidbody 2D component and set Gravity Scale to 0
- Add a new Polygon Collider 2D component and edit it to wrap the ship more accurately
- Drag Bouncy Metal from the Physics Material folder in the Project tab to the Material field of the Polygon Collider 2D component
- Add a new tag called Enemy Debris
The lazers object
- Add a new Rigidbody 2D component and set Gravity Scale to 0
- Add a new Box Collider 2D component. This collider might need slight editing in a similar manner as the Polygon Collider 2D.
- Add a new tag called Lazer
Make prefabs of the lazers, enemy-ship, and enemy-ship-part1
All of our objects are configured and we will soon write some C# code to turn them into a working game. The problem is that we need more that one laser, enemy-ship, and enemy-ship-part1. To solve this problem we need to change them from actual game objects that are ready, in the scene, into prefabs of game objects that can be destroyed and spawned at will. This is really easy.
One at a time, drag, lazers, enemy-ship and enemy-ship-part1 from the Hierarchy tab, into the Prefabs folder of the Project tab. That’s it! Check that you do actually have three prefabs in the Prefabs folder.
If your folder looks the same as the previous image then you can delete lazer, enemy-ship and enemy-ship-part1 from the Hierarchy tab as they are no longer required.
Lighting the game
The game already has default lighting. What we will do is add another light shining on the moon in the top right-hand corner of the background image. This will give the approximate effect of a moon glowing from the light of the Sun. When a game object is near it then it will be sharply lit and as they move away, less so. There will also be areas of complete dark in the top-right and bottom-left where game objects will not be visible at all. This tutorial is already quite long and sprawling so I won’t go into any details about lights. What I encourage you to do is experiment with different types of lights and their various parameters.
Here is how to quickly achieve the effect I have just described. Right-click in the Hierarchy tab and select Light > Spotlight. Name the new light Sun and then configure the following parameters in the Inspector tab.
Position X 4, Y 4, Z -3
Rotation X 56, Y 51, Z 0
Scale X 1, Y 1, Z 1
Type Spot
Baking Realtime
Range 30
Spot Angle 110
Color Yellow
Intensity 3
Bounce Intensity .75
Adding a game controller object
It is almost time to wire-up all our game objects with C# and start spawning our prefabs. We need one more game object. We will use this game object to control all the other game objects and some other handy uses as well. Right-click in the Hierarchy tab and select Create Empty…. Call this new game object Game Controller.
Let’s waste no time making our game controller useful. Make sure it is selected in the Hierarchy tab and then click the Add Component button in the Inspector tab. Choose Audio > Audio Source. Now, drag and drop Gran Batalla from the Sounds folder in the Project tab to the Audio Clip field of the new Audio Source component in the Inspector tab.
Finally, for this part, add a new tag called Game Controller.
If you run the game now you will see a glowing moon, a solitary spaceship and hear a moody battle tune. It is time to write some C# scripts.
Writing the scripts
If you have a beginner-level understanding of C# and object-oriented programming you are good to go!
I am not going to teach C# in this tutorial but I will give an overview of what each of the scripts does. If you have just a basic understanding of similar languages like C++ or Java perhaps then you should easily follow along. Some C# features might need you to do a quick Web search for an explanation if you want to understand some of the finer points, however.
If you are completely new to programming then just keep going. You will see that C# (like other languages) uses English-like keywords and simple structures to get things done and make decisions. I would, however, recommend the following C# tutorials.
The longest script will be called GameController and unsurprisingly we will attach it to the GameController object. It will control spawning enemies at the start of the game and at each level. It will also control setting up the HUD.
Each game object will also have a script attached. The lazers object will have a script that simply propels it forward in the direction it is facing. Each instance of lazer will also destroy itself one second after instantiation. This is just about right for this game.
The enemy-ship object will be in charge of spawning three enemy-ship-part1 objects when destroyed. Actually, we will see how we can use the same script for enemy-ship and enemy-ship-part1.
The player-ship object will need to handle user input for rotating, thrusting/reverse thrusting. If this is your first Unity game you will probably be surprised how easily we can achieve this.
All the game objects will need a script so that when they reach the far left, right, top, or bottom of the screen they reappear on the other side. We will call this script Teleport and then attach it to all the game objects.
The easiest way to see how all this works is to go ahead and write the scripts.
Coding the Teleport script
Right-click the Scripts folder in the Project tab and select Create > C# Script. Name the script Teleport. Double-click on it to open it in the MonoDevelop code editor which is part of Unity. Edit the script to be the same as this next code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using UnityEngine; using System.Collections; public class Teleport : MonoBehaviour { // Update is called once per frame void Update () { // Teleport the game object if(transform.position.x > 8){ transform.position = new Vector3(-8, transform.position.y, 0); } else if(transform.position.x < -8){ transform.position = new Vector3(8, transform.position.y, 0); } else if(transform.position.y > 5){ transform.position = new Vector3(transform.position.x, -5, 0); } else if(transform.position.y < -5){ transform.position = new Vector3(transform.position.x, 5, 0); } } } |
Explaining the Teleport script
The Teleport script has a single method called Update. This method is not in entirely our own but an override of the Update method provided by Unity. This means we never actually call this method ourselves. Unity, however, will call it dozens of times per second. The four if/ else if conditions check whether the horizontal position of the game object goes beyond -8 or 8 or the vertical position goes beyond -5 or 5. When they do the coordinates are changed (teleported) to the other extreme.
The units are Unity units. We don’t deal in pixels because they will be different across various devices and platforms. By using Unity units we can be sure the code will be consistent on a small Android phone up to a 50-inch plasma TV.
As we want all our game objects to behave like this we will add it to all our prefabs and player-ship as well. You could select each game object individually, add a component and select the script via the Inspector tab. The quick way, however, is to simply drag the Teleport script from the Scripts folder and drop it on each of the three prefabs (lazer, enemy-ship, enemy-ship-part1 and also player-ship in the Hierarchy tab). Drag and drop the Teleport script and then double-check that the process has worked by checking one of the game objects. Below you can see the script on my player-ship object.
Let’s code the main GameController script.
Coding the GameController script
Right-click the Scripts folder in the Project tab and select Create > C# Script. Name the script GameController. Double-click on it to open it in MonoDevelop. Edit the script to be the same as this next code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
using UnityEngine; using System.Collections; using UnityEngine.UI; public class GameController : MonoBehaviour { public GameObject enemy; public Text scoreText; public Text shipsText; public Text levelText; public Text hiscoreText; private int levelNumber; private int score; private int hiscore; // Get hard in a hurry private const int partsMultiple = 18; private int partsToDestroy; private int ships; // Load the hi-score and start a new game void Start () { hiscore = PlayerPrefs.GetInt ("hiscore", 0); PrepareNewGame (); } // Update is called once per frame void Update () { // Quit if (Input.GetKey("escape")) Application.Quit(); } // This method sets up a new game void PrepareNewGame(){ // Reset the score and level etc score = 0; ships = 3; levelNumber = 1; // Prepare the HUD scoreText.text = "SCORE:" + score; hiscoreText.text = "HISCORE: " + hiscore; shipsText.text = "SHIPS: " + ships; levelText.text = "LEVEL: " + levelNumber; SpawnEnemies(); } void SpawnEnemies(){ ClearAllEnemies(); // Spawn required enemies partsToDestroy = (levelNumber * partsMultiple); for (int i = 0; i < partsToDestroy; i++) { // Spawn an asteroid Instantiate(enemy, new Vector3(Random.Range(-9.0f, 9.0f),Random.Range(-6.0f, 6.0f), 0), Quaternion.Euler(0,0,Random.Range(-0.0f, 359.0f))); } levelText.text = "WAVE: " + levelNumber; } public void ScorePlusPlus(){ score++; scoreText.text = "SCORE:" + score; if (score > hiscore) { hiscore = score; hiscoreText.text = "HISCORE: " + hiscore; // Save the new hiscore PlayerPrefs.SetInt ("hiscore", hiscore); } // Has player destroyed all enemies? if (partsToDestroy < 1) { // Start next wave levelNumber++; SpawnEnemies(); } } public void LifeLost(){ ships--; shipsText.text = "SHIPS: " + ships; // Has player run out of lives? if (ships < 1) { // Restart the game PrepareNewGame(); } } public void DebrisDestroyed(){ partsToDestroy--; } public void EnemyDestroyed(){ // Destroyed an enemy and spawned three debris partsToDestroy+=2; } void ClearAllEnemies(){ // Make an array of all enemies and destroy each in turn GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy"); foreach (GameObject current in enemies) { GameObject.Destroy (current); } // Make an array of all enemy debris and destroy each in turn GameObject[] enemyDebris = GameObject.FindGameObjectsWithTag("Enemy Debris"); foreach (GameObject current in enemyDebris) { GameObject.Destroy (current); } } } |
Explaining the GameController script
This script sets up a Text object to hold a reference to each of the Text objects in the HUD. Notice they are public. We will associate these objects with the appropriate HUD objects in a moment. The script also has a selection of int variables for holding the score, hi-score, ships left, etc.
The first method is Start. This method is called by Unity when the Game Controller object enters the game – which is as soon as the game is run. The Start method loads the hi-score and then calls the PrepareNewGame method.
The PrepareNewGame method resets the various int variables (score etc.) to their appropriate values and then resets the HUD to values appropriate for a new game. The last thing the PrepareNewGame method does is call SpawnEnemies.
The SpawnEnemies method starts by clearing any existing enemies that might be left over from a previous game. It does so by calling the ClearAllEnemies method. The SpawnEnemies method then determines how many enemies are required based on the current level the player has reached. Then it uses a loop to spawn the new enemies using the Instantiate method. The new enemies are spawned at random locations and random angles of rotation.
Now we understand what is happening, add the script to the Game Controller object in the Hierarchy tab. Make sure that Game Controller is selected in the Hierarchy tab and drag the following references to the Inspector tab.
- Drag the enemy-ship prefab to the Enemy field in the Inspector tab.
- Drag playerscore object from the Hierarchy tab to the Score Text field
- Drag ships from the Hierarchy tab to the Ships Text field
- Drag level from the Hierarchy tab to the Level Text field
- Drag hiscore from the Hierarchy tab to the Hiscore Text field
The GameController, Script component for the Game Controller object in the Inspector tab should now look like this next image.
Let’s code the lazer next.
Coding the Lazer script
Right-click the Scripts folder in the Project tab and select Create > C# Script. Name the script Lazer. Double-click on it to open it in MonoDevelop. Edit the script to be the same as this next code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using UnityEngine; using System.Collections; public class Lazer : MonoBehaviour { // This runs every time a lazer spawns void Start () { // Kill me in one second Destroy (gameObject, 1.0f); // Propel the lazer in the direction set by player-ship GetComponent<Rigidbody2D>().AddForce(transform.up * 400); } } |
Explaining the Lazer script
This script is short and sweet. The Destroy method tells Unity to destroy the instance but the 1.0f parameter says to wait for one second. This is just enough time for the lazer to whiz across the screen and do some damage.
The AddForce method propels the laser in the direction it is facing. As all this takes place in the Start method, it is executed immediately after a lazer is spawned. We will see this happen in the Ship script we will write soon.
Drag and drop the Lazer script onto the lazers prefab.
Coding the Enemy script
Right-click the Scripts folder in the Project tab and select Create > C# Script. Name the script Enemy. Double-click on it to open it in MonoDevelop. Edit the script to be the same as this next code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
using UnityEngine; using System.Collections; public class Enemy : MonoBehaviour { public AudioClip destroy; public GameObject enemyDebris; private GameController gameController; // Use this for initialization void Start () { // Get a reference to the game controller object and the script GameObject gameControllerObject = GameObject.FindWithTag ("Game Controller"); gameController = gameControllerObject.GetComponent<GameController>(); // Push the enemy or debris in the direction it is facing GetComponent<Rigidbody2D>().AddForce(transform.up * Random.Range(-50.0f, 150.0f)); // Give it a random spin GetComponent<Rigidbody2D>().angularVelocity = Random.Range(-0.0f, 90.0f); } // Called when enemy collides with anything at all void OnCollisionEnter2D(Collision2D c){ // Is it a lazer? if (c.gameObject.tag.Equals("Lazer")) { // Destroy the lazer Destroy (c.gameObject); // If enemy spawn three debris if (tag.Equals ("Enemy")) { // Spawn three debris Instantiate (enemyDebris, new Vector3 (transform.position.x - .5f, transform.position.y - .5f, 0), Quaternion.Euler (0, 0, 90)); Instantiate (enemyDebris, new Vector3 (transform.position.x + .5f, transform.position.y + .0f, 0), Quaternion.Euler (0, 0, 0)); Instantiate (enemyDebris, new Vector3 (transform.position.x + .5f, transform.position.y - .5f, 0), Quaternion.Euler (0, 0, 270)); gameController.EnemyDestroyed (); } else { // Debris destroyed gameController.DebrisDestroyed(); } // Play a sound AudioSource.PlayClipAtPoint (destroy, Camera.main.transform.position); // Add to the score gameController.ScorePlusPlus(); // Destroy the current asteroid Destroy (gameObject); } } } |
Explaining the Enemy script
First, notice the public objects this script has. One for spawning enemy debris, one for a sound effect and another for the Game Controller object. As the Enemy script will have a reference to the Game Controller object, it will be able to call its public methods.
The Start method initializes that reference using the tag that we assigned to the Game Controller object. Then in the Start method a random velocity and heading and spin is added. The enemy is now randomly heading through space.
The OnCollisionEnter2D method is called by Unity when anything touches it. The first course of action is to see if that thing is a lazer. Again, this is achieved using the tag assigned to all lazers.
Next, the script checked if the current enemy is an actual enemy or just debris. This yet again is achieved by checking the tag. If it is a full-size enemy then three debris are spawned and we use the reference to the Game Controller to add two to the current tally of enemy objects. Otherwise (if the current object is debris) the object is simply destroyed.
Regardless of whether the object is an enemy or just debris, the Game Controller reference is used to add to the score. A sound effect is played and the current object is destroyed.
Add the Enemy script to both the enemy-ship prefab and the enemy-ship-part1 prefab.
Let’s add all the relevant references for this script. As this Script is assigned to two game objects you need to assign the references for the script to both as well.
For each of the enemy-ship and enemy-ship-part1 prefabs, drag the following references to the appropriate fields in the Inspector tab.
- From the Sound folder in the Project, tab drag wreckage-destroy to the Destroy field
- From the Prefabs folder drag the enemy-ship-part1 to the Enemy Debris field
Time to code the final script. We will soon be able to play the game.
Coding the Player script
Right-click the Scripts folder in the Project tab and select Create > C# Script. Name the script Player. Double-click on it to open it in MonoDevelop. Edit the script to be the same as this next code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
using UnityEngine; using System.Collections; public class Player : MonoBehaviour { float rotationSpeed = 200.0f; float thrustForce = 4f; public AudioClip death; public AudioClip shoot; public GameObject lazer; private GameController gameController; void Start(){ // Get a reference to the game controller object GameObject gameControllerObject = GameObject.FindWithTag ("Game Controller"); gameController = gameControllerObject.GetComponent <GameController>(); } void FixedUpdate () { // Rotate transform.Rotate(0, 0, -Input.GetAxis("Horizontal")* rotationSpeed * Time.deltaTime); // Thrust GetComponent<Rigidbody2D>().AddForce(transform.up * thrustForce * Input.GetAxis("Vertical")); // Shoot lazer? if (Input.GetMouseButtonDown (0)) FireLazer (); } void OnTriggerEnter2D(Collider2D c){ // Anything except a lazer is an enemy if (c.gameObject.tag != "Lazer") { AudioSource.PlayClipAtPoint (death, Camera.main.transform.position); // Move the ship to the start transform.position = new Vector3 (0, 0, 0); // Remove all thrust GetComponent<Rigidbody2D> ().velocity = new Vector3 (0, 0, 0); gameController.LifeLost (); } } void FireLazer(){ // Spawn a lazer Instantiate(lazer, new Vector3(transform.position.x,transform.position.y, 0), transform.rotation); // Play a shoot sound AudioSource.PlayClipAtPoint (shoot, Camera.main.transform.position); } } |
Explaining the Player script
In the Start method, just as we did in the Enemy script we get a reference to the Game Controller by using its tag.
In the FixedUpdate method, there are three lines of code. The first reads the current horizontal axis of movement which is based on the state of the A and D keyboard keys. The second line gets the state of the vertical axis of movement based on the W and S keys. Each of the lines also translates these axes into an incremental amount of rotation or force and rotates/moves the spaceship.
The final line spawns a lazer if the left mouse button is clicked.
The FixedUpdate method is similar to the Update method as it is called by Unity many times a second. FixedUpdate, however, is where you should put any code that affects the physics system.
The OnTriggerEnter2D method is called whenever the ship collides with something. We use this method because earlier in the project we checked the Is Trigger field for player-ship. Inside this method, we check to see if the object collided with is anything other than a lazer. Anything other than a lazer means it is an enemy or some debris. If the test is true, the spaceship’s position is set to the middle of the screen, a sound effect is played and (using the Game Controller reference) the number of ships remaining is reduced by one.
The FireLazer method instantiates a lazer object and plays a sound effect.
Drag the Player Script to the player-ship object in the Hierarchy tab. Make sure that player-ship is selected and drag the following to the appropriate field in the Inspector tab.
- Drag lose-life from the Sound folder to the Death field
- Drag shoot from the Sound folder to the Shoot field
- Drag lazers from the Prefabs folder to the Lazer field
We are done!
Running the game
You can run the game in Unity by clicking the “play” icon in the center-top of the Unity interface or export to your favorite platform by selecting File > Build & Run….
Here is my game running in Unity or you can play the WebGL version from the top of this page.
Please feel free to comment or ask questions.
As a beginner in Unity, this was just what I needed to find out how to fire a projectile for a game that I’m working on.
Your example is a fairly decent variant on the old Asteroids game, too.
Many thanks