Click here to Skip to main content
Click here to Skip to main content

Game Development Using JavaScript

By , 19 May 2013
Rate this:
Please Sign up or sign in to vote.

Introduction

For me developing games is a hobby and maybe a little more, developing games can become a complex task, as you trying to make your game more and more attractive and with a lot of capabilities. Games can be developed in any language known to man (of course some languages will make the development process much harder.) The bottom line is - Game development doesn't have to be complex or difficult you can do it very easily.

Background

There is not study proves this, but JavaScript is probably the most common language around the world, of course because the Internet and the progress of HTML 5 that supports tablets and mobile devices.

So this is the reason I choose to post this article explaining how to develop games using HTML 5 and JavaScript.

Before getting started let's talk about the basics of Game Development:

The Game Loop

One of the most important elements in developing games is called Game Loop, the game loop is responsible for establishing the game by drawing the graphics of the game. Game loop allows the game to continue to progress without making new actions from the user.

So how to create a game loop? The example below is the first thing that many developers think of right way to create a background process but is probably not the most effective:

while (!MyGame.stopped) { 
  MyGame.update(); 
} 

One of the main things that the game loop does is calling the update function, that responsible for updating the game (graphics, objects position etc).

If we'll use While statement for calling the update function the browser will probably block the function and beside that we'll not be very efficient because we cannot control how often the update function will be called, in other words we cannot control the FPS rate.

Another solution for creating Game Loop is using setInterval function, than very easily we can control when the update function will be called:

MyGame.fps = 60;
MyGame.update = function () {
    // Move game parts
};
// Start the game loop
MyGame._intervalId = setInterval(Game.update, 1000 / Game.fps);
// To stop the game, use the following:
clearInterval(MyGame._intervalId); 

When I wrote FPS i didn't talk about - First-person shooter, but Frames per second - Frame rate (also known as frame frequency) is the frequency (rate) at which an imaging device produces unique consecutive images called frames.
The human eye and its brain can process 10 to 12 separate images per second, perceiving them individually, so in order to provide the Animation effect (Games, Movies etc), the FPS should be higher.

Part 1 - Getting Started With EASEJS

Although Game Loop creation looks simple I'm not going to build it for my game, I'm going to use EASEJS library that will provide me a Game Loop and some other features that will help me building my game.

EaselJS provides straight forward solutions for working with rich graphics and interactivity with HTML5 Canvas. It provides an API that is familiar to Flash developers, but embraces Javascript sensibilities. It consists of a full, hierarchical display list, a core interaction model, and helper classes to make working with Canvas much easier.

The canvas element is part of HTML5 and allows for dynamic, scriptable rendering of 2D shapes and bitmap images. It is a low level, procedural model that updates a bitmap and does not have a built-in scene graph.

<script type="text/javascript" src="js/easeljs/geom/Matrix2D.js"></script>
<script type="text/javascript" src="js/easeljs/geom/Rectangle.js"></script>
<script type="text/javascript" src="js/easeljs/events/EventDispatcher.js"></script>
<script type="text/javascript" src="js/easeljs/utils/UID.js"></script>
<script type="text/javascript" src="js/easeljs/utils/Ticker.js"></script>
<script type="text/javascript" src="js/easeljs/utils/SpriteSheetUtils.js"></script>
<script type="text/javascript" src="js/easeljs/display/DisplayObject.js"></script>
<script type="text/javascript" src="js/easeljs/display/Container.js"></script>
<script type="text/javascript" src="js/easeljs/display/Stage.js"></script>
<script type="text/javascript" src="js/easeljs/display/SpriteSheet.js"></script>
<script type="text/javascript" src="js/easeljs/display/BitmapAnimation.js"></script>
<script type="text/javascript" src="js/easeljs/display/Graphics.js"></script>
<script type="text/javascript" src="js/easeljs/display/Bitmap.js"></script>
<script type="text/javascript" src="js/easeljs/display/Text.js"></script> <span style="font-size: 9pt;"> </span> 

Add the Canvas element inside our Body section, the Canvas will be the game container.

<canvas id="gameCanvas" width="600" height="100"></canvas> 

Now let's start working with EASEJS, first thing we need to do is creating new object of type - Stage.

