Content

Space Invaders Tutorial

In this tutorial, we will create a space invaders clone. This tutorial will primarily be focused on creating more game elements through code, and using other APIs that MelonJS provides, that the platformer tutorial does not cover.

Introduction

To work through this tutorial, you need the following:

The melonJS boilerplate, that we will use as default template project for our tutorial.

The tutorial image assets, to be uncompressed into the boilerplate data directory. So when you unzip, you should have:

data/img/player.png
data/img/ships.png
js/game.js
etc

The melonJS library. If you downloaded the boilerplate, you will already have this. It should be copied under the /lib directory. You can copy the development version, as the boilerplate provides a minification task.

Testing/debugging :
Your best bet is to use a local web server, as for example detailed in the melonJS boilerplate README, by using the `grunt connect` tool, and that will allow you to test your game in your browser using the http://localhost:8000 url.

If you just want to use the filesystem, the problem is you'll run into "cross-origin request" security errors. With Chrome, you need to use the "--disable-web-security" parameter or better "--allow-file-access-from-files" when launching the browser. This must be done in order to test any local content, else the browser will complain when trying to load assets through XHR. Though this method is not recommended. As long as you have the option enabled, you're adding security vulnerabilities to your session.

Setting up our ships

Your directory structure from the boilerplate should look something like this:

The boilerplate provides a bunch of default code. For this tutorial there are some files that we will not need. You can delete the file screens/title.js, and remove the entire js/entities folder. Then update the index.html file to no longer include those, and remove the references of TitleScreen from the game.js file.

Game.js is where the game is bootstrapped. index.html calls the game.onload function after loading all the js files in the window ready event. The me.video.init bit creates the canvas tag and gets the video setup.

Lines 12-16 are for using the debug panel.

Then we intialize the audio engine, telling it what formats we are supporting for this game.

We also tell, using me.loader, what assets needs to be loaded via an array, and set a callback to our loaded function.

The final step of this process is setting the state of the game to loading.

Note

Game states can be used to for things like a menu screen, play screen, pause screen, etc. When changing state, it's important to note that everything is removed from the game world, unless you have it set to be persistent across states.

The type of the asset. Valid types are: audio, binary, image, json, tmx, tsx. Binary is a good solution for loading raw text, or any other format not listed. TMX & TSX are for tiled file formats. Whether it be the xml or json format.

src

The path to the asset, relative from index.html. For audio you need specify the folder instead of direct path.

Open js/screens/play.js and empty the code from the two methods: onResetEvent and onDestroyEvent. Then save, and then open the game in your web browser.

There is not much to see yet. Let's change that.

First thing is to create a player entity.

Add a new file under the js folder, and call it player.js. Be sure to add it in the index.html file.

So what we're doing is adding a function to the window.game object that extends me.Sprite. It sets up an init method that grabs the player image.

For the x coordinate, we simply grab the dead center, and subtract half the ship, so it can be positioned in the center. And then set its y property to be 20 pixels above the bottom. Then finally pass the image instance to it.

Calling this._super is how we reference a parent classes' method. In this case, we're calling the me.Sprite contrusctor.

Let's setup the player in the entity pool. Open game.js and add the following at the top of the loaded method:

me.pool.register("player", game.Player);

Now open up js/screens/play.js, and edit the onResetEvent method so it looks like this:

The onResetEvent is called when this state is loaded. So when invoking

me.state.change(me.state.PLAY);

In the game.js file, onResetEvent is then called.

Yay, the ship is on the bottom of the screen!

But we can still see the loading bar, that's not cool. The reason for this is that MelonJS does not want to do any operations that it doesn't have to. Sometimes you'll have a background image that gets redrawn, so it covers the original loading bar. However, we don't have a background image for this game, so what we will do is add a color layer.

With the enemy, we will need to place them in different spots, so x & y will be added to its constructor, and then passed along to the me.Entity's constructor. The third parameter in the array is a hash of settings. The settings specifies the image as "ships", referencing our game.resources array. The width and height are set to 32x32.

We're creating a custom update method, to tell the game engine to redraw. When melon goes through the game loop, it does an or on the result.
If there are no changes in a given frame, it won't repaint. Returning true, and calling the super method will ensure the enemies do indeed render.

In game.js, add the enemy to the entity pool:

me.pool.register("enemy", game.Enemy);

Back in play.js, add an enemy to the game world. Your play.js should now look like:

You can put the enemy at any x & y to try it out. Save & refresh the page in your browser.

You'll likely notice that the ship is constantly changing how it looks. If you open the ships.png file under data/img, you can see that it is a sprite sheet containing 4 different ships. me.Entity for its renderable uses the me.AnimationSheet class. Since we didn't add and set any animations on the renderable property, it is just looping through each & every frame. Let's fix that.

The first line simply randomizes which frame we want. The ship is 32x32, the image is 64x64, so we have 4 frames. ~~ is a shortcut for Math.floor when the number is 0 or positive. On negative numbers, it works like Math.ceil.

The second line is accessing the animation sheet instance (this.renderable), and uses the addAnimation function to add a new idle frame. So we simply specify the index that was generated at random.

The method calls here are pretty straight forward. We bind a keypress to an action name. Multiple keys can be assigned to a single action name.

It's typically a good game design practice to offer multiple key bindings. Even a better practice make it configurable. You always need to keep in mind people who are left handed or who have different layouts.

You might also noticed i added the z index option to the addChild calls. It's a pretty good practice, because that way you ensure your draw order.

The onDestroyEvent removes the events when changing state. Not something we actually need, because we only have the play state after loading. But a good practice to keep in mind.

Now that we have bindings, let's implement player movement. Add the following update function to the player class:

Then add a velx property to the player in its init method, as well as the furthest x position it can go on screen (maxX):

Note

The reason for use of delta time, is to have consistent transformations across various frame rates. If you have pos.x += 5; in your update, and run at 1 frame per second, the player will move 5 pixels once per second. If you have 30 fps, the player will move at 150 pixels per second. The delta time is used to level the playing field. The less fps you have, the more jumpy it will look.

We then use clamp to ensure the x value does not go outside the screen.

this.pos.x = me.Math.clamp(this.pos.x, 0, this.maxX);

The return value tells melon whether a re-draw is required. This can be useful to dictate for when an animation sheet needs to animate on a given frame. However, this is a single sprite, so we can just tell it to redraw.

return true;

Save the file & refresh your browser. Try using A/D or the Left & Right arrow keys to move.

Enemy movement

A defining characteristic of space invaders is that all the ships move in one direction, shift down and then go in the other direction. They all move together. We could take the velocity logic that we used for the player, and apply it to the enemy class. But we can better leverage MelonJS to do this for us. Time to use our own subclass of me.Container

Objects inside a container are relative to its parent. So when we move the container, all objects inside shift with it. This applies to rotation & scale operations as well. So let's create one.

Generating 9 columns, and 4 rows: 36 ships. Notice as well the call to the "updateChildBounds" function that will ensure that our object container is resized properly
to take in account all added childs. Correspondly we will later to the same when removing a child

Now in play.js, remove the addChild for the enemy, and set a property to an enemy manager. Below that invoke createEnemies, and add it to the game world.

