spacer

Useful Links

  • Tiled Homepage
  • TapJS Homepage

A step by step game creation tutorial

Introduction:

Prerequisites :

  • Tiled version 0.7.x, installed and running
  • The melonJS documentation for more details
  • This template file, containing what we will use for the tutorial :
    • a copy of melonJS (0.9.3) (/lib)
    • a level tileset and a metatileset for collision (/data/area01_tileset/)
    • two background for parallax layers (/data/area01_parallax/)
    • some basic spritesheet (/data/sprite/)
    • some audio sfx and music (/data/audio/)
    • a title screen background (/data/GUI/)
    • a main.js skeleton
    • a default index.html

Testing/debugging :
When using Chrome, and due to the "cross-origin request" security mechanism implemented, you need to use the "--disable-web-security" parameter or better "--allow-file-access-from-files" when launching the browser in order to test any local content, else the browser will complain when trying to load a level map.

Additional Credits :
- SpicyPixel.NET for the GfxLib-Fuzed assets
- noSoapRadio for the in game music

Feel free to modify whatever you want. We also assume here, that you are already familiar with Tiled; if you need more help with the tool, you can check the Tiled homepage and wiki for further help

Part 1: Creating a level using Tiled

melonJS supports only uncompressed tilemaps, so before continuing, please check that your settings are correct (Tiled/Preferences). I recommend the Base64 encoding, since it produces a smaller file, but it's really up to you. spacer

First let's open Tiled and create a new map : for this tutorial we will we use a 640x480 canvas, and since we have 32x32 tiles, we must specify at least 20 and 15 for the map size. In my example I'll define a 40x15 level, so we can play with scrolling background later.

spacer

Then let's add both our tileset (using Map/New Tileset), and the "meta" tileset that we will use for collision. Both have no spacing or margin, so be sure to let the corresponding values to zero in tiled (note: melonJS support tilesets with margin and space)

spacer

For the beauty of it, we will create two layers - one background layer, and one foreground layer. Feel free to use your imagination and do whatever you want. I named them logically "background" and "foreground", but you can put whatever you want.

Here's what my level looked like when I finished it : spacer

Finally, let's define a background color for our level, by adding a "background_color" property to the map (Map/Map Properties), and just specify any color (in CSS format) you prefer.

spacer

To finish, let's save our new map as "area01" under the "data" folder. We are done the first step!

Part 2: Loading our level

First let's have a look at our main.js skeleton :


// game resources
var g_resources = [];

var jsApp = {
    /* ---

     Initialize the jsApp

     --- */
    onload: function() {

        // init the video
        if (!me.video.init('jsapp', 640, 480, false, 1.0)) {
            alert("Sorry but your browser does not support html 5 canvas.");
            return;
        }

        // initialize the "audio"
        me.audio.init("mp3,ogg");

        // set all resources to be loaded
        me.loader.onload = this.loaded.bind(this);

        // set all resources to be loaded
        me.loader.preload(g_resources);

        // load everything & display a loading screen
        me.state.change(me.state.LOADING);
    },

    /* ---

     callback when everything is loaded

     --- */
    loaded: function() {
        // set the "Play/Ingame" Screen Object
        me.state.set(me.state.PLAY, new PlayScreen());

        // start the game
        me.state.change(me.state.PLAY);
    }

};
// jsApp
/* the in game stuff*/
var PlayScreen = me.ScreenObject.extend({

    onResetEvent: function() {
        // stuff to reset on state change
    },

    /* ---

     action to perform when game is finished (state change)

     --- */
    onDestroyEvent: function() {
    }

});

//bootstrap :)
window.onReady(function() {
    jsApp.onload();
});

This is very simple. Once the page is loaded, the onload() function is called, the display and audio is initialized, and all game resources begin loading. We also define a callback to be called when everything is ready to be used. Within the callback, we define a new state that will be used for the in game stuff, together with a PlayScreen object that we will use to manage the game event (reset, etc...).

So in order to load our level, the next thing is to add the resources to be loaded by adding the following information into here (for example) the g_resources object :

  • the tileset itself, an image
  • our map "area01", a "tmx" object
//game resources
var g_resources = [{
    name: "area01_level_tiles",
    type: "image",
    src: "data/area01_tileset/area01_level_tiles.png"
}, {
    name: "area01",
    type: "tmx",
    src: "data/area01.tmx"
}];

Be sure as well to correctly name the tileset resource name accordingly to the filename, else the level loader will not be able to find the tileset and will fail.

Finally, in the onResetEvent() function (which is called on a state change), we ask the level director to display our previously preloaded level :

/* the in game stuff*/
var PlayScreen = me.ScreenObject.extend({

    onResetEvent: function() {
        // stuff to reset on state change
        // load a level
        me.levelDirector.loadLevel("area01");
    },

    /* ---

    action to perform when game is finished (state change)

    --- */
    onDestroyEvent: function() {
}

});


That's all! If you did everything correctly, and open you index.html, you should see something like this:
(click on the image to see it running in your browser)
Yes, nothing fancy yet, but that's only the beginning!

Also in case you didn't noticed, since we defined a 640x480 display in our application, we only see a part of the map (the half of it to be exact), which is normal. melonJS automatically create a corresponding viewport, and we will be able to navigate through the map in the next step, when we will add a "main player"

Part 3: Add a main player

Here we will create a new object by extending the default me.ObjectEntity, to create our player. We will use the provided simple spritesheet (gripe_run_right.png) to animate our character. It's of course possible to define different animation for the same entity, but let's keep things simple first.

spacer

First, let's add our spritesheet in the list of the resources to be loaded, just after our map :

//game resources
var g_resources = [{
    name: "area01_level_tiles",
    type: "image",
    src: "data/area01_tileset/area01_level_tiles.png"
}, {
    name: "area01",
    type: "tmx",
    src: "data/area01.tmx"
}, {
    name: "gripe_run_right",
    type: "image",
    src: "data/sprite/gripe_run_right.png"
}];

Then it's time to create our entity:
(Feel free to put this object either in our main.js or in a new file, in my example I use gameObj.js)

/*------------------- 
a player entity
-------------------------------- */
var PlayerEntity = me.ObjectEntity.extend({

    /* -----

    constructor

    ------ */

    init: function(x, y, settings) {
        // call the constructor
        this.parent(x, y, settings);

        // set the walking & jumping speed
        this.setVelocity(3, 15);

        // set the display to follow our position on both axis
        me.game.viewport.follow(this.pos, me.game.viewport.AXIS.BOTH);

    },

    /* -----

    update the player pos

    ------ */
    update: function() {

        if (me.input.isKeyPressed('left')) {
            this.doWalk(true);
        } else if (me.input.isKeyPressed('right')) {
            this.doWalk(false);
        } else {
            this.vel.x = 0;
        }
        if (me.input.isKeyPressed('jump')) {
            this.doJump();
        }

        // check & update player movement
        this.updateMovement();

        // update animation if necessary
        if (this.vel.x!=0 || this.vel.y!=0) {
            // update objet animation
            this.parent(this);
            return true;
        }
        return false;
    }

});

I think the above code is quite easy to understand. Basically, we extend the ObjectEntity, configure the default player speed, tweak the camera, test if some keys are pressed and manage our player movement (by setting player speed, and then calling the updateMovement function). Also, you may notice that I'm testing the final velocity (this.vel.x and this.vel.y) of my object, which allows me to know if my object actually moved, and control if I want the sprite animation to run or not.

Note that the functions here above, like doWalk() and doJump(), are actually helper functions for platform games that basically compute the new player velocity based on the parameters defined by the setVelocity() function :

/**
 * helper function for platform games
 * make the entity move left of right
 * @param {Boolean} left will automatically flip horizontally the entity sprite
 */
doWalk : function(left) {
	this.flipX(left);
	this.vel.x += (left) ? -this.accel.x * me.timer.tick : this.accel.x * me.timer.tick;
},

If you do plan to develop any other type of games (or if have more complex needs), I do not recommend using these function, but to rather directly modify the entity velocity based on your requirements.

Then, we have to modify our "main" to actually declare our new Object in the EntityPool (that is used by the engine to instantiate object), and finally to map the keys we will use for the player movement. So our loaded() function will become :