A stage is the root level Container for a display list. Each time its Stage/tick method is called, it will render its display list to its target canvas.

stage = new createjs.Stage(id("gameCanvas")); 

After we initialize the Stage object we can start adding elements to our game, on of the ways to do that is creating new Bitmap object (Also part of EASEJS) that receives the image path, then adding the new Bitmap object inside the Stage using addChild function. When all the games elements were added to the Stage object we need to start the game loop by calling createjs.Ticker.setFPS(30); and finally register to "tick" event (once the tick event is invoked it's time to update the game...)

The Ticker provides a centralized tick or heartbeat broadcast at a set interval. Listeners can subscribe to the tick event to be notified when a set time interval has elapsed.

Note that the interval that the tick event is called is a target interval, and may be broadcast at a slower interval during times of high CPU load. The Ticker class uses a static interface (ex. Ticker.getPaused()) and should not be instantiated.

var stage, image, fpstext;
function init() {
    stage = new createjs.Stage(id("gameCanvas"));
    image = new createjs.Bitmap("assets/hill.png");
    stage.addChild(image);
    createjs.Ticker.setFPS(30);
    createjs.Ticker.addEventListener("tick", tick);
} 

Now we need to implement the tick function, for this demo we'll move the image 10 pixels to the right, once the image reach to 600 pixels we'll return the image to start position (x= 0), after we change the image properties we need to call the stage.update() function to refresh the canvas.

function tick() {
    image.x += 10;
    if (image.x > 600)
        image.x = 0;
    stage.update();
} 

Before we see the result let's add one more element to our stage, a Text element. I want to display the FPS rate on the Stage, inside the Init function add the following code to add Text element inside the Stage:

fpstext = new createjs.Text("fps:", "20px Arial", "#7a1567");
fpstext.x = 0;
fpstext.y = 20;
stage.addChild(fpstext); 

To add the current FPS rate add the following code inside the tick function :

 fpstext.text = Math.round(createjs.Ticker.getMeasuredFPS()) + " fps"; 

See Demo 

Part 2 - Sprites

In part 1 we understand the basics of EASEJS, from adding elements to the Stage and how to move those elements, in this part we are going to use Sprite to display our player object is different modes. (Jump, Run, Gam Gam Style etc.).

Sprites are 2D bitmaps that are drawn directly to a render target without using the pipeline for transformations, lighting or effects. Sprites are commonly used to display information such as health bars, number of lives, or text such as scores.

Sprite is one image that contains several images inside it and we can display any image from the sprite by specifying coordinates of that image.

First, let's start with loading the Player Sprite into our game,  it's important to make sure the image is loaded before starting the game loop, this is way I register the onload event and once the load event is invoked I'll can the start function. 

var stage, player, playerImage, _action;
var playerWH = 64;
var frequency = 4;
function init() {
    playerImage = new Image();
    playerImage.src = "assets/Player.png";
    playerImage.onload = start;
} 

In part 1 we used Bitmap object to display an image inside the stage, because we're working with Sprite we need to use a different object that knows how to handle Sprite images. 

SpriteSheet -  Encapsulates the properties and methods associated with a sprite sheet. A sprite sheet is a series of images (usually animation frames) combined into a larger image (or images). For example, an animation consisting of eight 100x100 images could be combined into a single 400x200 sprite sheet (4 frames across by 2 high). 

After creating the Stage object we'll create the playerSprite that represent a SpriteSheet object, The data passed to the SpriteSheet constructor defines three critical pieces of information:

  1. The image or images to use.
  2. The positions of individual image frames. This data can be represented in one of two ways: As a regular grid of sequential, equal-sized frames, or as individually defined, variable sized frames arranged in an irregular (non-sequential) fashion.
  3. Likewise, animations can be represented in two ways: As a series of sequential frames, defined by a start and end frame [0,3], or as a list of frames [0,1,2,3]. 

function start() {
    stage = new createjs.Stage(id("gameCanvas"));
    var playerSprite = new createjs.SpriteSheet({
        animations:
        {
            "walk": [0, 9, null],
            "fall": [10, 21, null],
            "jump": [22, 32, null],
            "gamgam": [34, 64, null],
            "stand": [44, 44, null],
            "special_combo": [22, 32, "gamgam"]
        },
        images: [playerImage],
        frames:
            {
                height: playerWH,
                width: playerWH,
                regX: 0,
                regY: 0,
            } 
<span style="font-size: 9pt;">    });</span> 
<span style="font-size: 9pt;">}  </span><span style="font-size: 9pt;"> </span>

The above code defines the different modes of our player, Jump, Walk, Fall etc by specifying the image start id and the end id.  From the image below you can see the Walk action starts from image id = 0 to image id = 9. 

 After loading the sprite and define all the player actions we'll use the BitmapAnimation object to help us playing those images one after another to create the animation effect. 

BitmapAnimation - Displays frames or sequences of frames (ie. animations) from a sprite sheet image. A sprite sheet is a series of images (usually animation frames) combined into a single image. For example, an animation consisting of 8 100x100 images could be combined into a 400x200 sprite sheet (4 frames across by 2 high). You can display individual frames, play frames as an animation, and even sequence animations together. 

Inside the start function add the following code to play the Walk action.

player = new createjs.BitmapAnimation(playerSprite);
player.x = id("gameCanvas").width / 2;
player.y = id("gameCanvas").height - playerWH;
//player.currentFrame = 2;
player.gotoAndPlay("walk");
stage.addChild(player);
createjs.Ticker.setFPS(60);
createjs.Ticker.addEventListener("tick", tick); 

 Now you can see the player walking  or performing other actions on the screen, I've added the following code to allow FPS, Velocity and Frequency changes while the game is running.

switch (_id) {
    case "fps":
        createjs.Ticker.setFPS(parseInt(value));
        break;
    case "velocity":
        player.vX = parseInt(value);
        break;
    case "frequency":
        frequency = parseInt(value);
        start();
        break;
}  

See Demo 

Part 3 - Moving the Player  

Part 2 shows you how to use a Sprite to display our player in different modes (Jump, Walk, Fall etc), now we'll see how to move our player inside the game space.

In order to accomplished that we need to modify the tick function (out game loop update): 

if (player.x >= id("gameCanvas").width - playerWH) {
    // You reached the end -  We need to walk left
}
if (player.x < playerWH) {
    // You reached the end -  We need to walk right
} 

In the code above we define the game borders and if the user reach the right border of the game we want the player to go left and vice versa. 

To change the player direction (left or right) we'll use the direction property also part of the player object (BitmapAnimation) 

if (player.x >= id("gameCanvas").width - playerWH) {
    // You reached the end -  We need to walk left
    player.direction = -90;
}
if (player.x < playerWH) {
    // You reached the end -  We need to walk right
    player.direction = 90;
}  

After changing the player direction all we have to do is moving the player by changing the player x property.

player.direction == 90 ? player.x += player.vX : player.x -= player.vX; 

 The full tick function code: 

 function tick() {           
   if (player.x >= id("gameCanvas").width - playerWH) {
       // You reached the end -  We need to walk left
       player.direction = -90;
       player.gotoAndPlay("walk")
   }

   if (player.x < playerWH) {
       // You reached the end -  We need to walk right
       player.direction = 90;
       player.gotoAndPlay("walk_h");
   }

   // Moving the sprite based on the direction & the speed
   player.direction == 90 ? player.x += player.vX : player.x -= player.vX;
    
stage.update();
} 
Now we face a problem, even when the player moves to the right the image isn't flipped.

 

We can flip the image using CSS 3 scaleX but this will harm the game performance, EASEJS give a solution to our problem, we can use the  addFlippedFrames function, addFlippedFrames extends the existing sprite sheet by flipping the original frames horizontally, vertically, or both, and adding appropriate animation & frame data. The flipped animations will have a suffix added to their names (_h, _v, _hv as appropriate). Make sure the sprite sheet images are fully loaded before using this method.

so before creating the player object let's add the following line: 

//( spriteSheet  horizontal  vertical  both ) 
createjs.SpriteSheetUtils.addFlippedFrames(playerSprite, true, false, false); 

 The start function should look like that: 

function start() {
stage = new createjs.Stage(id("gameCanvas"));

var playerSprite = new createjs.SpriteSheet({
	animations:
	{
		"walk": [0, 9, null, frequency],
		"fall": [10, 21, null, frequency],
		"jump": [22, 32, null, frequency],
		"gamgam": [34, 64, null, frequency],
		"stand": [44, 44, null, frequency],
		"special_combo": [22, 32, "gamgam"]
	},
	images: [playerImage],
	frames:
		{
			height: playerWH,
			width: playerWH,
			regX: 0,
			regY: 0,
		}
});

createjs.SpriteSheetUtils.addFlippedFrames(playerSprite, true, false, false);

var fps = parseInt(id("fps").value);
var velocity = parseInt(id("velocity").value);

player = new createjs.BitmapAnimation(playerSprite);
player.x = id("gameCanvas").width / 2;
player.y = id("gameCanvas").height - playerWH;
player.vX = velocity;
_action = "walk";
player.gotoAndPlay("walk");

stage.addChild(player);

createjs.Ticker.setFPS(fps);
createjs.Ticker.useRAF = true;
createjs.Ticker.addEventListener("tick", tick);
}

After using the addFlippedFrame function each animation (action - Jump, Walk etc) will be duplicated to support the other view, the name of the flipped view will have _h at the end, so if you want to walk to the left using the flipped image you need to call the gotoAndPlay function passing "walk_h" action name.

function tick() {
    if (_action.indexOf("walk") != -1) {
        if (player.x >= id("gameCanvas").width - playerWH) {
            // You reached the end -  We need to walk left
            player.direction = -90;
            player.gotoAndPlay("walk")
        }
        if (player.x < playerWH) {
            // You reached the end -  We need to walk right
            player.direction = 90;
            player.gotoAndPlay("walk_h");
        }
        // Moving the sprite based on the direction & the speed
        player.direction == 90 ? player.x += player.vX : player.x -= player.vX;
    }
    stage.update();
}  

See Demo 

Part 4 -  Creating Game Environment

In part 3 we saw how to move the player inside the game space, flip player animation to support both vertical and horizontal views. 

In this post we going to talk about game "Atmosphere", but adding background and additional elements into the game, but not just static images but animated images that will create a live game atmosphere. 

Before we start designing our game let's talk for a sec on PreloadJS library, also belong to CreateJS (EaseJS also belong there.) 

PreloadJS provides a consistent way to preload content for use in HTML applications. Preloading can be done using HTML tags, as well as XHR. 

So let's add additional JavaScript file to our html page: 

<script type="text/javascript" src="js/preloadjs-0.3.0.min.js"></script>  

 Now, let's add additional function called - start - this function will be in charge of loading all game resources before we start the game: 

 function start() {           
    manifest = [
        { src: "assets/sky.png", id: "sky" },
        { src: "assets/ground.png", id: "ground" },
        { src: "assets/logo.png", id: "sun" },
        { src: "assets/hill.png", id: "hill" }
    ];
    loader = new createjs.LoadQueue(false);
    loader.onFileLoad = handleFileLoad;
    loader.onComplete = handleComplete;
    loader.loadManifest(manifest);
}  

 As you can see we define a manifest object (can be any name), inside that object we define all resources required for our game before we can start playing.

Once the download has finished the onComplete event will be invoked, and for each resource the onFileLoad event will invoked. 

The below code will add each resource to the assets array: 

var assets = [];
function handleFileLoad(event) {
    assets.push(event.item);
}  

Before we continue with implementing the graphics into the game let's see it:

As shown in the picture is a small part of the Ground - we'll see how easily we can duplicate it to create a continuous ground throughout the game.

Name 
Ground 
Hill 
Sun 
Sky  

After all resources were added to assets array we can start placing them inside the game.

We'll add new function called - handleComplete that will add each resource from the assets array, now it's important to understand the PreloadJS didn't just load those resources in the background but also catalog those resource based on their type and gave them a unique id. in order to retrive the object loaded from the PreloadJS library  we'll use the getResult function using the item unique id. 

Lets start with placing the ground image, the ground should be duplicate across all game width so it will look as continues floor. The first step is creating new Shape object with the ground image. From the code below you can see that w property of the Shape object is set to the game width, this will not stretch the image but duplicate it across the entire width. 

Finally we'll add the ground Shape into the stage. 

See Demo 4.1 

function handleComplete() {
    buildPlayerSprite();
    for (var i = 0; i < assets.length; i++) {
        var item = assets[i];
        var _id = item.id;
        var result = loader.getResult(_id);
        if (item.type == createjs.LoadQueue.IMAGE) {
            var bmp = new createjs.Bitmap(result);
        }
        switch (_id) {          
            case "ground":
                ground = new createjs.Shape();
                var g = ground.graphics;
                g.beginBitmapFill(result);
                g.drawRect(0, 0, w, 79);
                ground.y = h - 79;
                break;          
        }
    }
    stage.addChild(ground, player);
    player.gotoAndPlay("walk_h");
    
    var fieldValue = id("fps");
    var fps = parseInt(fieldValue.value);
    createjs.Ticker.setFPS(fps);
    createjs.Ticker.useRAF = true;
    createjs.Ticker.addEventListener("tick", tick);
}  

See Demo 4.1 

From the demo you can see the player is running on the continues ground, but we want to create moving effect so it will look like the ground is moving with the player. 

 To create that effect we need o update the tick function and change the ground x property. 

function tick() {
          var outside = w + 20;
        var position = player.x + player.vX;
        player.x = (position >= outside) ? -200 : position;
        ground.x = (ground.x - 10);
    }
    stage.update();
} 

