Simple ping-pong game using HTML5 canvas

ping_pong

This game will not work on touch screen devices.

Click here to see the demo.


The index.php file

<!DOCTYPE html>
<html>
<head>
	<script type="text/javascript" src="js/ml.pingpong.stripped.js"></script>
	
<title>PingPong</title>
</head>

<body>

<canvas id="canvas" style="border:1px solid #ddd; background:#eee;"></canvas>

<script type="text/javascript">
	var me = setInterval(function() {
		if(document.readyState === 'complete') {
			clearInterval(me);
			mlPingPong.ball();
		}
	});
</script>

</body>
</html>

Important things to note are the link to the .js file in the head and the canvas element and .js script in the body.
The code within the script tags checks if the DOM is ready using the setInterval function and if it is it clears the Interval and runs the ball function in the mlPingPong object.

The javascript object

To start we create our object which will hold the main function.

var mlPingPong = { /* All the code goes here */ }

Then we make our function which includes all the code for the game…

var mlPingPong = { 
       ball : function() { /* Code below goes here */ }
}

We call this function like we have in our index.php page…

mlPingPong.ball();

Variables

Inside our ball function we declare our variables…

 var z = document.outerWidth || document.documentElement.clientWidth || document.body.clientWidth,
 zz = document.innerHeight || document.documentElement.clientHeight || document.body.clientWidth,
 ctx,
 x = 65, /* Starting Xposition (ball) */
 y = 110, /* Starting Yposition (ball) */
 dx = 8, /* Amount x is incremented or decremented */  
 dy = 0, /* Amount y is incremented or decremented */
 doy = 20, /* Amount player one paddle moves on keypress */
 dty = 20, /* Amount player two paddle moves on keypress */
 impact, /* Position of impact on paddle in pixels */
 flag = 0, /* Ball is moving if flag = 1 */
 scores = {playerOneScore : 0, playerTwoScore : 0}, /* object for scores */
 keys = [], /* Array for pressed or released keys */
 WIDTH = z * (3/4), /* Width of canvas (3/4 width of browser window) */
 HEIGHT = zz * (3/4), /* Height of canvas (3/4 height of browser window) */
 pox = 50, /* Start x position of player one paddle */
 poy = 50, /* Start y position of player one paddle */
 ptx = WIDTH - 50, /* Start x position of player two paddle */
 pty = 50, /* Start y position of player two paddle */
 a, /* Fill style of ball */
 c = 0;	/* Collision toggle */

Declare the canvas variable and assign a width and height attribute to the canvas element which is 3/4 the width and height of the browser window.

 var canvas = document.getElementById('canvas');
 canvas.setAttribute('width',WIDTH);
 canvas.setAttribute('height',HEIGHT);

The init function

The init function gets the context of the canvas and then fires the draw function once every 15 thousandths of a second.

function init() {
ctx = canvas.getContext("2d");
return setInterval(draw, 15);
}

Within the draw function there are other functions which are called.

The clear function

function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}

This function clears the canvas before it is redrawn with any changes that have been made, i.e. movement of the ball or movement of the paddles. Without it the canvas would become a mess very quickly.

The board

function rect(x,y,w,h) {
ctx.beginPath();
ctx.rect(x,y,w,h);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = '5px';
ctx.beginPath();
ctx.moveTo(w/2,0);
ctx.lineTo(w/2,h);
ctx.stroke();
ctx.closePath();
} 

This function draws the rectangular board onto the canvas with a white line down the middle.

The ball

function circle(x,y,r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.fill();
}

This function draws the ball. The change in the x and y co-ordinates coupled with the fast redrawing of the canvas gives the impression that the ball is moving.

Player stuff

function p1Rect(pox,poy,pow,poh) {
ctx.beginPath();
ctx.rect(pox,poy,pow,poh);
ctx.closePath();
ctx.fill();
}

function p1Score(ox,oy) {
ctx.font = '100px Arial';
ctx.strokeText(scores.playerOneScore,ox,oy);
}

This is the code for the player one paddle and score. The functions are the same for player two but using different arguments. See the complete javascript file.

keyUp and keyDown

The function below I borrowed from an answer on Stack Exchange. It adds the value of the keydown to the keys array and removes it when released. This way you can hold down multiple keys and their values will be held in the array until released. You assign logic depending on which keys are pressed.