/* ---

   callback when everything is loaded
	
   ---	*/
	
loaded: function ()
{
   // set the "Play/Ingame" Screen Object
   me.state.set(me.state.PLAY, new PlayScreen());
    
   // add our player entity in the entity pool
   me.entityPool.add("mainPlayer", PlayerEntity);
			
   // enable the keyboard
   me.input.bindKey(me.input.KEY.LEFT,	"left");
   me.input.bindKey(me.input.KEY.RIGHT,	"right");
   me.input.bindKey(me.input.KEY.X,     "jump", true);
     
   // start the game 
   me.state.change(me.state.PLAY);
}


And now we can add our entity into the level! Go back to Tiled, add an new Object Layer, and finally a new Entity.
Name it (case does not matter) mainPlayer (or using the same name you used when adding our Object into the entityPool), and add two properties to the Object :
- image with the gripe_run_right value (name of our resource
- spritewidth with the value 64 which is the size of a single sprite in the spritesheet
- spriteheight we don't define this value here since we use a single line spritesheet, and since in this case the engine will take the actual image height as a value for it.

These two parameters will be passed as parameters (settings object here above used by the constructor) when the object will be created. Now you can either specify these fields here in Tiled, or directly in your code (when dealing with multiple objects, it can be easier to just specify the name in Tiled, and manage the rest in the constructor directly).

Note: You also free to add as many properties as you want, they will all be available in the settings object passed to your constructor.

spacer

We are almost done! The last step is to define the collision layer, for this we will use the other tileset we previously added into Tiled, and specify for each tile a property corresponding to the tile type.

spacer

First, right click on the "Solid" tile (1st one), and add a "type" property with "solid" as a value.

spacer

Then, right click on the "Platform" tile (2nd one), and add a "type" property with "platform" as a value.

spacer

That's it! Though we won't be using them for this tutorial, it is nice to know that the others possible values are "lslope" for the left slope, "rslope" for the right slope, "ladder" for ladder tiles and "breakable" (you'll never guess) for breakable tiles. Also be careful when defining these two fields, as the engine is exactly looking for these labels, so if they are incorrect, it won't work.

Now add a new Tilelayer. This layer MUST contains the keyword "collision" for the engine to recognize it as a collision layer.

Once the layer added, select it, and just "draw" you level collision map. At the end it should look like this:

spacer


Save everything, and if you now re-open you index.html, you should see something like this :
(click on the image to see it running in your browser)

You will also notice that display is automatically following our player.

One last thing - when creating an object, a collision rectangle is automatically created to manage collision between objects. For debugging purposes, you can use the following debug settings in your main to enable it :

me.debug.renderHitBox = true;

If you reload the game, you will see this:

spacer

As you can see we have a lot of white space on the left and right of our character, so let's adjust the collision rectangle to our sprite:

/* -----

    constructor

    ------ */

init: function(x, y, settings) {
    // call the constructor
    this.parent(x, y, settings);

    // set the walking & jumping speed
    this.setVelocity(3, 15);

    // adjust the bounding box
    this.updateColRect(8, 48, -1, 0);

    // set the display to follow our position on both axis
    me.game.viewport.follow(this.pos, me.game.viewport.AXIS.BOTH);

},

Here we add a horizontal 8 pixel offset, and set it's width to 48 pixels. We don't change the height, so we specify -1.

spacer

Part 4: Add a scrolling background

This one is very easy. We don't even have to add a single line of code, since everything is done through Tiled.

First, let's remove in Tiled the "background_color" property (Map/Map properties) that we added previously at the end of Part 1. Since the background will be filled with our scrolling layers, we don't need the display to be cleared with a specific color (furthermore it will save some precious frames).

Then we will use the two following backgrounds :

/data/area01_parallax/area01_bkg0.png for the first background layer

spacer

/data/area01_parallax/area01_bkg1.png for the second background layer

spacer

Let's add them in the resource list:

// game resources
var g_resources = [
// our level tileset
{
    name: "area01_level_tiles",
    type: "image",
    src: "data/area01_tileset/area01_level_tiles.png"
}, 
// our level
{
    name: "area01",
    type: "tmx",
    src: "data/area01.tmx"
}, 
// the main player spritesheet
{
    name: "gripe_run_right",
    type: "image",
    src: "data/sprite/gripe_run_right.png"
}, 
// the parallax background
{
    name: "area01_bkg0",
    type: "image",
    src: "data/area01_parallax/area01_bkg0.png"
}, {
    name: "area01_bkg1",
    type: "image",
    src: "data/area01_parallax/area01_bkg1.png"
}];

Open Tiled, and add two new Tile Layer. These layers MUST contains the keyword "parallax" for the engine to recognize it as a parallax layers. Be sure as well to adjust correctly the layer order (the display order being from bottom to top)

spacer

Now right-click the layers to define their properties and add the following property
- imagesrc property with the area01_bkg0 value for the first layer (parallax_layer1 in my example), and area01_bkg1 value for the second one (parallax_layer2 in my example)

spacer


"Et voila!". If you now open you index.html, you should see:
spacer

Play around with your player, and enjoy the view :)

Part 5: Adding some basic objects and enemies

In this part we will add a collectible coin (that we will use later to add to our score), using the spinning_coin_gold.png spritesheet : spacer

And a basic enemy, using the wheelie_right.png spritesheet : spacer

As always, add them to the resource list:

// game resources
var g_resources = [
// our level tileset
{
    name: "area01_level_tiles",
    type: "image",
    src: "data/area01_tileset/area01_level_tiles.png"
}, 
// our level
{
    name: "area01",
    type: "tmx",
    src: "data/area01.tmx"
}, 
// the main player spritesheet
{
    name: "gripe_run_right",
    type: "image",
    src: "data/sprite/gripe_run_right.png"
}, 
// the parallax background
{
    name: "area01_bkg0",
    type: "image",
    src: "data/area01_parallax/area01_bkg0.png"
}, {
    name: "area01_bkg1",
    type: "image",
    src: "data/area01_parallax/area01_bkg1.png"
}, 
// the spinning coin spritesheet
{
    name: "spinning_coin_gold",
    type: "image",
    src: "data/sprite/spinning_coin_gold.png"
}, 
// our enemty entity
{
    name: "wheelie_right",
    type: "image",
    src: "data/sprite/wheelie_right.png"
}];

The coin itself is pretty easy; we just extend the me.CollectableEntity. Actually, we could directly use it in Tiled (without needing to create CoinEntity here), but since we will add some score and some audio sfx later when the coin is collected, let's do it directly this way.

/*----------------
 a Coin entity
------------------------ */
var CoinEntity = me.CollectableEntity.extend({
    // extending the init function is not mandatory
    // unless you need to add some extra initialization
    init: function(x, y, settings) {
        // call the parent constructor
        this.parent(x, y, settings);
    },

    // this function is called by the engine, when
    // an object is touched by something (here collected)
    onCollision: function() {
        // do something when collected
	}

});

Also, just to be sure it's clear for you that both ways of doing this is possible, we will define the Coin object properties directly in Tiled, so we don't need to add anything else in the constructor for now :
spacer


For our enemy, it's a bit longer :

/* --------------------------
an enemy Entity
------------------------ */
var EnemyEntity = me.ObjectEntity.extend({
    init: function(x, y, settings) {
        // define this here instead of tiled
        settings.image = "wheelie_right";
        settings.spritewidth = 64;

        // call the parent constructor
        this.parent(x, y, settings);

        this.startX = x;
        this.endX = x + settings.width - settings.spritewidth;
        // size of sprite

        // make him start from the right
        this.pos.x = x + settings.width - settings.spritewidth;
        this.walkLeft = true;

        // walking & jumping speed
        this.setVelocity(4, 6);

        // make it collidable
        this.collidable = true;
        // make it a enemy object
        this.type = me.game.ENEMY_OBJECT;

    },

    // call by the engine when colliding with another object
    // obj parameter corresponds to the other object (typically the player) touching this one
    onCollision: function(res, obj) {

        // res.y >0 means touched by something on the bottom
        // which mean at top position for this one
        if (this.alive && (res.y > 0) && obj.falling) {
            this.flicker(45);
        }
    },

    // manage the enemy movement
    update: function() {
        // do nothing if not visible
        if (!this.visible)
            return false;

        if (this.alive) {
            if (this.walkLeft && this.pos.x <= this.startX) {
                this.walkLeft = false;
            } else if (!this.walkLeft && this.pos.x >= this.endX) {
                this.walkLeft = true;
            }
            this.doWalk(this.walkLeft);
        } else {
            this.vel.x = 0;
        }
		
        // check and update movement
        this.updateMovement();
		
        // update animation if necessary
        if (this.vel.x!=0 || this.vel.y!=0) {
            // update objet animation
            this.parent(this);
            return true;
        }
        return false;
    }
});
    

As you can see here, I specified the settings.image and settings.spritewidth properties in the constructor directly, meaning that in Tiled, I won't have to add these properties to my Object (Once again, it's up to you to decide how to use it).
Also, I am using the width property given by Tiled to specify a path on which this enemy will run. Finally, in the onCollision method, I make the enemy flicker if something is jumping on top of it.

