This is the fourth part in the series on how to create a web browser game of Pong using JavaScript. Part 3 can be read here:
For this article, I will demonstrate how to add mouse control to the left paddle so that it moves straight up and down on the Y axis, based on mouse movement. In this step I am also:
assigning variables
creating a “drawComponents” function
setting up a framerate of 30 frames per second for interactive gameplay
implementing an event listener to move the left paddle according to the mouse position
Step 1: Create variables
For this step, I moved some numeric values that were previously hard coded, into global variables at the top of the script. For example:
var canvas;
var canvasContext;
var canvas_width;
var canvas_height;
// offsets to account for the rounded corners
var offsetA = 30;
var offsetB = 60;
// starting y position of the left paddle
var paddle1Y = 100;
// defaults for paddle thickness and height
const PADDLE_THICKNESS = 10;
const PADDLE_HEIGHT = 70;
var canvas;
var canvasContext;
var canvas_width;
var canvas_height;
// offsets to account for the rounded corners
var offsetA = 30;
var offsetB = 60;
// starting y position of the left paddle
var paddle1Y = 100;
// defaults for paddle thickness and height
const PADDLE_THICKNESS = 10;
const PADDLE_HEIGHT = 70;
var canvas;
var canvasContext;
var canvas_width;
var canvas_height;
// offsets to account for the rounded corners
var offsetA = 30;
var offsetB = 60;
// starting y position of the left paddle
var paddle1Y = 100;
// defaults for paddle thickness and height
const PADDLE_THICKNESS = 10;
const PADDLE_HEIGHT = 70;
Step 2: Create drawComponents function
Things that were previously drawn in the window.onload function are now moved to a dedicated “drawComponents” function. For example:
functiondrawComponents(){
// this line blacks out the background to give the appearance of animation
function drawComponents() {
// this line blacks out the background to give the appearance of animation
canvasContext.fillStyle = 'black';
canvasContext.fillRect(offsetA, 0, canvas_width-offsetB, canvas_height);
// Left paddle
canvasContext.fillStyle = 'white';
canvasContext.fillRect(offsetA, paddle1Y, PADDLE_THICKNESS, PADDLE_HEIGHT);
// Right paddle
canvasContext.fillStyle = 'white';
canvasContext.fillRect(canvas_width-40, canvas_height-120, PADDLE_THICKNESS, PADDLE_HEIGHT);
// Draw the ball
canvasContext.fillStyle = 'white';
canvasContext.fillRect(200, 300, 10, 10);
// Place holder for the scores
canvasContext.font="40px monospace";
canvasContext.fillText("1", (canvas_width/2)-60, 40);
canvasContext.fillText("0", (canvas_width/2)+35, 40);
drawNet();
}
function drawComponents() {
// this line blacks out the background to give the appearance of animation
canvasContext.fillStyle = 'black';
canvasContext.fillRect(offsetA, 0, canvas_width-offsetB, canvas_height);
// Left paddle
canvasContext.fillStyle = 'white';
canvasContext.fillRect(offsetA, paddle1Y, PADDLE_THICKNESS, PADDLE_HEIGHT);
// Right paddle
canvasContext.fillStyle = 'white';
canvasContext.fillRect(canvas_width-40, canvas_height-120, PADDLE_THICKNESS, PADDLE_HEIGHT);
// Draw the ball
canvasContext.fillStyle = 'white';
canvasContext.fillRect(200, 300, 10, 10);
// Place holder for the scores
canvasContext.font="40px monospace";
canvasContext.fillText("1", (canvas_width/2)-60, 40);
canvasContext.fillText("0", (canvas_width/2)+35, 40);
drawNet();
}
Step 3: Create a frame rate and game loop
Inside the window.onload function, I am setting a framerate with the
setInterval()
setInterval() method, which sets up a game loop. Since this function contains the game loop, everything that happens in the game, all of the “action”, needs to occur in this loop, which is why there is a call to drawComponents() inside this block.
This setup, defining a framerate, and using it as the second argument to the setInterval() method, is the standard way to create a game loop in JavaScript.
var framesPerSecond = 30;
setInterval(
function(){
drawComponents();
}, 1000/framesPerSecond);
var framesPerSecond = 30;
setInterval(
function() {
drawComponents();
}, 1000/framesPerSecond);
var framesPerSecond = 30;
setInterval(
function() {
drawComponents();
}, 1000/framesPerSecond);
Notice inside the function, a mousePos variable is being set by calling another function called calculateMousePos(). That function also needs to be added to the script, but not inside the window.onload function. It looks like this:
functioncalculateMousePos(evt){
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
var mouseX = evt.clientX - rect.left - root.scrollLeft;
var mouseY = evt.clientY - rect.top - root.scrollTop;
return{
x:mouseX,
y:mouseY
};
}
function calculateMousePos(evt) {
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
var mouseX = evt.clientX - rect.left - root.scrollLeft;
var mouseY = evt.clientY - rect.top - root.scrollTop;
return {
x:mouseX,
y:mouseY
};
}
function calculateMousePos(evt) {
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
var mouseX = evt.clientX - rect.left - root.scrollLeft;
var mouseY = evt.clientY - rect.top - root.scrollTop;
return {
x:mouseX,
y:mouseY
};
}
This function is crucial in working with the EventListener to enable movement of the paddle based on mouse movement. It uses a JavaScript method called
getBoundingClientRect()
getBoundingClientRect(). It also uses MouseEvent
clientX
clientX and
clientY
clientY properties, which output the coordinates of the mouse pointer.
canvasContext.fillRect(canvas_width/2, i, 2, 10, 'white');
}
}
var canvas;
var canvasContext;
var canvas_width;
var canvas_height;
// offsets to account for the rounded corners
var offsetA = 30;
var offsetB = 60;
// starting y position of the left paddle
var paddle1Y = 100;
// defaults for paddle thickness and height
const PADDLE_THICKNESS = 10;
const PADDLE_HEIGHT = 70;
window.onload = function() {
canvas = document.getElementById("gameCanvas");
canvasContext = canvas.getContext("2d");
canvas_width = canvas.width;
canvas_height = canvas.height;
// Create the rounded canvas play area
canvasContext.roundRect(0, 0, canvas_width, canvas_height, 40, "black");
var framesPerSecond = 30;
setInterval(
function() {
drawComponents();
}, 1000/framesPerSecond);
canvas.addEventListener('mousemove',
function(evt) {
var mousePos = calculateMousePos(evt);
paddle1Y = mousePos.y - (PADDLE_HEIGHT/2);
});
}
// Function to create rounded corners on the rectangle
CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, radius, fill, stroke) {
// If stroke argument not provided, provide a stroke by default
if (typeof stroke == "undefined" ) {
stroke = true;
}
// If no radius is defined, then give it a default of 5
if (typeof radius === "undefined") {
radius = 5;
}
// Start of shape definition
this.beginPath();
this.moveTo(x + radius, y);
// Draw the top border line
this.lineTo(x + width - radius, y);
// Draw the top right curve
this.quadraticCurveTo(x + width, y, x + width, y + radius);
// Draw the right border line
this.lineTo(x + width, y + height - radius);
// Draw the bottom right curve
this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
// Draw the bottom border line
this.lineTo(x + radius, y + height);
// Draw the bottom left curve
this.quadraticCurveTo(x, y + height, x, y + height - radius);
// Draw the left border line
this.lineTo(x, y + radius);
// Draw the final (top left) curve
this.quadraticCurveTo(x, y, x + radius, y);
// End of shape definition
this.closePath();
// Draw the shape with a line
if (stroke) {
this.stroke();
}
// Fill in the shape with color
if (fill) {
this.fill();
}
}
function calculateMousePos(evt) {
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
var mouseX = evt.clientX - rect.left - root.scrollLeft;
var mouseY = evt.clientY - rect.top - root.scrollTop;
return {
x:mouseX,
y:mouseY
};
}
function drawComponents() {
// this line blacks out the background to give the appearance of animation
canvasContext.fillStyle = 'black';
canvasContext.fillRect(offsetA, 0, canvas_width-offsetB, canvas_height);
// Left paddle
canvasContext.fillStyle = 'white';
canvasContext.fillRect(offsetA, paddle1Y, PADDLE_THICKNESS, PADDLE_HEIGHT);
// Right paddle
canvasContext.fillStyle = 'white';
canvasContext.fillRect(canvas_width-40, canvas_height-120, PADDLE_THICKNESS, PADDLE_HEIGHT);
// Draw the ball
canvasContext.fillStyle = 'white';
canvasContext.fillRect(200, 300, 10, 10);
// Place holder for the scores
canvasContext.font="40px monospace";
canvasContext.fillText("1", (canvas_width/2)-60, 40);
canvasContext.fillText("0", (canvas_width/2)+35, 40);
drawNet();
}
function drawNet() {
for(var i=0; i < canvas_height; i += 30) {
canvasContext.fillStyle = 'white';
canvasContext.fillRect(canvas_width/2, i, 2, 10, 'white');
}
}
var canvas;
var canvasContext;
var canvas_width;
var canvas_height;
// offsets to account for the rounded corners
var offsetA = 30;
var offsetB = 60;
// starting y position of the left paddle
var paddle1Y = 100;
// defaults for paddle thickness and height
const PADDLE_THICKNESS = 10;
const PADDLE_HEIGHT = 70;
window.onload = function() {
canvas = document.getElementById("gameCanvas");
canvasContext = canvas.getContext("2d");
canvas_width = canvas.width;
canvas_height = canvas.height;
// Create the rounded canvas play area
canvasContext.roundRect(0, 0, canvas_width, canvas_height, 40, "black");
var framesPerSecond = 30;
setInterval(
function() {
drawComponents();
}, 1000/framesPerSecond);
canvas.addEventListener('mousemove',
function(evt) {
var mousePos = calculateMousePos(evt);
paddle1Y = mousePos.y - (PADDLE_HEIGHT/2);
});
}
// Function to create rounded corners on the rectangle
CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, radius, fill, stroke) {
// If stroke argument not provided, provide a stroke by default
if (typeof stroke == "undefined" ) {
stroke = true;
}
// If no radius is defined, then give it a default of 5
if (typeof radius === "undefined") {
radius = 5;
}
// Start of shape definition
this.beginPath();
this.moveTo(x + radius, y);
// Draw the top border line
this.lineTo(x + width - radius, y);
// Draw the top right curve
this.quadraticCurveTo(x + width, y, x + width, y + radius);
// Draw the right border line
this.lineTo(x + width, y + height - radius);
// Draw the bottom right curve
this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
// Draw the bottom border line
this.lineTo(x + radius, y + height);
// Draw the bottom left curve
this.quadraticCurveTo(x, y + height, x, y + height - radius);
// Draw the left border line
this.lineTo(x, y + radius);
// Draw the final (top left) curve
this.quadraticCurveTo(x, y, x + radius, y);
// End of shape definition
this.closePath();
// Draw the shape with a line
if (stroke) {
this.stroke();
}
// Fill in the shape with color
if (fill) {
this.fill();
}
}
function calculateMousePos(evt) {
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
var mouseX = evt.clientX - rect.left - root.scrollLeft;
var mouseY = evt.clientY - rect.top - root.scrollTop;
return {
x:mouseX,
y:mouseY
};
}
function drawComponents() {
// this line blacks out the background to give the appearance of animation
canvasContext.fillStyle = 'black';
canvasContext.fillRect(offsetA, 0, canvas_width-offsetB, canvas_height);
// Left paddle
canvasContext.fillStyle = 'white';
canvasContext.fillRect(offsetA, paddle1Y, PADDLE_THICKNESS, PADDLE_HEIGHT);
// Right paddle
canvasContext.fillStyle = 'white';
canvasContext.fillRect(canvas_width-40, canvas_height-120, PADDLE_THICKNESS, PADDLE_HEIGHT);
// Draw the ball
canvasContext.fillStyle = 'white';
canvasContext.fillRect(200, 300, 10, 10);
// Place holder for the scores
canvasContext.font="40px monospace";
canvasContext.fillText("1", (canvas_width/2)-60, 40);
canvasContext.fillText("0", (canvas_width/2)+35, 40);
drawNet();
}
function drawNet() {
for(var i=0; i < canvas_height; i += 30) {
canvasContext.fillStyle = 'white';
canvasContext.fillRect(canvas_width/2, i, 2, 10, 'white');
}
}
And here is what the game looks like so far, with the left paddle responding to mouse movements: