Wednesday, November 4, 2015

Building a Basic Platformer with Phaser

Like I said last week, I've started work on a new game for Amphibian.com. I'm once again using the Phaser HTML5 game framework, but this time I'm trying to make a classic platformer. Specifically, I want to make a game staring Science Frog in the style of NES Megaman.

While very similar to the approach I took creating Frog Soccer, this game will use very different mechanics. It needs to be side-scrolling, include jumping (and also falling), and have some sort of enemies that must be defeated.

To make this game, my first goal was to create a sandbox stage to test things out. I've been told that this is an important part of making any game. It lets you play around with the mechanics until you get the right behavior. After that, you can make the "real" levels.

My simple map in Tiled 

Dealing with the side-scrolling part was simple. In much the same way I created the soccer field for my last game, I used Tiled to make a map that was wider than it was tall. To get that 8-bit feel, I used a set of tiles from OpenGameArt.org. To start, I have a single layer named "layer1" and I called the tileset simply "ground." I saved the map in JSON format as "stage1.json" and added the tileset image, my frog spritesheet image, and the tilemap file to my pack.json file, shown here:

{
    "main": [
        {
            "type": "image",
            "key": "tiles",
            "url": "assets/images/nestle2.png"
        },
        {
            "type": "spritesheet",
            "key": "frog",
            "url": "assets/sprites/frog.png",
            "frameWidth": 79,
            "frameHeight": 60
        },
        {
            "type": "tilemap",
            "key": "stage1",
            "url": "assets/stage1.json",
            "data": null,
            "format": "TILED_JSON"
        }
    ]
}

Even though I only have 3 game assets at this point, putting them in an asset pack file now will save time later. In my game's preload function, I only need one line to load everything.

function preload() {

    game.load.pack("main", "assets/pack.json");

}

With all the assets loaded in preload, I can set up the game in the create function. Here's where things start to diverge from my last game. Here, I need to use gravity. The frog (and presumably other characters) should be pulled down to the bottom of the screen until they hit a platform or the ground. I want only gravity on the y-axis, so I set game.physics.arcade.gravity.y = 1500. Why 1500? I basically played around with it until I got the frog to fall as fast as I liked. I might change it later. The other thing here is that the frog has to stop falling when he lands on something - specifically one of the ground tiles. Phaser gives us an easy way to specify which tiles should have collision turned on. The map.setCollisionBetween function turns on collisions for all tiles with ids between two values. There is also a similar function that takes an array of ids instead of a range. In my example code below, I am basically turning on collision with all tiles. I might fine-tune this later.

function create() {

    game.physics.startSystem(Phaser.Physics.ARCADE);

    // set up gravity to pull everything down
    game.physics.arcade.gravity.y = 1500;

    game.stage.backgroundColor = "#D3EEFF";

    var map = game.add.tilemap("stage1");

    // associates "ground" from the tilemap with the "tiles" image
    map.addTilesetImage("ground", "tiles");

    // collide with everything for now
    map.setCollisionBetween(0, 6569);

    layer = map.createLayer("layer1");
    layer.resizeWorld();

    group = game.add.group();
    frog = createFrog(group, 50, 50, "frog", 200, "left");
    game.camera.follow(frog);

    cursors = game.input.keyboard.createCursorKeys();
    spacebar = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);

}

Just turning on collisions for tiles isn't quite enough. In the update function, I have to check for collisions between the frog and the map layer. Again, this is very simple. There's also a trick to make sure holding the jump button (the spacebar in my case) doesn't cause the frog to fly up forever. Phaser's arcade physics gives us a function, onFloor, which returns true if and only if the body is at rest on a platform. If that is true, I give the frog upward velocity. The gravity setting I discussed above takes care of bringing him back down.

function update() {

    game.physics.arcade.collide(frog, layer);

    frog.body.velocity.x = 0;

    if (spacebar.isDown) {
        if (frog.body.onFloor()) {
            frog.body.velocity.y = -600;
        }
    }

    if (cursors.left.isDown) {
        frog.body.velocity.x = -150;
    } else if (cursors.right.isDown) {
        frog.body.velocity.x = 150;
    }

}

And with those steps, I have the basic functions of an 8-bit style platformer. I have a lot more work to do, and I'll keep writing about my progress here. As always, the complete source code is available on GitHub. Check it out!

My first HTML5 platformer made with Phaser!

Also, check out today's comic for a deep conversation about the use of COTS in software development...or pictures of frogs falling asleep on the job.

Amphibian.com comic for 4 November 2015