Then again, we add these new objects in the entityPool

// add our object entities in the entity pool
me.entityPool.add("mainPlayer", PlayerEntity);
me.entityPool.add("CoinEntity", CoinEntity);
me.entityPool.add("EnemyEntity", EnemyEntity);

And we are ready to complete our level in Tiled. Create a new object layer, and use the Insert Object tool to add coins and enemies where you want. Right-click on each object and make sure to set their name to either CoinEntity or EnemyEntity. spacer

Before testing, we also need to modify our player to heck for collision with other entities. In order to do this, we add a call to the me.game.collide(this) function in our mainPlayer code, see below :

/* -----
update the player pos
------ */
update: function() {

    if (me.input.isKeyPressed('left')) {
        this.doWalk(true);
    } else if (me.input.isKeyPressed('right')) {
        this.doWalk(false);
    } else {
        this.vel.x = 0;
    }
    if (me.input.isKeyPressed('jump')) {
        this.doJump();
    }

    // check & update player movement
    this.updateMovement();

    // check for collision
    var res = me.game.collide(this);

    if (res) {
        // if we collide with an enemy
        if (res.obj.type == me.game.ENEMY_OBJECT) {
            // check if we jumped on it
            if ((res.y > 0) && ! this.jumping) {
                // bounce
                this.forceJump();
            } else {
                // let's flicker in case we touched an enemy
                this.flicker(45);
            }
        }
    }

    // update animation if necessary
    if (this.vel.x!=0 || this.vel.y!=0) {
        // update objet animation
        this.parent(this);
        return true;
    }
    return false;

}

});


