October 2, 2014

Dungeon Drawing in Phaser using Tilemap

Introduction to Phaser


The Dungeon Generator creates perfecrtly logical maps. You could witness the perfection in the console by using Dungeon.print(). But, really… we can’t have a game on the console right?


That’s where Phaser comes in. Phaser is an HTML5 game enigine that’s based on webGL, with a neat fallback to HTML5 Canavs. It is super lightweight and mobile platform oriented. The best part? The massive store of documentation and examples. And, there a whole load of tutorials out there that would help a noob like me figure out how things are done. This was the first Phaser tutorial I took. You should really check out the examples to see what Phaser is capable of.



Source for this part is the same as last time.

SOURCE


Play





Dungeon Time


The map would have 32x32 tiles and hence, MAP_SIZE has been set to 32. We call our fancy Dungeon object that we had made last time. It prints the dungeon array onto the console.




var MAP_SIZE = 32;
Dungeon.generate(MAP_SIZE);
Dungeon.print();
var map = Dungeon.getMap();
var rooms = Dungeon.getRooms();
var tree = Dungeon.getTree();



Set Phaser on Stun


The index.html sets the playing field for Phaser.




<div style="margin-left:64px;" id="phaserCanvas"></div>

We kickstart Phaser in the main.js.




var game = new Phaser.Game(640, 384, Phaser.AUTO, 'phaserCanvas', { 
    preload: preload, 
    create: create,  
    update: update,
    render: render
});

function preload() {
}

function create() {
}

function update() {
}

function render() {
}

This will set the Phaser canvas to 640x384 pixels and renders it on the div phaserCanvas. These functions - preload(), create(), update() and render() form Game Loop. You preload the assets such as images and music, you then create all game objects such as the background, player and enemies. At every iteration of the Game Loop, you update the game objects, say, player movement or enemy logic. Finally you render the extra game objects onto the canvas, after Phaser is done drawing everything. Like this.


Created with Raphaƫl 2.1.0PhaserPreloadCreateUpdateRender

An awesome feature about Phaser is that it completely handles the rendering part. The game objects such as player sprite or background tiles are rendered on each iteration in the order in which they were created. Hence, the render() function serves to augment the canvas with objects that we hadn’t already created during the create() call. Neat, huh?




Load before firing


We fill up the preload() with relevant assets that we’d want to load.




function preload() {
    game.load.image('wall', '../../assets/wall.png');
    game.load.image('floor', '../../assets/floor.png');
    game.load.image('corridor', '../../assets/corridor.png');
    game.load.spritesheet('button', '../../assets/flixel-button.png', 80, 20);
}

The Phaser.Loader class functions are used to add the assets to the cache. The wall, floor and corridor tiles are simply flat colored 12x12 pixel sprites. I stole the flixel-button sprite from the Phaser examples.






Let there be Tiles


create() is where all the magic happens. Game objects are brought to life and given starting properties.


To draw our dungeon, I have used a Phaser.tilemap object. This comes with a lot of cool functions that I would otherwise have to write myself, if I had simply used regular Sprite objects.




    //  Creates a blank tilemap
    gmap = game.add.tilemap();

    //  Add a Tileset image to the map
    gmap.addTilesetImage('wall','wall',12,12,null,null,0);
    gmap.addTilesetImage('floor','floor',12,12,null,null,1);
    gmap.addTilesetImage('corridor','corridor',12,12,null,null,2);

Once gmap is created we assign the assets that we had preloaded to it. Look at Phaser.Tilemap.addTilesetImage:




addTilesetImage(tileset, key, tileWidth, tileHeight, tileMargin, tileSpacing, gid)

Since I am using individual tile sprites, rather than a sprite sheet, I need to manually assign the gid. Else, the gid gets reset and reconfigured each time. Using 0, 1 and 2 for wall, grass and corridor works default with out Dungeon.map.


The tilemap object, gmap is a logic object. We still need a Phaser.TilemapLayer which would be the graphic representation of the tilemap. A tilemap can have multiple layers, and we can assign unique tiles to any layer. In our case, we just need one.




    //  Creates a new blank layer and sets the map dimensions.
    layer0 = gmap.create('layer0',MAP_SIZE,MAP_SIZE,12,12);
    layer0.resizeWorld();

    stats = Dungeon.getStats();
    for (var i=0; i<MAP_SIZE; i++) {
        for(var j=0; j<MAP_SIZE; j++) {
            gmap.putTile(map[i][j],i,j,layer0);
        }
    }

And now, the fun part. We finally assign our map to the tilemap on the appropriate layer. Here’s what Phaser.putTile takes:




putTile(tile, x, y, layer)

Where, the first argument is the tile key that we had assigned during the addTilesetImage calls. Since, the map holds values 0,1 or 2, it fits along with keys that we had assigned. The x,y indicates the tile position in tile units (not pixels). The last argument adds our tile to our only layer.


I kinda just picked up the code by going over this Blank Tilemap example and the tilemap docs, and a bit of trial and error.


The last part is creating the buttons that would re-generate the map and toggle displaying the Boxes. Once, again Phaser’s Button Example gave me all that I needed. We also need a handle to the graphics object to draw the Boxes. Here’s the Phaser Display Graphics example. The buttons, when clicked on will call genMap() and toggleBox()




    game.add.button( 400, 256, 'button', genMap,this, 0, 1, 2);
    game.add.button( 400, 300, 'button', toggleBox,this, 0, 1, 2);
    marker = game.add.graphics();

If you run the game even with empty update() and render() functions, you’d see this:

enter image description here


That’s just how awesome and simple Phaser is. All you had to do was load a few assets and assign them to game objects, and lo, Phaser handles the rest.




Drawing Words


Honestly at this point, just with the preload() and create() functions, we are all set. The map would render onto the screen using the tile sprites we assigned. But, if I wanted to re-generate the map I would have no choice but to reload the page. That’s why we had added those buttons.


genMap() repopulates the map array with a newly generated dungeon and reassigns the tiles on tilemap object. toggleBox() toggles a logic variable that records whether the Boxes need to be drawn or not and calls drawBox(). The tree object has the information for the boxes, yes? So, we drawRect with those dimensions using the Phaser graphics object, marker.




function genMap() {
    console.clear();
    Dungeon.generate(MAP_SIZE);
    // Dungeon.print();
    map = Dungeon.getMap();
    stats = Dungeon.getStats();
    rooms = Dungeon.getRooms();

    for (var i=0; i<MAP_SIZE; i++) {
        for(var j=0; j<MAP_SIZE; j++) {
            gmap.putTile(map[i][j],i,j,layer0);
        }
    }

    drawBox();
}

function toggleBox() {
    state_toggleBox = state_toggleBox? 0: 1 ;
    drawBox();
}

function drawBox() {
    if(state_toggleBox){
        marker.clear();
        marker.lineStyle(4, 0x000000, 1);

        tree = Dungeon.getTree();
        for (var node in tree) {
            var t = tree[node];
            marker.drawRect(t.x*12, t.y*12, t.w*12, t.h*12);
        }
    } else {
        marker.clear();
    }
}

enter image description here


As a last feature, I wanted to display the sizes of each room at the center of the room. Used Phaser.debug.text. All of these instructions would go into the render() command. The stats would be drawn in the corner and button text would be drawn over the buttons.




function render() {
    game.debug.text("gen", 400+16, 256+12,'#F00');
    game.debug.text("box: "+state_toggleBox, 400+5, 300+14, '#000');

    var i=0;
    for (var key in stats) {
        game.debug.text(key+": "+stats[key], 400, 48+(i*16));
        i++;
    }

    for (var i=0; i<rooms.length; i++) {
        var r = rooms[i];
        game.debug.text(r.w+"x"+r.h, r.center.x*12, r.center.y*12);
    }
}

Adding simple text gave more depth to the whole thing. We are now Done!

enter image description here


Play it


Back to Tower of Fun Project