a pong battle to the death on an HTML5 canvas

June 19th, 2010 § 7

Download the full source.

The first (and only) HTML5 element we need is a canvas to draw our stuff on.

<canvas id="game" width="500" height="350">
	<p>Your browser doesn't support canvas.</p>
</canvas>

Using  jQuery for simplicity we get a reference to the canvas element and define constants and variables for the game.

var canvas = $('#game');
var ctx =  canvas[0].getContext('2d');

const game_width = $("#game").width();
const game_height = $("#game").height();

const paddle_height = 60;
const paddle_width = 10;
const paddle_speed=8;
const ball_max_speed = 30;

var ball_x;
var ball_y;
var ball_x_speed;
var ball_y_speed;

var hits = 0;

var p1_paddle_x = 20;
var p1_paddle_y = game_height/2-paddle_height/2;
var p2_paddle_x = game_width-paddle_width-20;
var p2_paddle_y = game_height/2-paddle_height/2;

var p1_score=0;
var p2_score=0;

Now to draw the game background

//define the fill color
ctx.fillStyle = "#fb2";
//start drawing
ctx.beginPath();
//draw a rectangle
ctx.rect(0,0,game_width,game_height);
//this is not necessary as the path will close before fill()
ctx.closePath();
//do the fill
ctx.fill();

The above javascript  gives the following output

To draw the ball and the paddles:

//draw the ball
ctx.fillStyle = "#f55";
ctx.beginPath();
ctx.arc(ball_x, ball_y, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
//draw the left paddle
ctx.beginPath();
ctx.rect(p1_paddle_x,p1_paddle_y,paddle_width,paddle_height);
ctx.closePath();
ctx.fill();
//draw the right paddle
ctx.beginPath();
ctx.rect(p2_paddle_x,p2_paddle_y,paddle_width,paddle_height);
ctx.closePath();
ctx.fill();
//draw a line to separate the playing field
ctx.fillStyle = "#ddd";
ctx.beginPath();
ctx.rect(game_width/2-1,0,2,game_height);
ctx.closePath();
ctx.fill();

Each player’s score can be drawn with the fillText function.

ctx.textBaseline = "top";
ctx.fillStyle = "#000";
ctx.font = "bold 13px sans-serif";
ctx.fillText("( "+p1_score+" )", 40, 5);
ctx.fillText("( "+p2_score+" )", game_width-60, 5);

To make it look a little bit better we can use a drop shadow on the paddles and ball

ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 10;

Now in order to make things move we need to clear the canvas and redraw the updated positions of the paddles and the ball. This can be achieved using a setInterval calling a draw function every 10 milliseconds.

setInterval(draw, 10);

function draw() {
   ctx.clearRect(0,0,game_width,game_height);
   ...
   calculate and draw stuff
   ...
   ...
}

Making the ball move. We need the ball to bounce off the top and bottom , bounce off the paddles and when it leaves the stage to keep score and move to center with a random direction

// bounce off the paddles
// first we check the x coordinate to see if the ball
// is in the paddle area.
// Then if the y coordinate of the ball is within the
// paddle dimensions reverse the x speed
// we also keep track of the bounces so that we can
// increase the ball speed
if (ball_x+ball_x_speed>p2_paddle_x)
	if (ball_y+ball_y_speed>=p2_paddle_y && ball_y+ball_y_speed<p2_paddle_y+paddle_height) {
		ball_x_speed *= -1;
		hits++;
	}

if (ball_x+ball_x_speed<=p1_paddle_x+paddle_width)
	if (ball_y+ball_y_speed>p1_paddle_y && ball_y+ball_y_speed<p1_paddle_y+paddle_height) {
		ball_x_speed *= -1;
		hits++;
	}
// to bounce off the top and bottom simply check
// the y of the ball and reverse the vertical speed
if (ball_y + ball_y_speed > game_height || ball_y + ball_y_speed < 5)
  ball_y_speed = -ball_y_speed;

// the ball has gone beyond the game_width
// so increase p1's score and reposition ball
if (ball_x + ball_x_speed > game_width) {
	p1_score++;
	new_ball()
}
// the ball has gone off the lest side. point for player 2
if (ball_x - ball_x_speed < 0 ) {
	p2_score++;
	new_ball()
}
// for every 3 bounces of the paddles
// we increase the ball speed, up to maximum
if (hits == 3) {
	if (Math.abs(ball_x_speed) < ball_max_speed && Math.abs(ball_y_speed) < ball_max_speed) {
		ball_x_speed += 1.3;
		ball_y_speed *= 1.3;
		hits = 0;
	}
}
//calculate the ball's position and draw it
ball_x += ball_x_speed;
ball_y += ball_y_speed;
ctx.fillStyle = "#f55";
ctx.beginPath();
ctx.arc(ball_x, ball_y, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

To get a new ball after each point we create a random number from -5 to 5 for both speeds and reposition the ball

function new_ball() {
			var adjustedHigh = (parseFloat(5) - parseFloat(-5)) + 1;
			var numRand=0;
			while (numRand==0) {
				numRand = Math.floor(Math.random()*adjustedHigh) + parseFloat(-5);
			}
			ball_x_speed = numRand;
			ball_y_speed = numRand;
			hits=0;
			ball_x = 250;
			ball_y = 175;
		}

Lastly we need to make the paddles move and try to hit the ball. Here we have two cases. The first case is when the ball is coming towards the paddle. In this case we check the ball position and move the paddle accordingly.

The second case is the ball leaving the paddle and going to the other direction. In this case we slowly bring back the paddle to the middle position. This is the code for the left paddle.

// if the ball is coming towards the paddle
if (ball_x_speed < 0) {
// if ball is higher than paddle move up
	if (ball_y < p1_paddle_y + paddle_height / 2 && p1_paddle_y>0)
		p1_paddle_y -= paddle_speed;
// if ball is lower than paddle move down
	if (ball_y > p1_paddle_y + paddle_height / 2 && p1_paddle_y + paddle_height<game_height)
		p1_paddle_y += paddle_speed;
}
// else slowly go back to the middle
else {
	if (p1_paddle_y<game_height/2-paddle_height/2) p1_paddle_y += 2;
	else if (p1_paddle_y>game_height/2-paddle_height/2) p1_paddle_y -= 2;
}

Here is the full source:

<script type="text/javascript">
	$(document).ready (function () {

		var canvas = $('#game');
	 	var ctx =  canvas[0].getContext('2d');
		const game_width = $("#game").width();
		const game_height = $("#game").height();
		const paddle_height = 60;
			const paddle_width = 10;
		const paddle_speed=8;
		const ball_max_speed = 30;

		var ball_x;
		var ball_y;
		var ball_x_speed;
		var ball_y_speed;

		var hits = 0;

		var p1_paddle_x = 20;
		var p1_paddle_y = game_height/2-paddle_height/2;
		var p2_paddle_x = game_width-paddle_width-20;
		var p2_paddle_y = game_height/2-paddle_height/2;

		var p1_score=0;
		var p2_score=0;

		setInterval(draw, 10);

		function new_ball() {
			var adjustedHigh = (parseFloat(5) - parseFloat(-5)) + 1;
			var numRand=0;
			while (numRand==0) {
				numRand = Math.floor(Math.random()*adjustedHigh) + parseFloat(-5);
			}
			ball_x_speed = numRand;
			ball_y_speed = numRand;
			hits=0;
			ball_x = 250;
			ball_y = 175;
		}
		function draw() {
			if (!ball_x) new_ball();
			ctx.clearRect(0,0,game_width,game_height);
			ctx.fillStyle = "#fb2";
			ctx.beginPath();
				ctx.rect(0,0,game_width,game_height);
				ctx.closePath();
				ctx.fill();

			ctx.shadowOffsetX = 0;
			ctx.shadowOffsetY = 0;
			ctx.shadowBlur = 0;

			ctx.textBaseline = "top";
			ctx.fillStyle = "#000";
			ctx.font = "bold 13px sans-serif";
			ctx.fillText("( "+p1_score+" )", 40, 5);
			ctx.fillText("( "+p2_score+" )", game_width-60, 5);

			ctx.shadowOffsetX = 2;
			ctx.shadowOffsetY = 5;
			ctx.shadowBlur = 10;

			ctx.shadowColor = "black";
			ctx.fillStyle = "#ddd";
			ctx.beginPath();
				ctx.rect(game_width/2-1,0,2,game_height);
				ctx.closePath();
				ctx.fill();

			if (ball_x+ball_x_speed>p2_paddle_x)
				if (ball_y+ball_y_speed>=p2_paddle_y && ball_y+ball_y_speed<p2_paddle_y+paddle_height) {
					ball_x_speed *= -1;
					hits++;
				}
			if (ball_x+ball_x_speed<=p1_paddle_x+paddle_width)
				if (ball_y+ball_y_speed>p1_paddle_y && ball_y+ball_y_speed<p1_paddle_y+paddle_height) {
					ball_x_speed *= -1;
					hits++;
				}
			if (ball_y + ball_y_speed > game_height || ball_y + ball_y_speed < 5)
			  ball_y_speed = -ball_y_speed;
			if (ball_x + ball_x_speed > game_width) {
				p1_score++;
				new_ball()
			}
			if (ball_x - ball_x_speed < 0 ) {
				p2_score++;
				new_ball()
			}
			if (hits == 3) {
				if (Math.abs(ball_x_speed) < ball_max_speed && Math.abs(ball_y_speed) < ball_max_speed) {
					ball_x_speed += 1.3;
					ball_y_speed *= 1.3;
					hits = 0;
				}
			}

		  	ball_x += ball_x_speed;
			ball_y += ball_y_speed;
			if (Math.abs(ball_x_speed)<2.5) ball_x_speed*=5;

			ctx.fillStyle = "#f55";
			ctx.beginPath();
			ctx.arc(ball_x, ball_y, 10, 0, Math.PI*2, true);
			ctx.closePath();
			ctx.fill();

			ctx.fillStyle = "#1e1";

			if (ball_x_speed < 0) {
				if (ball_y < p1_paddle_y + paddle_height / 2 && p1_paddle_y>0)
					p1_paddle_y -= paddle_speed;
				if (ball_y > p1_paddle_y + paddle_height / 2 && p1_paddle_y + paddle_height<game_height)// && act > 4)
					p1_paddle_y += paddle_speed;
			}
			else {
				if (p1_paddle_y<game_height/2-paddle_height/2) p1_paddle_y += 2;
				else if (p1_paddle_y>game_height/2-paddle_height/2) p1_paddle_y -= 2;
			}
			ctx.beginPath();
				ctx.rect(p1_paddle_x,p1_paddle_y,paddle_width,paddle_height);
				ctx.closePath();
				ctx.fill();

			if (ball_x_speed > 0) {
				if (ball_y < p2_paddle_y + paddle_height / 2 && p2_paddle_y>0)
					p2_paddle_y -= paddle_speed;
				if (ball_y > p2_paddle_y + paddle_height / 2 && p2_paddle_y + paddle_height<game_height)// && act > 4)
					p2_paddle_y += paddle_speed;
			}
			else {
				if (p2_paddle_y<game_height/2-paddle_height/2) p2_paddle_y += 2;
				else if (p2_paddle_y>game_height/2-paddle_height/2) p2_paddle_y -= 2;
			}

			ctx.beginPath();
			ctx.rect(p2_paddle_x,p2_paddle_y,paddle_width,paddle_height);
			ctx.closePath();
			ctx.fill();

		}
	})

Download the full source.

This is my first attempt on anything HTML5 so comments and suggestions are welcome.

Where Am I?

You are currently browsing the HTML5 category at Discorganized.

Partly powered by CleverPlugins.com