Back in November 1979, Atari launched Asteroids, their all-time best selling game. Over time, Atari sold more than 100,000 units of Asteroids video game machines. Although that number seems small compared to games available on mobile phones and tablets, back then, it was a phenomenal success.
Lyle Rains, vice president of the coin-operated games division, came up with the basic game idea: Players would control the movement of a small spaceship while shooting asteroids to clear an area of space. Ed Logg, the programmer working on Asteroids, added the idea that the asteroids would repeatedly get smaller as the player shot them. Also, to keep players alert, he also added 2 UFOs: large, slow moving ones that shot randomly; and smaller fast moving ones that would aim at the player’s ship.
Players would control their ship with 5 buttons: rotate left, rotate right, thrust, fire, and hyperspace. When a player used hyperspace, their ship would be transported to a random spot on the screen. Hopefully they wouldn’t collide with an asteroid or UFO. For every 10,000 points a player scores, they will be rewarded with an extra life.
On November 14, 1982, 15-year-old Scott Safran became the official top scorer in Asteroids with a score of 41,336,440. That high score remained unbeatable for 28 years until April 5, 2010. John McAllister beat the world record in 58 hours. Eventually, he toppled the 1982 world record with a high score of 41,338,740.
Tutorial overview
In this tutorial series, we’ll be looking at how to create an Asteroids like game. Here’s a screen shot of what the final game should look like:
The steps for this series includes:
- Project setup
- Player ship
- Creating a single screen game world
- Adding asteroids
- Adding player lives
- Adding sounds
- The main menu state
Game mechanics
Before we jump right in and start coding our game, it’s always good to figure out how our game works and the features we want to include.
Player ship:
- Always starts right at the centre of our game world
- The player ship is invulnerable for 3 seconds upon respawning after it crashes with an asteroid
- 5 lives when a game starts
- Extra life given every 10,000 points
Asteroids:
- 4 asteroids when a game starts
- 2 asteroids added to each completed round
- A maximum of 20 large asteroids is allowed to appear
- Asteroid numbers stop increasing after a score of 60,000 is reached
Scoring:
- Large asteroid: 20 points
- Medium asteroid: 50 points
- Small asteroid: 100 points
Project Files
Click here to download the project files that we’ll be using in this tutorial. You will find the following in the zip file:
index.html
, the html canvas where the game will be displayed/js/phaser.min.js
, version 2.4.0 of the Phaser framework/js/game.js
, the game source codes/assets/graphics/asteroidLarge.png
/assets/graphics/asteroidMedium.png
/assets/graphics/asteroidSmall.png
/assets/graphics/bullet.png
/assets/graphics/ship.png
Here’s a quick tutorial on how to set up a Phaser project.
Loading assets
Let’s start by loading all our game graphics. After the states
object declaration, add in the highlighted code below from lines 10-17:
6 7 8 9 10 11 12 13 14 15 16 17 |
var states = { game: "game", }; var graphicAssets = { ship:{URL:'assets/ship.png', name:'ship'}, bullet:{URL:'assets/bullet.png', name:'bullet'}, asteroidLarge:{URL:'assets/asteroidLarge.png', name:'asteroidLarge'}, asteroidMedium:{URL:'assets/asteroidMedium.png', name:'asteroidMedium'}, asteroidSmall:{URL:'assets/asteroidSmall.png', name:'asteroidSmall'}, }; |
In the code we just added, we’re declaring a new object called graphicAssets
. Within the graphicAssets
object, we declare 5 properties for each asset. Each property is an object containing 2 properties: the URL
property that points to a relative path to our graphic files and the name
property that is a unique string or key to identify which graphic asset to use.
Next we’ll need to preload these graphic assets into our game state. Add the following highlighted codes from lines 26-31:
23 24 25 26 27 28 29 30 31 32 |
gameState.prototype = { preload: function () { game.load.image(graphicAssets.asteroidLarge.name, graphicAssets.asteroidLarge.URL); game.load.image(graphicAssets.asteroidMedium.name, graphicAssets.asteroidMedium.URL); game.load.image(graphicAssets.asteroidSmall.name, graphicAssets.asteroidSmall.URL); game.load.image(graphicAssets.bullet.name, graphicAssets.bullet.URL); game.load.image(graphicAssets.ship.name, graphicAssets.ship.URL); }, |
The game.load
method is used load all external content such as Images, Sounds, Texture Atlases and data files. In the above code, we’re loading the PNG files for the asteroids, ship and bulllet.
The load.image
method requires 2 arguments with an optional third argument:
key
(string) – The unique asset name of this image file. This name is used in our code later on to identify which image to load in our game.url
(string) – The URL where the image file is located.overwrite
(boolean <optional>,false
by default) – If set totrue
, this will overwrite an asset if there is an existing key.
At line 26 for example, the compiler will look up the reference for graphicAssets.asteroidLarge.name
and graphicAssets.asteroidLarge.URL
then replace it with the string values we have assigned to the large asteroid’s name and URL at line 14. Line 26 will then be interpreted like this:
26 |
game.load.image('asteroidLarge', 'assets/asteroidLarge.png'); |
Adding the player ship
Next we’ll add the player ship. Start off by adding the following code after the graphicAssets
object:
19 20 21 22 |
var shipProperties = { startX: gameProperties.screenWidth * 0.5, startY: gameProperties.screenHeight * 0.5, }; |
A new object called shipProperties is declared and will contain all the properties for our player ship. At this time, we only have 2 properties:
startX
: the x-position of the ship whenever the ship resetsstartY
: the y-position of the ship whenever the ship resets
Next, we’ll declare a new property in the gameState
object called shipSprite
to reference our player ship:
24 25 26 |
var gameState = function (game){ this.shipSprite; }; |
After the update
function, add in the following highlighted codes from lines 47-51:
43 44 45 46 47 48 49 50 51 |
update: function () { }, initGraphics: function () { this.shipSprite = game.add.sprite(shipProperties.startX, shipProperties.startY, graphicAssets.ship.name); this.shipSprite.angle = -90; this.shipSprite.anchor.set(0.5, 0.5); }, |
Line 48 is where we add the ship sprite to our game world. Calling the game.add.sprite
function will make our sprite appear on screen at the specified x and y positions. Each time we add a new sprite, we will need to have at least 3 parameters:
x
: the x coordinate of the spritey
: the y coordinate of the spritekey
: the image used as a texture by this display object
By default, at 0 degrees rotation, a sprite always faces right. We will want our sprite to be facing upwards so we set the angle
to -90 degrees in line 49.
Also, to ensure that our sprite rotates along the correct centre point, we set it’s anchor
to 50% of its width and height.
Now, we need to call the initGraphics
function to add our ship sprite. Add the following in the create
function:
39 40 41 |
create: function () { this.initGraphics(); }, |
You should now see something like this:
You can download the source codes up to this point here.
Adding a physics body
Let’s declare a few more properties for our ship:
19 20 21 22 23 24 25 26 |
var shipProperties = { startX: gameProperties.screenWidth * 0.5, startY: gameProperties.screenHeight * 0.5, acceleration: 300, drag: 100, maxVelocity: 300, angularVelocity: 200, }; |
Here’s a brief description of each property:
acceleration
: how fast the ship will increase it’s velocitydrag
: friction that slows down the shipmaxVelocity
: the maximum movement velocity for our shipangularVelocity
: how fast our ship can rotate
Each of these properties will be applied to the ship’s physics body which we will add on next.
After the initGraphics
function, add the following:
57 58 59 60 61 62 63 |
initPhysics: function () { game.physics.startSystem(Phaser.Physics.ARCADE); game.physics.enable(this.shipSprite, Phaser.Physics.ARCADE); this.shipSprite.body.drag.set(shipProperties.drag); this.shipSprite.body.maxVelocity.set(shipProperties.maxVelocity); }, |
We’re declaring a new function called initPhysics
to initialise the arcade physics system and add physics bodies to all our game objects.
To start the physics system, we call the game.physics.startSystem
function in line 58 and enter the Phaser arcade physics as its parameter.
Next, we enable the physics body for our shipSprite
in line 60 and set the drag (line 61) and maximum movement velocity (line 62) for the sprite.
Going back to our create
function, we then call the initPhysics
function to enable the physics system and add the physics body to our ship:
43 44 45 46 |
create: function () { this.initGraphics(); this.initPhysics(); }, |
Controlling our player ship
Now that our ship has a physics body enabled, we can now add some controls to move our ship. Let’s declare a few properties in our gameState object:
28 29 30 31 32 33 34 |
var gameState = function (game){ this.shipSprite; this.key_left; this.key_right; this.key_thrust; }; |
Looking at the code above, we currently have 3 keys used to control our rotation and forward movement of our player ship.
Next, after the initPhysics
function, add the following:
70 71 72 73 74 |
initKeyboard: function () { this.key_left = game.input.keyboard.addKey(Phaser.Keyboard.LEFT); this.key_right = game.input.keyboard.addKey(Phaser.Keyboard.RIGHT); this.key_thrust = game.input.keyboard.addKey(Phaser.Keyboard.UP); }, |
Each of our 3 key objects are stored in its respective properties. When calling the game.input.keyboard.addKey
function, we pass the key code parameter that represents a specific key we are listening for. For example, the LEFT
key code value is 37, the RIGHT
key code value is 39 and the UP
key code value is 38. You can easily Google up the entire list of key codes or look at the Phaser Keyboard.js file.
Next, in our create
function, call the initKeyboard
function to run the above codes for our keyboard events:
47 48 49 50 51 |
create: function () { this.initGraphics(); this.initPhysics(); this.initKeyboard(); }, |
Now to check for the various key presses and add responses to it. After the initKeyboard
function, add the following:
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
checkPlayerInput: function () { if (this.key_left.isDown) { this.shipSprite.body.angularVelocity = -shipProperties.angularVelocity; } else if (this.key_right.isDown) { this.shipSprite.body.angularVelocity = shipProperties.angularVelocity; } else { this.shipSprite.body.angularVelocity = 0; } if (this.key_thrust.isDown) { game.physics.arcade.accelerationFromRotation(this.shipSprite.rotation, shipProperties.acceleration, this.shipSprite.body.acceleration); } else { this.shipSprite.body.acceleration.set(0); } }, |
The checkPlayerInput
function will be called every frame loop to check if any of the assigned keys are being pressed.
From lines 78-84, we check whether our left or right key is being pressed. Note that only one key is being checked at any time. When the LEFT
key is being pressed, we set the angularVelocity
for the ship’s physics body to a negative value. This rotates the ship counter clockwise. If the RIGHT
key is being pressed, the ship rotates clockwise. Finally if neither left nor right key is being pressed, the ship stops rotating.
As for the UP
or thrust key, we called the arcade physics accelerationFromRotation
function to determine how much acceleration there should be for the x and y axis of the ship. When the key is released, the acceleration is set back to 0 so that the drag can take over and eventually stop our ship from moving forward.
Now to add the checkPlayerInput
function to our update
function:
49 50 51 |
update: function () { this.checkPlayerInput(); }, |
Here’s our work in progress so far. Click on the game world below to activate it if you are unable to move your ship.
You can download the source codes here.
Next week on the 30th October, we’ll look at creating a single screen game world so whenever our ship moves outside the game world, it will reappear on the opposite side.
*Edit: The next part is ready. Let’s move on!
Did you find this post beneficial? Spot any bugs? Do leave a comment and let me know your thoughts.
“After a certain period of time” you get a free ship?? Ace, it’s 10,000 points.
Hey MB, thanks for noticing that =) I’ve made the correction
I am too the point in the tutorial where the files could be downloaded again. Right now everything looks great in Firefox, but in Chrome and Safari nothing shows up.
Are there additional steps I need to make to make this project work in Chrome?
Thank you for the AWESOME tutorial!
Hey Joe, are you using Windows or Mac? You will need a local web server running in order to make it work with Chrome and Safari due to security restrictions.
For Windows, I would suggest WAMP:
http://www.wampserver.com/en/
For Mac, I would suggest MAMP:
https://www.mamp.info/en/
Both are fairly straight forward to set up run.
Alternatively you could try out the Cloud 9 development environment. It’s web based and they do offer a free account:
https://c9.io/
So far I think this tutorial is very helpful.
Also I find cloud9 very friendly and useful after I give up WAMP.
Thank you
Yes, cloud9 is useful if you cant get a local server up and running locally… WAMP is easy to install but is a pain when it doesn’t work. Glad you find the tutorials helpful =) Feel free to suggest any tutorial topics you’d like covered
Anyone can explain why the ship does not always go straight? If you have it at an angle, and try to to small thrusts, you notice some little slips downwards. This also happens here: https://phaser.io/examples/v2/arcade-physics/asteroids-movement. Could it be some rounding gone wrong?
I don’t quite understand what you mean by slipping downward. What I do notice is this: if the x or y velocity is too small, it is reduced to 0 which makes the ship move either vertically or horizontally.