DHTML 2-D Scrolling Engine
Jun 27th, 2007 by gatoloc0
Hi. Here we are with the very first Primera tutorial.
As you can imagine this is about DHTML game-like scrolling.
After reading this article, you should be able to code your own scrolling engine in DHTML.
For this writing I’ll be using some sprites from Lost Labyrinth (Hello Markus!). If you already know all about 2D platform games and the like and are looking for some action (!) just jump to First draft. Otherwise you can continue reading…
Intro
Scrolling is one of the most important aspects on 2D games. Since its first appearance on 1981 under the form of an arcade platform title called Jump Bug it greatly evolved following the 8-bit and 16-bit game era. Its decline started on the 90’s has soon as the first 3D (an pseudo 3D) games reached the game scene.
Some concepts
In computer terms, Scrolling is the operation a program needs to accomplish every time a certain object, that is holding some kind of information, becomes too small to contain and display all the available content.
We can call that object a Viewport and you can think of it as a window in your house that is doing what every window does, and that is showing you just a portion of the entire landscape you’ll find outside.
In computer games, a landscape is usually called a Map and is often associated to a specific Game level.
So let’s assume we need to display the first level of our 2D game.
To represent every different object on our map we’ll need to draw various pictures.
To simplify this operation we’ll set standard dimensions to every object we draw. By doing this we are creating a Map grid.
Every single picture of this grid it’s called a Tile and the Tile size defines the detail (distance between vertical and horizontal lines) of our grid.
Here’s an image to resume this concepts:

- A very simple map of 7×7 tiles is displayed on a Viewport capable of showing 4×4 tiles.
- Every tile on the map has width=32 px and height=32 px.
- The couple of numbers bellow Current view represents the coordinates defining the current area of the map we are showing on the viewport. (from x=4, y=3 to x=7, y=6).
- In same manner 2,7 below the Tile title identifies that tile unequivocally.
Now I will try to translate all of this into some javascript code.
First draft
Map
As a first thing we are going to translate our objects into code so let’s start with the map.
A computer programmer will instantly associate the map to a Matrix. This is easy since we could easily access any of the map tiles just by doing map[x,y].
I agree with you, this is nice, but we are not going to use this method because matrices (or two-dimensional arrays) are not directly available in javascript and handling them requires a bit of effort. So we are using a vector instead. Specially since accessing map[x][y] is a little slower than map[z].
So here’s an example of a 20×18 vector map :
var map=[ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 3,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3, 3,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,5,0,3, 3,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 ];
Every different number represents a different tile. Easy, isn’t it?
Now maybe you are asking “How am I able to access the x,y tile?”.
Very easy, the answer is map[x+y*20] or, more generally, map[x+y*MAP_WIDTH].
Ok, the map is done. Now for the viewport.
Viewport
Our Viewport will be 10×10 tiles. Our tile size will be 32×32 pixels.
The question is: How is the best way to build our viewport?
An experienced html coder could think:
Build a grid of 10×10 IMGs then change the .src property of every image to represent the different tiles.
OK, right. this works, but it is slow, really slow, and we don’t want people laughing at our scrolling engine.
My method is a bit different. Here’s the recipe:

- Place all the tiles in a single image like the one on the left.
- obtain all tile coordinates using the top left corner of the image as the origin (0,0).
e.g.
the metal door starts at x=46, y=177 - Build a grid of absolute positioned DIVs.
All divs will be the same size being equivalent to the predefined tile-size (32×32 pixels). - Using the background property, assign the image as a background to every div using a negative offset so we are able to specify the proper tile.
Now I’ll try to be more specific about the second point.
Here’s another picture.