See Demo 4.2 

 Now we can see the ground moving with the player but the ground is cut at the right side, because it's not long enough, so we need to increase the group Shape width. 

case "ground":
   ground = new createjs.Shape();
   var g = ground.graphics;
   g.beginBitmapFill(result);
   g.drawRect(0, 0, w+330, 79);
   ground.y = h - 79;
   break; 

 The code above shows that the ground Shape width is now equal to w+330, this is just part of the solution because the ground Shape will disappear from the screen.  To fully solve this we need to update the tick function  by adding modulo of the additional width we added.  

This will ensure that the ground will stay full within the game borders.  

ground.x = (ground.x - 10) % 330; 

See Demo 4.3 

No that we understand the basic example of how to add objects into the game stage and create effects and shapes manipulations we can add the rest of the game objects. 

* Note -  I've added the following event - stagemousedown - to know when the user clicked on the game stage. 

function handleComplete() {
    buildPlayerSprite();
    for (var i = 0; i < assets.length; i++) {
        var item = assets[i];
        var _id = item.id;
        var result = loader.getResult(_id);
        if (item.type == createjs.LoadQueue.IMAGE) {
            var bmp = new createjs.Bitmap(result);
        }
        switch (_id) {
            case "sky":
                var g = new createjs.Graphics()
                g.beginBitmapFill(result);
                g.drawRect(0, 0, w * 2, h)
                sky = new createjs.Shape(g);
                break;
            case "ground":
                ground = new createjs.Shape();
                var g = ground.graphics;
                g.beginBitmapFill(result);
                g.drawRect(0, 0, w + 330, 79);
                ground.y = h - 79;
                break;
            case "hill":
                hill = new createjs.Shape(new createjs.Graphics().beginBitmapFill(result).drawRect(0, 0, w, 159));
                hill.x = 0;
                hill.scaleX = 3;
                hill.y = 163;
                break;
            case "sun":
                var g = new createjs.Graphics();
                g.beginBitmapFill(result);
                g.drawRect(0, 0, 129, 129);
                sun = new createjs.Shape(g);
                sun.x = w;
                sun.y = 37;
                break;
        }
    }
    stage.addChild(sky, ground, hill, sun, player);
    player.gotoAndPlay("walk_h");
    stage.addEventListener("stagemousedown", function () {
        play("jump_h");
    });
    var fieldValue = id("fps");
    var fps = parseInt(fieldValue.value);
    createjs.Ticker.setFPS(fps);
    createjs.Ticker.useRAF = true;
    createjs.Ticker.addEventListener("tick", tick);
} 

 Finally as we did for the ground Shape we need to change the tick function to manipulate those objects.
 
 This time we add another parameter to calculate when an object is placed outside the game borders, once an object leave the game borders we can change his x property and return him back to the game.

 function tick() {
    if (_action.indexOf("walk") != -1 || _action.indexOf("jump") != -1) {
        var outside = w + 20;
        var position = player.x + player.vX;
        player.x = (position >= outside) ? -200 : position;
        sky.x = (sky.x - 5) % w;
        hill.x = (hill.x - 2) % w;
        ground.x = (ground.x - 10) % 330;
        sun.x = (sun.x - 1);
        if (sun.x <= -135) { sun.x = outside + 50; }
    }
    stage.update();
} 

