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
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:
- maze.html is a game view, run this to see the maze in action
- 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.
- The maze screen is created by the class
maze.Level_1
. All you have to do is populate the arraythis.rooms
, in the constructor, withmaze.Room
objects. - The rest will be done by it’s parent class
maze.Level
. Based on the list of rooms, in the arraythis.rooms
, it will create a maze and set up navigation events/controls.
That’s it.
Let’s go deeper
maze.js
maze.start = function() { // create game director to manage screens var director = new lime.Director(document.getElementById('maze'), maze.size.width, maze.size.height); // create maze first level var level = new maze.Level_1(); // in constructor you set up the maze rooms, see maze.Level_1 class level.create(); // we start the engine to build the maze director.replaceScene(level); // show it! director.setDisplayFPS(false); }
Nothing special here, just create and show the maze.
maze.Level_1.js
/** * @constructor * @extends {maze.Level} */ maze.Level_1 = function() { goog.base(this); this.rooms = [ new maze.Room([maze.direction.UP, maze.direction.RIGHT], [0, 0]), new maze.Room([maze.direction.RIGHT, maze.direction.LEFT], [0, 1]), new maze.Room([maze.direction.LEFT], [0, 2]), new maze.Room([maze.direction.UP, maze.direction.DOWN], [1, 0]), new maze.Room([maze.direction.RIGHT, maze.direction.DOWN], [2, 0]), new maze.Room([maze.direction.UP, maze.direction.LEFT], [2, 1]), new maze.Room([maze.direction.RIGHT, maze.direction.DOWN], [3, 1]), new maze.Room([maze.direction.UP, maze.direction.LEFT], [3, 2]), new maze.Room([maze.direction.UP, maze.direction.DOWN], [4, 2]), new maze.Room([maze.direction.RIGHT], [5, 0]), new maze.Room([maze.direction.RIGHT, maze.direction.LEFT], [5, 1]), new maze.Room([maze.direction.DOWN, maze.direction.LEFT], [5, 2]) ]; }
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
/** * Constants for direction * @enum {string} */ maze.direction = { UP: 'up', RIGHT: 'right', DOWN: 'down', LEFT: 'left' }
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:
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:
/** * Create maze. */ maze.Level.prototype.create = function() { /** @type {maze.Room} */ var room; for (var i = 0; i < this.rooms.length; i++) { room = this.rooms[i]; // set room image and move room outside of the screen room.setFill(room.getImage()).setPosition(-1000, 0).setSize(480, 320).setAnchorPoint(0, 0); if (i == 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, i)); this.appendChild(room); } // add click/touch navigation this.setTouchNav(); }
This is the place where maze is created. It loops through all rooms and add them to the screen.
/** * A flag which tells whether room animation is in progress. * We need this to NOT interrupt moving to another room. * @type {bool} */ maze.Level.moving = false; /** * Move to another room based on direction. * @param {maze.direction} direction */ maze.Level.prototype.move = function(direction) { // check if room has this direction and if animation is in progress if (goog.array.contains(maze.Level.currentRoom.directions, direction) === false || maze.Level.moving === true) { return; } var nextRoom = maze.Level.currentRoom.neighbours[direction]; /** @type {goog.math.Coordinate} */ var coordinate; // based on direction, set next room starting position and coordinates where should animation end switch(direction) { case maze.direction.LEFT: nextRoom.setPosition(-maze.size.width, 0); coordinate = new goog.math.Coordinate(maze.size.width, 0); break; case maze.direction.RIGHT: nextRoom.setPosition(maze.size.width, 0); coordinate = new goog.math.Coordinate(-maze.size.width, 0); break; case maze.direction.UP: nextRoom.setPosition(0, -maze.size.height); coordinate = new goog.math.Coordinate(0, maze.size.height); break; case maze.direction.DOWN: nextRoom.setPosition(0, maze.size.height); coordinate = new goog.math.Coordinate(0, -maze.size.height); break; } // move both, next and current, rooms // we can see current room is going away and next room is coming var moveAction = new lime.animation.MoveBy(coordinate).setDuration(0.5); moveAction.addTarget(maze.Level.currentRoom); moveAction.addTarget(nextRoom); moveAction.play(); // we don't want to interrupt current animation maze.Level.moving = true; // set event to know when animation is finished // so we can set next room to current and maze.Level.moving that animation is finished goog.events.listen( moveAction, lime.animation.Event.STOP, function(){ maze.Level.moving = false; maze.Level.currentRoom = nextRoom; }); }
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:
- Create a Mobile HTML5 Farming Game
- Create a Virtual Pet Game with HTML5
- Create a Mobile HTML5 RPG for Beginners