Mozilla Labs has announced Game On 2010, our first-ever game development contest.
My first example is a set of classes for implementing a top-down, tile-based map screen that scrolls to follow the player’s movement. If you, like me, wasted your youth playing 8-bit and 16-bit console RPGs (like Dragon Warrior, Ultima, and my favorite under-appreciated classic: Phantasy Star) then this ought to look a bit familiar:
Move the character around with the arrow keys, and notice how the screen scrolls when you get close to the edge.
The principle here is very simple. We’re not even using <canvas>; the map is simply a table filled with images. The character image is a transparent .png file which is superimposed on the table using a <div> styled with position: absolute and z-index: 1.
The basic concept for any kind of scrolling background is that you essentially have two different Cartesian coordinate systems: screen coordinates, and world coordinates. The world coordinates describe where something “really” is; the core game logic deals entirely in world coordinates. When it’s time to draw to the screen, the world coordinates are transformed into screen coordinates used for drawing. (In a three-dimensional game, the transformation can be quite complex, but in a two-dimensional one, “transform” really just means “offset”.)
The heart of my example code is the WorldMap class. WorldMap._scrollX and WorldMap._scrollY variables track the offset of the current view relative to the world coordinates. _scrollX and _scrollY are used in the WorldMap.redraw() method to offset the tiles being drawn. The WorldMap.scroll() method sets the values of _scrollX and _scrollY, ensures they stay within reasonable bounds, and calls WorldMap.redraw() whenever they change. Finally, the WorldMap.autoScrollToPlayer() calculates how much to scroll and in which direction in order to keep the “camera” pointed at the moving player character.
The WorldMap class and its associated helper classes are in the file rpg.js. The file bindings.js contains the key bindings and hard-coded map data, and provides an example of how client code uses the classes defined in rpg.js. Finally, the source code of rpg.html provides the <table> and the CSS styles which are needed for rpg.js to function correctly.
I am releasing the code in rpg.js under an MPL license (Edited to add: MPL / GPL / LGPL Tri-License), so you are free to take it, modify it, and use it for your Game On submission or for any other purpose.
Of course, you’re going to want to replace my crummy tileset images with your own (I put approximately zero effort into this tileset; it’s just there as a proof of concept). Designing tiles so that their edges look nice when lined up together is an art form in itself.
Beyond replacing the tileset, here are some ideas for building on this code:
- Change the character image based on the direction of motion, to show the character facing the way they move.
- Add a walking animation and/or an idle animation for the character.
- Instead of a hard-coded array of map square images, how about a procedurally generated map? Use the algorithm of your choice to decide on the elevation of each square, and then add some logic to MapSquare.pickImageForTerrain() to pick an appropriate image for each square based on its elevation and those of its neighbors.
- Use Math.random in the MapSquare.onEnter() method to have a random chance of encountering a monster for each step you take (implementing the “Battle Mode” is way beyond the scope of this example).
- Add “encounter” data to each MapSquare so that different types of terrain have different encounter frequencies and different types of monsters.
- Alter the MapSquare.passable() to make some types of squares conditionally passable – for instance, ocean squares passable only if the character is in a boat, or one-way doors that are passable only if entered from a certain direction.
- Make something happen when the player clicks on certain squares. This requires adding an onMouseUp handler to the table; and to figure out which map square was clicked, you’ll need a reverse transformation function that turns the click location (in pixels) into a map location (in squares) by using the current scroll values.
- Draw multiple characters on the map at once, using multiple instances of the sprite class. These could include independent characters that move about on their own, or allies that follow the main character around the map. (Make sure not to call autoScrollToPlayer() in response to the movement of non-player characters.)
- Although this feature is not used in the demo, the WorldMap class can contain multiple subMaps. Additional subMaps can be used to create the insides of towns, caves, castles, etc. etc. You just need to call addSubMap to create each new submap. Then when the player switches submaps, call WorldMap.goToMap(). You might do this in response to the player stepping onto a particular square representing a door, dungeon entrance, etc. perhaps represented by a subclass of MapSquare.
I hope this is helpful, and I can’t wait to see what you might do with it.
I have several other code examples that I’ll be sharing over the next few weeks, including a class for interpreting multi-touch input. Stay tuned!