- Note that every arrow points to a tile origin. As I said before you must consider the picture’s top left corner as the origin.
- Every x,y couple will be equivalent to the horizontal and vertical distance of every tile-block from the origin.
- If you use Photoshop you can easily obtain every offset just using the Slice function
- For Primera sprites I actually do not calculate every offset.. I’ve made a javascript routine that extracts offsets automatically.. I will talk about this in a future tutorial.
Ok. Now that we have every tile coordinate, we only need to assign the main image as a background and set the proper offset.
Here’s an example
This code…
<div style="display:block;width:32px;height:32px; background:url(/wordpress/wp-content/uploads/2007/07/glo2.png) -46px -177px no-repeat"> </div>
is equivalent to:
Metal door
While…
<div style="display:block;width:32px;height:32px; background:url(/wordpress/wp-content/uploads/2007/07/glo2.png) -139px -236px no-repeat"> </div>
is equivalent to:
Stairs
As you can notice, the background image remains unchanged. All we have to do is change the image offset to switch the tiles.
Other objects
Now that you got the main idea we only need to define a few objects.
I’ll use a very simple JS object called gfzSprite to store every tile. Here’s the code:
var gfzSprite=function(sId,bx,by,bw,bh) { //define the gfzSprite Object by passing: ID, offsetX, offsetY, Width, Height this.constructor.all[sId]=this; //the All collection (an associative array or dictionary of gfzSprites) this.width=bw; //store sprite width this.height=bh; //store sprite height this.background='transparent url('+this.constructor.imageFile+') -'+bx+'px -'+by+'px no-repeat'; //store the background style } gfzSprite.imageFile=''; //global background property gfzSprite.all=[]; //initialize all property
An here’s how we’ll use this object (note the specified coordinates being the same as the above picture):
gfzSprite.imageFile='glo2.png'; //set the image we'll be using as background for every tile new gfzSprite('glo2_wall01',107,76,32,32); //just create a tile named 'glo2_wall01' using the specified coordinates new gfzSprite('glo2_terrain01',139,108,32,32); new gfzSprite('glo2_wall02',14,143,32,32); new gfzSprite('glo2_terrain02',46,143,32,32); new gfzSprite('glo2_door01',46,177,32,32); new gfzSprite('glo2_stair01',139,236,32,32); //we'll be able to reference our tiles later using gfzSprite.all[SPRITE_ID]
Now we need a simple keystroke parser so we are able to scroll the map using just the keyboard. Here’s a simple object:
var key=function() { var LEFT=37; var UP=38; var RIGHT=39; var DOWN=40; return { capture:function(e) { switch(e.keyCode) { case LEFT : MOVE_LEFT ;break; case RIGHT : MOVE_RIGHT;break; case UP : MOVE_UP ;break; case DOWN : MOVE_DOWN ;break; } } } }()
We can start capturing keystrokes just by doing:
window.onkeydown=function(e){key.capture(e);return false;}
Next, we need to associate every sprite tile to a specific value on our map. Here’s how:
/* We just need to define an array of gfzSprite IDs. */ // 0 1 2 3 var spr=['glo2_wall01','glo2_terrain01','glo2_stair01','glo2_wall02', // 4 5 'glo2_terrain02','glo2_door01']; /* Just take another look to our map: Every different number on the map array will be associated to a different background offset. Now is easy to understand that the first row means "build a row of 'glo2_wall02' tiles" */ var map=[ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 3,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3, ...
This is all we need. Now we are ready to put all things together.
Code example I
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script> /***************************************************************** ******** gfzSprite OBJECT *****************************************************************/ var gfzSprite=function(sId,bx,by,bw,bh) { this.constructor.all[sId]=this; //all collection this.width=bw; this.height=bh; this.background='transparent url('+this.constructor.imageFile+') -'+bx+'px -'+by+'px no-repeat'; } gfzSprite.imageFile=''; gfzSprite.all=[]; /***************************************************************** ******** MAP OBJECT *****************************************************************/ var map=function() { // 0 1 2 3 var spr=['glo2_wall01','glo2_terrain01','glo2_stair01','glo2_wall02', // 4 5 'glo2_terrain02','glo2_door01']; var dat=[ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 3,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3, 3,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,5,0,3, 3,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,3, 3,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,3, 3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 ]; var viewport=[]; //the viewport is defined as a simple array of absolute positioned DIVs //constants init --> var WIDTH=20; //a constant value indicating the MAX_WIDTH of our map (number of tiles) var HEIGHT=dat.length/WIDTH; //we just calculate the height var VIEWPORT_WIDTH=10; //the width of our viewport is just the number of horizontal tiles var VIEWPORT_HEIGHT=10;//viewport height var TILE_WIDTH=32; //tile width and height in pixels var TILE_HEIGHT=32; //<-- constants end return { //public properties init --> limitx:WIDTH-VIEWPORT_WIDTH, //the horizontal scrolling limit value limity:HEIGHT-VIEWPORT_HEIGHT,//the vertical scrolling limit value x:0, //viewport's current x position y:0, //viewport's current y position //<-- public properties end //public methods init --> createViewPort: function(sParent) { /* create the viewport as a grid of absolute positioned divs store every div into the viewport array every div is created as a child of the specified sParent object */ var parent=document.getElementById(sParent); var ix=0; var iy=0; for (var z=0;z<VIEWPORT_WIDTH;z++) { for (var y=0;y<VIEWPORT_HEIGHT;y++) { var d=document.createElement('div'); var ds=d.style; ds.position='absolute'; ds.left=ix+'px'; ds.top=iy+'px'; ds.display='block'; ds.width=TILE_WIDTH+'px'; ds.height=TILE_HEIGHT+'px'; parent.appendChild(d); ix+=TILE_WIDTH; viewport.push(d); } ix=0; iy+=TILE_HEIGHT; } }, paint: function(oriX,oriY) { /* just paint the viewport at specified oriX, oriY coordinates */ var idx=0; var yEnd=oriY+VIEWPORT_HEIGHT; var xEnd=oriX+VIEWPORT_WIDTH; for (var y=oriY;y<yEnd;y++) { for (var x=oriX;x<xEnd;x++) { var b=dat[x+y*WIDTH]; viewport[idx].style.background=gfzSprite.all[spr[b]].background; idx++; } } } //<-- public methods end } }() /***************************************************************** ******** KEY OBJECT *****************************************************************/ var key=function() { var LEFT=37; var UP=38; var RIGHT=39; var DOWN=40; return { capture:function(e) { switch(e.keyCode) { /* Just increment or decrement current viewport x,y coordinates basing on the pressed key. */ case LEFT :if (map.x){map.x--;map.paint(map.x,map.y)};break; case RIGHT :if (map.x<map.limitx){map.x++;map.paint(map.x,map.y)};break; case UP :if (map.y){map.y--;map.paint(map.x,map.y)};break; case DOWN :if (map.y<map.limity){map.y++;map.paint(map.x,map.y)};break; } } } }() function init() { gfzSprite.imageFile='glo2.png'; new gfzSprite('glo2_wall01',107,76,32,32); new gfzSprite('glo2_terrain01',139,108,32,32); new gfzSprite('glo2_wall02',14,143,32,32); new gfzSprite('glo2_terrain02',46,143,32,32); new gfzSprite('glo2_door01',46,177,32,32); new gfzSprite('glo2_stair01',139,236,32,32); map.createViewPort('mappa'); map.paint(0,0); window.onkeydown=function(e){key.capture(e);return false;} } window.onload=init; </script> </head> <body> Use cursor keys to scroll the map. <div id="mappa" style="position:absolute;left:10px;top:40px;"></div> </body> </html>
Use the provided “RunMe” button to see the code in action. There’s also a 7zip version available ready for download.
OK, that’s all for now. I hope you got the idea. If there’s something you want to know about the code just tell me.
The next tutorial will be about smooth scrolling.
Ciao!
That is amazing. Great work.
Now can you (please) post a tutorial on making elements clickable (ie. clicking an enemy or friendly unit), select attacks and the actual attack combat code?
Whoa. Thank you
Your request is really interesting.
I will try to add what you are asking on my next tutorial. I’ll do my best to release it ASAP.
Bye. gato.
Very nice