And this is what you should get (note that I completed the level a little bit, adding platforms, etc...) :
spacer

Try to collect your coins, avoid the enemy or jump on it!

Part 6: Adding some basic HUD information

It's time now to display some score when we collect those coins, right?

We will use a bitmap font (data/sprite/32x32_font.png) to display our score, as always we need to add it in our list of resources to be loaded :

// game resources
var g_resources = [
// our level tileset
{
    name: "area01_level_tiles",
    type: "image",
    src: "data/area01_tileset/area01_level_tiles.png"
}, 
// our level
{
    name: "area01",
    type: "tmx",
    src: "data/area01.tmx"
}, 
// the main player spritesheet
{
    name: "gripe_run_right",
    type: "image",
    src: "data/sprite/gripe_run_right.png"
}, 
// the parallax background
{
    name: "area01_bkg0",
    type: "image",
    src: "data/area01_parallax/area01_bkg0.png"
}, {
    name: "area01_bkg1",
    type: "image",
    src: "data/area01_parallax/area01_bkg1.png"
}, 
// the spinning coin spritesheet
{
    name: "spinning_coin_gold",
    type: "image",
    src: "data/sprite/spinning_coin_gold.png"
}, 
// our enemty entity
{
    name: "wheelie_right",
    type: "image",
    src: "data/sprite/wheelie_right.png"
}, 
// game font
{
    name: "32x32_font",
    type: "image",
    src: "data/sprite/32x32_font.png"
}];