See Demo  

Part 5 - Collision and Jump 

Now for the last part in this tutorial, Objects Collision.

For this game we want to create a basic logic for handling object collision -> If the player hit a rock object the game should stop and the player dies (not for real... Big Grin | <img src= " /> ).  

 

We'll add the rock object into our stage (Step 4) by adding the following code under handleComplete function: 

 

case "rock":
    var g = new createjs.Graphics()
    g.beginBitmapFill(result);
    g.drawRect(0, 0, 45, 44)
    rock = new createjs.Shape(g);
    rock.y = h - 119;
    rock.x = w;
    rock.height = 44;
    rock.width = 45;
    break;

 Now in order to show the rock inside the game add the following code:

 stage.addChild(sky, ground, hill, sun, player, rock); 

 Now that we have a rock object in our game we should allow the player to jump in order to avoid hitting the rock. To do that we need to register the stagemousedown event to catch mouse click event, once the event is invoked we'll play the Jump animation and change the isJumping object to true. 

We'll add the following code inside handleComplete function just before the setFPS function. 

 stage.addEventListener("stagemousedown", function () {
    if (isJumping) return;
    play("jump_h");
    gameOver = false;
    isJumping = true;
});  

The next step is to change the Y value once the player is jumping in order to jump above the rock, the following code isn't the best way to accomplish that but it's simple and fast for this tutorial.

The code is checking that our player isn't already in the air (to avoid double jump) and starts a timer that will elapsed after 1 second, once the timer has finished the lap he will return the player back to the ground  but not before he will increase the Y value  by 4 pixels, once the player is on the air and at maximum high the timer will decrease the Y value by  4 pixels. 

function handleJump() {
    if (isJumping) {
        if (onTheAir == null) {
            onTheAir = setTimeout(function () {
                isJumping = false;
                player.y = playerBaseY;
                onTheAir = null;
                goingDown = false;
                top = false;
            }, 1000);
        }
        if (goingDown && player.y <= playerBaseY) {
            player.y += 4;
        }
        else {
            player.y -= 4;
            if (player.y <= maxJumpHeight)
                goingDown = true;
        }
    }
}  

 To call this code let's add the following code under tick function: 

function tick() {
    handleJump();
    ...
}  

Now we can perform Jump by clicking left mouse button on the game.

Last thing is to apply objects collision so if the player will hit the rock we need to stop the game and play the fall animation, I suggest you to read about Object/Object Intersection to find and understand different ways you can do objects collision.

 function checkRectIntersection(r1, r2) {
    var deltax = r1.x - r2.x;
    var deltay = r1.y - r2.y;
    var dist = 25;
    if (Math.abs(deltax) < dist && Math.abs(deltay) < dist) {
        if (Math.sqrt(deltax * deltax + deltay * deltay) < dist) {
            return true;
        }
    }
    return false;
} 

Add the HandleCollision function that will be called from the tick function each time to check for collisions: 

function HandleCollisions() {
    var a = getCollideableItemBounds(player);
    var b = getCollideableItemBounds(rock);

    var oppss = checkRectIntersection(a, b);

    if (oppss && !gameOver) {
        console.log(oppss);
        gameOver = true;
        play("fall_h");
    }
} 

See Demo - Final  

Enjoy! 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Shai Raiten
Architect Sela
Israel Israel
Shai Raiten is VS ALM MVP, currently working for Sela Group as a ALM senior consultant and trainer specializes in Microsoft technologies especially Team System and .NET technology. He is currently consulting in various enterprises in Israel, planning and analysis Load and performance problems using Team System, building Team System customizations and adjusts ALM processes for enterprises. Shai is known as one of the top Team System experts in Israel. He conducts lectures and workshops for developers\QA and enterprises who want to specialize in Team System.
 
My Blog: http://blogs.microsoft.co.il/blogs/shair/
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberMihai MOGA13-Jun-13 20:54 
GeneralRe: My vote of 5 PinmvpShai Raiten26-Jun-13 19:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140421.2 | Last Updated 20 May 2013
Article Copyright 2013 by Shai Raiten
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid