Matthew Connelly's Developer Page


Game Development - Text-based Level Editing and Phaser JS

How I visually represent a level's layout through text (think ASCII art or Nethack) and use the Phaser JS game engine to render it to a canvas.


If you are familiar with roguelikes (turn-based dungeon crawlers) such as Nethack, then you are no doubt familiar with the idea of a game's elements being displayed entirely as ASCII characters.

Here I will explain how I use a similar approach to create the layout of rooms in a game I am working on and how I use Phaser (JS game engine) to render the rooms based on my layouts.

The following array of strings represents a single room:


   [
    //1         12             25
    'ppppppppppppppppppppppppp',//1
    'p------------------------',//2
    'p------------------------',//3
    'pppppppppp---------------',//4
    'p----------------E-------',//5
    'p--------------pppppppppp',//6
    'p------------------------',//7
    'p------E-E---------------',//8
    'pppppppppp---------------',//9
    'p------------------------',//10
    'p----------------E-E-E---',//11
    'p--------------pppppppppp',//12
    'p-----------------------p',//13
    'p-----------------------p',//14
    'p-----------ppppppppppppp',//15
    'p---------ppppppppppppppp',//16
    'p------pppppppppppppppppp',//17
    'ppppppppppppppppppppppppp',//18
    'ppppppppppppppppppppppppp' //19
    ]

"E" represents an enemy, and "p" represents an immovable block that can be used as a platform or wall. A dash represents empty space, simply because I find it easier to read than whitespace, although I am not exactly counting dashes to find any kind of distance.

The commented-out numbers on top and on the right represent columns and rows respectively; I have divided a canvas 800 pixels wide and 600 pixels high into 25-by-19 32-pixel squares.


How and when a room is rendered

  1. The player collides with the edge of the world (canvas)

  2. The player's x-position is then changed to the position of the edge opposite of the collision (right edge collision, player moved to far-left of canvas; left edge collision, player moved to far-right of canvas); this gives the appearance of entering a new room

  3. The room is then cleared, using Phaser's sprite.kill() method

  4. A variable integer is then increased or decreased by 1 depending on the edge of collision (right, +1; left, -1), and this integer is then used as our room's index in our level array.

  1. After getting the room we want by index, we check each string within the room array; each of these strings can be looked at as a row of things that can be rendered onto the canvas, from the top of the canvas to the bottom.

  2. For each row (string) in the room array, we check each character by its index using String.charAt().

  3. Using a switch statement, the character can be matched to a case; if and after being matched, we will call Phaser's group.create() method to render the matched sprite or image.

So using the room layout above, level1[0][8].charAt(4) would match a case of p in our switch statement:


The room-rendering function (abridged)

(To be called on collision.)

function renderRoom(room){
    for(var row in room){
        for(var column=0; column <=  room[row].length; column++){
            switch (room[row].charAt(column)){
                case 'p':
                    platform = platforms.create(column*32, row*32, 'platform-image');
                    break;
            }
        }
    }
}

This room-rendering function currently only works with one level but should scale to accommodate multiple levels and work similarly if not identically.

The room system works as detailed in pseudo-code below, with each "column" actually representing a single character in a string:

level1 [
    room1 [ 
        row1 'column1 column2 column3',
        row2 'column1 column2 column3',
        row3 'column1 column2 column3'
    ],
    room2 [ 
        row1 'column1 column2 column3',
        row2 'column1 column2 column3'
        row3 'column1 column2 column3'
    ]
]