Let's then define a new Object, that will extend the me.HUD_Item, in which we will draw the object value using our font.

/*-------------- 
a score HUD Item
--------------------- */

var ScoreObject = me.HUD_Item.extend({
    init: function(x, y) {
        // call the parent constructor
        this.parent(x, y);
        // create a font
        this.font = new me.BitmapFont("32x32_font", 32);
    },

    /* -----

    draw our score

    ------ */
    draw: function(context, x, y) {
        this.font.draw(context, this.value, this.pos.x + x, this.pos.y + y);
    }

});

And display it when we start a new game :

/* the in game stuff*/
var PlayScreen = me.ScreenObject.extend({

    onResetEvent: function() {
        // load a level
        me.levelDirector.loadLevel("area01");

        // add a default HUD to the game mngr
        me.game.addHUD(0, 430, 640, 60);

        // add a new HUD item
        me.game.HUD.addItem("score", new ScoreObject(620, 10));

        // make sure everyhting is in the right order
        me.game.sort();

    },

    /* ---

    action to perform when game is finished (state change)

    --- */
    onDestroyEvent: function() {
        // remove the HUD
        me.game.disableHUD();
    }

});

The me.game.addHUD() function is used to enable a HUD Object (specifying the HUD size), and we can then add our previously created score HUD Item by calling the me.game.HUD.addItem() function (and specifying its position within the HUD). We also assign the "score" keyword to that item, so that we can later access and modify it's value.

Additionally, we also add a call to me.game.disableHUD() in the onDestroyEvent() function, so that the HUD is removed when exiting the "play" state.

Now let's modify our Coin Object, and add some score when a coin is collected :

onCollision : function ()
{
	// give some score
	me.game.HUD.updateItemValue("score", 250);
	// make sure it cannot be collected "again"
	this.collidable = false;
	// remove it
	me.game.remove(this);
}

As you can see, in the onCollision function, we just call the me.game.HUD.updateItemValue function, giving the "score" keyword (we previously defined), and the value to be added to the score. Then we ensure the object cannot be collected again, and remove the coin


We can now check the result, and we should now have our score displayed in the bottom-right corner of the screen :
spacer

Part 7: Adding some audio

In this section we will add some audio to our game:

  • a sound when collecting a coin
  • a sound when jumping
  • a sound when stomping on enemy
  • a background (or in game music)

Then we will first add them to our resource list:

//game resources
var g_resources = [
// our level tileset
{
    name: "area01_level_tiles",
    type: "image",
    src: "data/area01_tileset/area01_level_tiles.png"
}, 
// our level
{
    name: "area01",
    type: "tmx",
    src: "data/area01.tmx"
}, 
// the main player spritesheet
{
    name: "gripe_run_right",
    type: "image",
    src: "data/sprite/gripe_run_right.png"
}, 
// the parallax background
{
    name: "area01_bkg0",
    type: "image",
    src: "data/area01_parallax/area01_bkg0.png"
}, {
    name: "area01_bkg1",
    type: "image",
    src: "data/area01_parallax/area01_bkg1.png"
}, 
// the spinning coin spritesheet
{
    name: "spinning_coin_gold",
    type: "image",
    src: "data/sprite/spinning_coin_gold.png"
}, 
// our enemty entity
{
    name: "wheelie_right",
    type: "image",
    src: "data/sprite/wheelie_right.png"
}, 
// game font
{
    name: "32x32_font",
    type: "image",
    src: "data/sprite/32x32_font.png"
}, 
// audio resources
{
    name: "cling",
    type: "audio",
    src: "data/audio/",
    channel: 2
}, {
    name: "stomp",
    type: "audio",
    src: "data/audio/",
    channel: 1
}, {
    name: "jump",
    type: "audio",
    src: "data/audio/",
    channel: 1
}, {
    name: "DST-InertExponent",
    type: "audio",
    src: "data/audio/",
    channel: 1
}];

In case you did not notice yet, we didn't specify any extension when adding the audio element to the resource list, but instead, the path where the audio can be found. Why? Simply because we cannot know which format is supported by the browser. Instead, we let melonJS find the right format, and then load the right audio file accordingly.