onActivateEvent is called (if it's defined) when the object is added to the game world. This goes for any object you pass to addChild on a container. Likewise, onDeactivateEvent is called when the object is removed from the game world.

Using the MelonJS version of setInterval (which is built into the game loop, it does not use window.setInterval), we can then increment the x position.

The reason for extending removeChildNow instead of removeChild, is removeChild is called after the current frame finishes. removeChildNow is the method that actually removes the object, and we want to resize the bounds after the object has been removed.

Then the last bit, we increment the velocity if the container hasn't moved left or right

else {
_this.pos.x += _this.vel;
}

Save and refresh this time, it should now move back and forth across the screen, closer to our player. But there's a problem! It isn't turning around when it gets to the edge! What's going on here?

At this point, you should add #debug to the URL: http://localhost:8000/#debug and tap on the checkbox next to "hitbox" in the debug panel. This will enable hitbox rendering, so you can visualize the internal structure of your objects.

The hitboxes show that the child bounds (the large purple rectangle occupying the EnemyManager's original position) is not moving with the container. This is because child bounds updates are not automatically computed. You can easily recompute the child bounds by overloading the update method:

So lets go through it. At the bottom, we have set two properties of width & height for the laser, so it can easily be re-used.

game.Laser.width = 5;
game.Laser.height = 28;

Traditional stuff here. Setup the x & y position from its parameters, and a width+height properties. A bit different from our other objects, we have set the z index on the object manually. This is an alternative to passing the z index in the addChild call.

The renderer can be either me.CanvasRenderer or me.WebGLRenderer, depending on your settings in me.video.init. The renderer provides basic drawing operations.

So we first get a reference of the original color. the return value of getColor() is an instance of me.Color.

var color = renderer.getColor();

Set the color to a nice laser green.

renderer.setColor('#5EFF7E');

Then use a fill rect number. Again, 0, 0 is relative. Then use the width & height to dictate the size of the rect we're filling

renderer.fillRect(0, 0, this.width, this.height);

Then set the color back. This is important so our other draw calls will not be effected by the colour change.

renderer.setColor(color);

Generally speaking you should create your game using images over using pure canvas draw calls, but knowing how and when to use the canvas can be rather useful.

The final step for our Laser's init method:

init : function (x, y) {
// ...
this.alwaysUpdate = true;
}

The alwaysUpdate property is to be avoided as much as possible. It will update an object when it is outside the viewport. The reason to use it in this game is because we don't want to remove the laser until it is offscreen. If we wait until it's offscreen, and alwaysUpdate is false, it will never get removed.

The first bit is how we will move the laser. The ship was just moved by manipulating the position directly. Since this has a collision body, we will manipulate the y velocity, by subtracting the y acceleration.

this.body.vel.y -= this.body.accel.y * time / 1000;

If the position of the laser plus the height (so the bottom of the laser) is less than zero, we can remove the laser from the game world. Again, this will function now work because alwaysUpdate is set to true.

The return false in this case isn't strictly necessary, but it's important to point out. When you return false from a collision handler in MelonJS, the object will pass through. If you return true, it will do a hard stop.

Save the changes, and reload your browser. You should now be able to take out the enemy ships.

Next step, is adding the win & loss conditions.

Win & Loss Conditions

The final step to this game is to actually add conditions for winning & losing. The conditions themselves will be pretty straight forward. When the ships get within range of the player, the player loses. When the player destroys all the enemy ships, they win.

So what happens when the game ends? A lot of the time you want to display a screen of some sort that the player lost or won. To keep this simple and show you another little trick, we'll just reset the game. So it starts over.

First, we'll do the loss condition

The pseudo code for this will be:

if enemy manager overlaps player
then end game
else
continue
end

The PlayScreen is our current game state. It holds the reference to the player, and it has the ability to reset the state. So let's add the logic for checking a lose condition there.

Add that above the onResetEvent method. It accepts a Y value, and checks if it has surpassed the player. Then calls its reset method. The reset will wipe out every object from the game world, and reload the state. So it re-invokes onResetEvent, re-populating the enemies and player.

Now to call this condition check, simply add the method call to our interval in the enemy manager:

This is pretty simple. Children is an array, so we check its length to be zero, and ensure it has created enemies first. Without the boolean check, the game could keep resetting itself because it has no children yet.

Save and refresh the browser. Try to take out all the ships in time, and see the game reset.

Challenges

We left some parts out of this tutorial, so you could explore them yourself. This is an important part of programming and game development.

If you get stuck on any of the challenges or parts of the tutorial, please search for the problem, or ask us the question on our forum @html5gamedevs

The win and loss screen can contain a sprite, or text, or both. Whatever you wish really. Be sure to look at me.Font and me.Sprite. To display a me.Font object, use an instance of me.Renderable that contains an instance of me.Font, and implement the draw function to invoke me.Font#draw.

Adjust the checkIfLoss method to show your new loss screen instead.

Adjust the if block in the update method on EnemyManager, to change state to your win screneobject.

Even more bonus, add a menu screen that tells the player how to play.

Challenge #2

Add a UI

Add an enemy counter, and enemy velocity to the top right/left corner of the screen. These properties can be retrieved via: game.playScreen.enemyManager.children.lengthgame.playScreen.enemyManager.vel

Again look at me.Font, and implement a renderable for drawing text. Try to only use one class that extends renderable that can be used for both UI pieces.

Add a score element. Keep track of the score on the play screen. Update it each time an enemy is killed. Remember that enemies are removed from the collision handler on the laser.

Challenge #3

Add the concept of levels

After you defeat a wave, instead of refreshing the same wave, do a new wave the starts faster. The main logic here will be keeping wave count on the game.js, and increase it after each win. Then use that count in the enemy manager to configure the velocity.

Have each wave progress faster too (+ 8 each Y increment over + 5 for example). Play with the numbers a bit until it feels right.