onKeyDown = onKeyUp = function(event) {
     event.preventDefault();
          keys[event.keyCode] = event.type == 'keydown';
               if(keys[32]) { /* space bar (space) pressed */
                    if(flag == 0) {
                         flag = 1;
                    }
               }
               if(keys[87]) { /* player one up key(w) pressed */
                    if(poy - doy > -15) {
                         if(flag == 0 && x == 65) {
                              poy -= doy;
                              y -= doy;
                         }
                         else { 
                              poy -= doy;
                         }		
                    }
               }
               if(keys[83]) { /* player one down key(s) pressed */
                    if(poy + doy < HEIGHT - 105) {
                         if(flag == 0 && x == 65) {
                              poy += doy;
                              y += doy;
                         }
                         else {
                              poy += doy;
                         }
                    }
               }
               .......

The if/else statement for each keydown takes into account if the ball is moving or not.
If the ball isn’t moving then it means that the ball is ready to be served and so when the key is depressed the paddle and the ball need to move at the same time and by the same amount.

To see the complete function refer to the complete javascript file.

The collision detection

We use the getImageData function to find the pixel colour values around the x and y co-ordinate.
In the case below the function looks at 12 x 12 = 576 pixels around the x,y co-ordinate and if the pixel g (rgba) value matches the g value (58) of the paddle then there is a collision.

function checkCollision() {
     var id = ctx.getImageData(x,y,12,12),
         px = id.data;
         for(var i = 0; i < px.length; i+=4) {
              if(px[i+1] == 58) {
                   c = 1;
                   if(x < (WIDTH / 2)) {
                        impact = y - poy;
                        calibrateAngle(impact);
                   }
                   else {
                        impact = y - pty;
                        calibrateAngle(impact);
                   }
              }
         }
}

If a collision is detected then we work out if it is with the paddle of player one (at less than half the WIDTH) or player two.
We then find the impact value (the y value of the ball minus the y value of the paddle) and insert it into the calibrateAngle function.

Set the rebound angle

The passed value of impact can now be used to set the angle of the rebounded ball. This is done by setting the value of dy to a negative if the value of impact is less than half the height of the paddle or to a positive if the value of impact is more than half the height of the paddle.
A large magnitude of dy will produce a large angle and a small magnitude, a small angle. If it is zero then the ball will rebound horizontally.

function calibrateAngle(impact) {
	if(impact < 59 && impact > 49) { 
		dy = -2;
	}
	else if(impact < 50 && impact > 39) {
		dy = -4;
	}
	else if(impact < 40 && impact > 29) {
		dy = -6;
	}
	else if(impact < 30 && impact > 19) {
		dy = -7;
	}
	else if(impact < 20 && impact > 9) {
		dy = -8;
	}
	else if(impact < 10 && impact > -1) {
		dy = -9;
	}
	else if(impact > 62 && impact < 70) {
		dy = 2;
	}
	else if(impact > 69 && impact < 80) {
		dy = 4;
	}
	else if(impact > 79 && impact < 90) {
		dy = 6;
	}
	else if(impact > 89 && impact < 100) {
		dy = 7;
	}
	else if(impact > 99 && impact < 110) {
		dy = 8;
	}
	else if(impact > 109 && impact < 121) {
		dy = 9;
	}
	else {
		dy = 0;
	}
}

Scoring

function addScore() {
	/* End point if ball is off the right edge of canvas */
	if (x + dx > WIDTH) {
                /* Increment score */
		scores.playerOneScore++;
		flag = 0; 
                /* Player one paddle reset */
		pox = 50; 
		poy = 50;
                /* Ball position and angle reset */
		x = 65; /* Starting Xposition */
		y = 110; /* Starting Yposition */
		dy = 0;
	}
        /* End point if ball is off the left edge of canvas */
	if (x + dx < 0) {
                /* Increment score */
		scores.playerTwoScore++;
		flag = 0; 
                /* Player two paddle reset */
		ptx = WIDTH - 50;
		pty = 50;
                /* Ball position and angle reset */
		x = WIDTH - 61; /* Starting Xposition */
		y = 110; /* Starting Yposition */
		dy = 0;
		dx = -dx;
	}
}

The draw funtion

function draw() {
	clear();
	ctx.fillStyle = '#eee';
	rect(0,0,WIDTH,HEIGHT);
	ctx.fillStyle = 'rgba(114,58,58,1)';
	ctx.strokeStyle = 'rgba(120,88,88,1)';
	p1Rect(pox,poy,4,120);
	p2Rect(ptx,pty,4,120);
	ctx.strokeStyle = '#fff';
	p1Score((WIDTH/4) - 30,HEIGHT/2);
	p2Score(((WIDTH/4) * 3) - 50,HEIGHT/2);
	a == 1 ? ctx.fillStyle = "#444444" : ctx.fillStyle = "#b3b3b3";
	circle(x, y, 10);
			
	addScore();
	flag != 0 ? checkCollision() : '';
				
	if(c == 1) {
        	dx = -dx;
		c = 0;
	}
				
	if(flag == 0) {}
	if(flag == 1) {
		x += dx; y += dy;
	}
				
	if(y + dy > HEIGHT || y + dy < 0) {
		dy = -dy;
	}	
}

The draw function puts it all together. It starts off with the clear function which clears the canvas ready for everything to be redrawn. Then the canvas is drawn followed by the paddles, the scores and the ball.

Some important points

The last part of the function changes the direction of the ball (dy = -dy) if the value of y+dy is greater than the height of the canvas or if it less than 0.

if(y + dy > HEIGHT || y + dy < 0) {
	dy = -dy;
}

The checkCollision function fires unless the flag variable is zero. If there is a collision (c == 1) then the ball changes direction (dx = -dx).

flag != 0 ? checkCollision() : '';
				
if(c == 1) {
       	dx = -dx;
	c = 0;
}

Finally

Calling the init function and setting the event listeners for the key pressing…

init();	
window.addEventListener('keydown',onKeyDown,true);
window.addEventListener('keyup',onKeyUp,true);

Click here to see the demo.


Previous

Next

No Comments

Comments are closed.