If we take a look back on how we first initialized the audio, you can see that I passed the "mp3,ogg" parameter to the initialization function, asking to try to use first the mp3 format, and then ogg as a fallback if mp3 is not supported. This also means, in this case, I must provide two versions of my audio files, one as mp3, and one as ogg. The engine will then use the right based on your browser capabilities

// initialize the "audio"
me.audio.init("mp3,ogg");

Then finally, still in the resource list, an audio element takes an extra parameter, the number of channel. This is useful if you need a sound to be played simultaneously. Let's take the example of the coin, if two coins are very close, there is a high "risk" ou player will hit them almost at the same time, and we must be able to notify the user with two distinct "bling" sound.

Still following? Let's modify our game :

  • Collecting a coin

In the CoinEntity code, where we previously managed our earned points, we just need to add a new call to me.audio.play() and use the "cling" audio resource. that's all !


onCollision : function ()
{
	// do something when collide
	me.audio.play("cling");
	// give some score
	me.game.HUD.updateItemValue("score", 250);
	// make sure it cannot be collected "again"
	this.collidable = false;
	// remove it
	me.game.remove(this);
}

  • Jumping

In the update() function of the mainPlayer, we also add a call to me.audio.play() and use the "jump" audio resource. You can also note that I added a test on the return value of doJump(). doJump can return false in case you are not allowed to jump (already jumping, etc..) and in that case there is no need to play the sound sfx.


if (me.input.isKeyPressed('jump')) {
    if (this.doJump()) {
        me.audio.play("jump");
    }
}

  • Stomping

And still the same for this one, but using the "stomp" resource, still in the update function of the mainPlayer :


// check for collision
var res = me.game.collide(this);

if (res) {
    if (res.obj.type == me.game.ENEMY_OBJECT) {
        if ((res.y > 0) && ! this.jumping) {
            // bounce
            me.audio.play("stomp");
            this.forceJump();
        } else {
            // let's flicker in case we touched an enemy
            this.flicker(45);
        }
    }
}

  • In game music

In our main, in the onResetEvent() function, we just add a call to the me.audio.playTrack() function, specifying the audio track to be used:


onResetEvent: function() {
    // play the audio track
    me.audio.playTrack("DST-InertExponent");
....
},

And we also need to modify the onDestroyEvent() function to stop the current track when exiting the game :


onDestroyEvent: function() {
    // remove the HUD
    me.game.disableHUD();

    // stop the current audio track
    me.audio.stopTrack();
}

That's all! click here to see the final result.

Part 8: Adding a second level

You should know how to create a level now. However, here I will show you how to go to another level.

To do this, melonJS has an Object call me.LevelEntity, that we will add in Tiled and specify what to do when our main player hit it :

spacer

Assuming that our new level is called "area02", we just need to add a "to" property with "area02" for the value. So that when our player will hit the Object, the engine will automatically load the "area02" level.
Optionally we can also ask the engine to add a fadeOut/fadeIn effect when changing level by adding the "fade" color and "duration" (in ms) properties (as in the image)

click here to see the final result.

Part 9: Adding a title screen

To finish, let's add a title screen to our game, using the title_screen.png files in the "/data/GUI" folder (and of course to be added in the ressource list, as we done it previously for other images) : spacer and on top of it we will add some message, and wait for the user input to start the game !

First let's declare a new Object, extending me.ScreenObject :

/*----------------------

    A title screen

    ----------------------*/

var TitleScreen = me.ScreenObject.extend({
    // constructor
    init: function() {
        this.parent(true);
    },

    // reset function
    onResetEvent: function() {
	},

    // update function
    update: function() {
	},

    // draw function
    draw: function(context) {
	},

    // destroy function
    onDestroyEvent: function() {
	}

    });

Note that in this example, when I call the parent constructor, I'm passing the "true" to the function, allowing me to extend the update and draw function of my Object (else they are not called by the engine).

So now we want to:
- display the above screen
- add some text to the center of the screen ("Press enter to play")
- wait for user input (pressing enter)
Additionally, I also want to add a small scrolling text about this tutorial.

