JavaScript: Intro to Web Game Development – Part 4: animate left paddle

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;

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:

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() 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);

For more information about setInterval, see:
https://www.w3schools.com/jsref/met_win_setinterval.asp

Step 4: Create a mousemove EventListener

Also inside the window.onload function, I need to add an EventListener for mouse movements. This is achieved with the JavaScript method addEventListener() and its first argument is ‘mousemove’.

canvas.addEventListener('mousemove',
	function(evt) {
		var mousePos = calculateMousePos(evt);
		paddle1Y = mousePos.y - (PADDLE_HEIGHT/2);
	});

The paddle will be moving up and down on the Y axis. The line that controls this is:

paddle1Y = mousePos.y - (PADDLE_HEIGHT/2);

PADDLE_HEIGHT/2 is the middle of the paddle vertically, which corresponds to the movement of the mouse.

For more information about addEventListener() see:
https://www.w3schools.com/jsref/met_element_addeventlistener.asp

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:

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(). It also uses MouseEvent clientX and clientY properties, which output the coordinates of the mouse pointer.

For more information about getBoundingClientRect(), see:
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect

For more about clientX and clientY, see:
https://www.w3schools.com/jsref/event_clientx.asp

Here is what the entire script looks like so far:

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: