html5 game tutorial

Create a Maze Engine in HTML5

In this tutorial, you will learn how to create a maze-like navigation system which you could apply to “point and click”, graphical adventures games, and more innovative genres as well. This system was used in my game Me Mnemonic, an HTML5 action memory game for Android, Firefox OS devices and web. At the end of this tutorial, you will be able to create any maze structure, with just few lines of codes, and navigate through it with keyboard arrows, mouse click or touch.

Requirements

You should be familiar with HTML, CSS, JavaScript and basic object oriented concepts. I will be using limeJS, which is the one most used game frameworks. Please check the documentation on their
web site and install it.

What to expect

Before moving on please check out the live demo in your browser to see what we are talking about. You can download the game files here. The root folder contains the non-compiled files, which you can open and read. You’ll need limeJS installed if you want to run them. There is also compiled version, in the “compiled” folder, which you can run standalone in your web browser (“compiled” in the limeJS lingo really means “minified” so that all the dependencies are in a single JavaScript file). Now we will browse through the files and I will explain how this all works.

How it works

Here is the list of files that we will be using:

  • direction.js
  • Level.js
  • Level_1.js
  • maze.css
  • maze.html
  • maze.js
  • Room.js

and a short explanation:

  1. maze.html is a game view, run this to see the maze in action
  2. maze.js is the starting point of the game, something like the main controller. There, we will create the Director, who’s job is to manage the game screens. It will load our maze screen.
  3. The maze screen is created by the class maze.Level_1. All you have to do is populate the array this.rooms, in the constructor, with maze.Room objects.
  4. The rest will be done by it’s parent class maze.Level. Based on the list of rooms, in the array this.rooms, it will create a maze and set up navigation events/controls.

That’s it.

Let’s go deeper

maze.js

Nothing special here, just create and show the maze.

maze.Level_1.js

This is the maze setup. The maze is a set of “rooms”, where we see only one at the time (see demo). Every maze.Room object has to know where is the exit (to up, right, down or left) to other rooms (first parameter array) and its position in the maze (second parameter array).

Room directions

For direction, we use enum maze.direction

In order for this to work, you have to use directions always in the same order: up, right, down or left. So [maze.direction.LEFT, maze.direction.DOWN] is not going to work, but [maze.direction.DOWN, maze.direction.LEFT] is OK. I like to use enums like this because then you make less errors while typing.

Rooms positions

Here is the maze map, the letter Z:

map

To create this structure in the code, we use an array with 2 dimensions for the room position – the first index is for row, and the second for column. It’s like we are building the table with rows and columns, from bottom to top:

  • [0, 0] means 1st row, 1st column,
  • [0, 1] means 1st row, 2nd column,
  • [0, 2] means 1st row, 3rd column,
  • [1, 0] means 2nd row, 1st column,

Directions and positions

Putting it all together:
new maze.Room([maze.direction.UP, maze.direction.RIGHT], [0, 0]) means that this room has 2 directions, to UP and RIGHT, and its position is 1st row, 1st column. This is the maze starting room, the bottom left room. By default, the starting room is always the first item in this.rooms array.

The engine

Class maze.Level will, based on it’s subclass maze.Level_1, create the maze. maze.Level_1 is just a setup or configuration, but maze.Level is the engine. Let see some of the methods:

This is the place where maze is created. It loops through all rooms and add them to the screen.

This method is responsible for the moving animation. Based on room neighbours, it will set next room and move it along with the current one, so we get a felling that we are moving through the maze.

Where to go now?

Now you can try to make your own maze. Create maze.Level_2 and populate this.rooms array with maze.Room. Ok, that’s fine, now we know how to create maze, but this is not a game yet. We are missing the game logic. Maybe in a future, we can add some bad guys, monsters or any other obstacle to beat and play.

If you have any questions, you can contact me on Twitter: @bmilakovic

Other LimeJS tutorials at the GameDev Academy:

Published by

Bosko Milakovic

Bosko is an indie HTML5 game developer, he created a game called Me Mnemonic, which you can download on Android, Firefox OS or just play on the web.

Share this article

  • as I get control was performed with the mouse?

  • Alex

    Hi, I’d like to point out a design flaw that I found while building my own maze-based game. The crux of it is that the two-dimensional maze in Level_1.js is expected to be constructed as a one-dimensional array. As such, the example maze you provided happens to work but adding more rooms and rooms with more branches will almost certainly cause problems.

    Since I needed a maze for my own project, I went ahead and fixed it. I would be great if you could update this article’s code to reflect the following fixes:

    1) In Level_1.js, construct this.rooms as a two-dimensional array:

    this.rooms = [
    [new maze.Room(…, [0, 0]),
    new maze.Room(…, [1, 0]),
    new maze.Room(…, [2, 0])],
    [new maze.Room(…, [0, 1]),
    new maze.Room(…, [1, 1]),
    new maze.Room(…, [2, 1])],
    //etc…
    ];

    The caveat here is that the maze must be built in order, row by row, and if you have an empty room you need to put an empty placeholder element into the above array.

    2) Once the maze is constructed properly, the “create” and “getNeighbours” functions need to be changed to accommodate the new array (apologies on behalf of Disqus’ horrible text formatting):

    //create method
    maze.Level.prototype.create = function() {
    /** @type {maze.Room} */
    var room;
    for (var row = 0; row < this.rooms.length; row++) {
    for (var col = 0; col < this.rooms[0].length; col++) {
    room = this.rooms[row][col];
    // set room image and move room outside of the screen
    room.setFill(room.getImage()).setPosition(-1000, 0).setSize(480, 320).setAnchorPoint(0, 0);
    if (row == 0 && col == 0) {
    // set first room as starting point
    room.setPosition(0, 0);
    maze.Level.currentRoom = room;
    }
    // set room neighbours, so we can know which room to show based on direction
    room.setNeighbours(this.getNeighbours(room, row, col));
    this.appendChild(room);
    }
    }
    // add click/touch navigation
    this.setTouchNav();
    }

    //getNeighbours method:
    maze.Level.prototype.getNeighbours = function (room, rowIndex, colIndex) {
    var row = rowIndex,
    col = colIndex,
    neighbors = {}, neighborPos;
    // loop through all room directions and find neighbours rooms
    for (var i = 0; i < room.directions.length; i++) {
    switch(room.directions[i]) {
    case maze.direction.RIGHT:
    neighborPos = [row, col + 1];
    break;
    case maze.direction.LEFT:
    neighborPos = [row, col – 1];
    break;
    case maze.direction.UP:
    neighborPos = [row + 1, col];
    break;
    case maze.direction.DOWN:
    neighborPos = [row – 1, col];
    break;
    }
    neighbors[room.directions[i]] = this.rooms[neighborPos[0]][neighborPos[1]];
    }
    return neighbors;
    }

    3) The new getNeighbours method renders the getNeighborIndexByRowCol irrelevant, so you can get rid of that as well

    And that's it! Thanks for the tutorial, and hopefully this fix helps someone else get going quicker than I did 🙂