/*----------------------

    A title screen

  ----------------------*/

var TitleScreen = me.ScreenObject.extend({
    // constructor
    init: function() {
        this.parent(true);

        // title screen image
        this.title = null;

        this.font = null;
        this.scrollerfont = null;
        this.scrollertween = null;

        this.scroller = "A SMALL STEP BY STEP TUTORIAL FOR GAME CREATION WITH MELONJS       ";
        this.scrollerpos = 600;
    },

    // reset function
    onResetEvent: function() {
        if (this.title == null) {
            // init stuff if not yet done
            this.title = me.loader.getImage("title_screen");
            // font to display the menu items
            this.font = new me.BitmapFont("32x32_font", 32);
            this.font.set("left");

            // set the scroller
            this.scrollerfont = new me.BitmapFont("32x32_font", 32);
            this.scrollerfont.set("left");

        }

        // reset to default value
        this.scrollerpos = 640;

        // a tween to animate the arrow
        this.scrollertween = new me.Tween(this).to({
            scrollerpos: -2200
        }, 10000).onComplete(this.scrollover.bind(this)).start();

        // enable the keyboard
        me.input.bindKey(me.input.KEY.ENTER, "enter", true);

        // play something
        me.audio.play("cling");

    },

    // some callback for the tween objects
    scrollover: function() {
        // reset to default value
        this.scrollerpos = 640;
        this.scrollertween.to({
            scrollerpos: -2200
        }, 10000).onComplete(this.scrollover.bind(this)).start();
    },

    // update function
    update: function() {
        // enter pressed ?
        if (me.input.isKeyPressed('enter')) {
            me.state.change(me.state.PLAY);
        }
        return true;
    },

    // draw function
    draw: function(context) {
        context.drawImage(this.title, 0, 0);

        this.font.draw(context, "PRESS ENTER TO PLAY", 20, 240);
        this.scrollerfont.draw(context, this.scroller, this.scrollerpos, 440);
    },

    // destroy function
    onDestroyEvent: function() {
        me.input.unbindKey(me.input.KEY.ENTER);

        //just in case
        this.scrollertween.stop();
    }

});

What do we have above?
1) In the constructor, we declare a few objects to handle the background tile, a font for displaying our message, and our scroller.
Note : Concerning the font, if you check carefully the corresponding asset (32x32_font.png), you will notice that it only contains uppercase letters, so be sure as well to only use uppercase letter in your text.
2) In the reset function, we load the resources we need (image, font, etc..) and reset our scrolling text position, add a tween object for it (with a callback to reset the scrolling text), and bind the ENTER key. Additionally, I'm also playing the "cling" sound, when the title menu is loaded, because it's nice :)
3) In the update function, we check for user input (to press enter) and switch to the PLAY state if pressed.
4) In the draw function, we draw our background (using the drawImage function), draw both the static and scrolling text.
5) On destroy, we unbind the ENTER key, and stop the tween!
Easy, no?

And of course the very last thing is to indicate to the engine we created a new object and associate it to the corresponding state (here, MENU). Also, using the transition function of me.state, I'm telling the engine to add a fading effect between state changes.
Finally, instead of switching to the PLAY state at the end of the loaded function, I'm switching now to the MENU state:

/* ---

    callback when everything is loaded

    --- */
loaded: function() {
    // set the "Play/Ingame" Screen Object
    me.state.set(me.state.MENU, new TitleScreen());

    // set the "Play/Ingame" Screen Object
    me.state.set(me.state.PLAY, new PlayScreen());

    // set a global fading transition for the screen
    me.state.transition("fade", "#FFFFFF", 250);

    // add our player entity in the entity pool
    me.entityPool.add("mainPlayer", PlayerEntity);
    me.entityPool.add("CoinEntity", CoinEntity);
    me.entityPool.add("EnemyEntity", EnemyEntity);

    // enable the keyboard
    me.input.bindKey(me.input.KEY.LEFT, "left");
    me.input.bindKey(me.input.KEY.RIGHT, "right");
    me.input.bindKey(me.input.KEY.X, "jump", true);

    // display the menu title
    me.state.change(me.state.MENU);
}

.

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.