JavaScript: Intro to Web Game Development – Part 7: add title screen and settings

As of the previous article, the game can be considered to be in a “complete” state, but I want to add a few features, such as a title screen, some options such as score to win, ball speed, and game colors. I also want to add a link back to the title screen from the winner screen, so this will entail some logic for navigation between pages.  Here is the previous article in the series:

JavaScript: Intro to Web Game Development – Part 6: add win screen and replay

Here is what the game’s title screen with options looks like:

Here are the coding steps to make all of these new features work:

Step 1: Add some new global variables

var GAME_COLOR = "black"; // default
var startNewGame = false;
var mouseClickX, mouseClickY;
var mouseClicked = false;
var box_1_selected = true; // default
var box_2_selected = false;
var box_3_selected = true; // default
var box_4_selected = false;
var box_5_selected = false;
var box_6_selected = true; // default
var box_7_selected = false;
var box_8_selected = false;
var linkText, linkText2;
var link1, link2;
var linkX2, linkY2;
var mousePosition = {x:0, y:0}
var mousePos;

Step 2: Update the game loop in the window.onload function

    // game loop - begin ------------
    var framesPerSecond = 30;
    setInterval(
        function() {

            if (startNewGame == true) {
                startGame();
            } else if (showGameOverScreen) {
                gameOverScreen();
            } else {
                titlePage();
            }

        }, 1000/framesPerSecond);
    // game loop - end  ------------

Notice the new function calls to startGame() and titlePage(). The startGame() function looks like this:

function startGame() {
    ballMovement();
    drawComponents();
}

Step 3: Enhance mousedown event listener in the window.onload function

    canvas.addEventListener('mousedown',
    function(evt) {
        mousePos = calculateMousePos(evt);
        mouseClickX = mousePos.x;
        mouseClickY = mousePos.y;
        // console.log(mousePos);
        // console.log("mouseClickX: ", mouseClickX, "mouseClickY: ", mouseClickY);
        mouseClicked = true;
    });

mouseClickX and mouseClickY are new variables that will help with determining if a button or link is clicked.

Step 4: Create new function for detecting button and link clicks

function isPointInsideRect(pointX, pointY, rectX, rectY, rectWidth, rectHeight) {
    // returns true if point is inside rect
    return  (rectX <= pointX) &&
            (rectX + rectWidth >= pointX) &&
            (rectY <= pointY) &&
            (rectY + rectHeight >= pointY);
}

This function will return true if a mouse click is within the rectangle of a button or link. Otherwise it will return false.

Step 5: Move the code for drawing the scores into it’s own function

function drawScores() {
    // Left side score - make room for more digits
    canvasContext.font="40px monospace";
    if (player1Score < 10) {
        canvasContext.fillText(player1Score, (canvas_width/2)-60, 40);
    } else if (player1Score > 99) {
        canvasContext.fillText(player1Score, (canvas_width/2)-100, 40);
    } else {
        canvasContext.fillText(player1Score, (canvas_width/2)-80, 40);
    }

    // Right side score
    canvasContext.fillText(player2Score, (canvas_width/2)+35, 40);
}

Step 6: Update the gameOverScreen() function

The winner screen has been updated with the following:

  • [settings] has been added to the bottom of both the left and right pages
  • both the play again and [settings] text now act as links that change the pointer when the mouse hovers over them
  • code has been added to make the mouseover code work
  • boxes have been created to make clicking on the the text links detectable
  • actions have been defined for when a link has been clicked

Here is an example of what the new winner screens look like. Both the “play again” and “[settings]” text are now links that change the pointer on hover and have specific actions when clicked. I also made sure the score and net were left on the screen.

Here is what the new gameOverScreen() looks like:

function gameOverScreen() {

    canvasContext.fillStyle = 'white';
    drawScores();
    drawNet();

    if(player1Score >= WINNING_SCORE) {

        canvasContext.font="54px monospace";
        canvasContext.fillText("Winner", canvas_width/8, canvas_height/4);
        canvasContext.font="44px monospace";
        canvasContext.fillText("Left Player", canvas_width/12, canvas_height/3);

        linkText = "begin game";
        linkX = (canvas_width/10);
        linkY = canvas_height/2;
        boxY = (canvas_height/2)- 50;
        var link1 = {x:65, y:235, width:280, height:60};
        // canvasContext.fillStyle = 'gray';
        // canvasContext.fillRect(link1.x, link1.y, link1.width, link1.height);
        canvasContext.fillStyle = 'white';
        canvasContext.fillText("play again", linkX, linkY);


        // options link
        canvasContext.font="38px monospace";
        linkText2 = "[settings]";
        linkX2 = canvas_width/8;
        linkY2 = canvas_height-70;
        var link2 = {x:90, y:445, width:235, height:55};
        // canvasContext.fillStyle = 'gray';
        // canvasContext.fillRect(link2.x, link2.y, link2.width, link2.height);
        canvasContext.fillStyle = 'white';
        canvasContext.fillText(linkText2, linkX2, linkY2);


    } else if(player2Score >= WINNING_SCORE) {
        canvasContext.font="54px monospace";
        canvasContext.fillText("Winner", (canvas_width/2 + canvas_width/8), (0 + canvas_height/4));
        canvasContext.font="44px monospace";
        canvasContext.fillText("Right Player", (canvas_width/2 + canvas_width/24), (0 + canvas_height/3));

        linkX = (canvas_width/2 + canvas_width/16);
        linkY = canvas_height/2;
        var link1 = {x:415, y:235, width:280, height:60};
        // canvasContext.fillStyle = 'gray';
        // canvasContext.fillRect(link1.x, link1.y, link1.width, link1.height);
        canvasContext.fillStyle = 'white';
        canvasContext.fillText("play again", linkX, linkY);


        // options link
        canvasContext.font="38px monospace";

        linkText2 = "[settings]";
        linkX2 = (canvas_width/2 + canvas_width/12);
        linkY2 = canvas_height-70;
        var link2 = {x:435, y:445, width:235, height:55};
        // canvasContext.fillStyle = 'gray';
        // canvasContext.fillRect(link2.x, link2.y, link2.width, link2.height);
        canvasContext.fillStyle = 'white';
        canvasContext.fillText(linkText2, linkX2, linkY2);

    }
    var x = mousePosition.x;
    var y = mousePosition.y;

    onlink2 = isPointInsideRect(x, y, link1.x, link1.y, link1.width, link1.height);
    onlink3 = isPointInsideRect(x, y, link2.x, link2.y, link2.width, link2.height);
    if (onlink2 == true || onlink3 == true) {
        document.body.style.cursor = "pointer";
    } else {
        document.body.style.cursor = "";
    }

    if (mouseClicked) {
        link1_clicked = isPointInsideRect(mouseClickX, mouseClickY, link1.x, link1.y, link1.width, link1.height);
        if (link1_clicked == true) {
            link1_selected = true;
            link2_selected = false;
            startNewGame = true;
            player1Score = 0;
            player2Score = 0;
            showGameOverScreen = false;
            document.body.style.cursor = "";
            startGame();
        }

        link2_clicked = isPointInsideRect(mouseClickX, mouseClickY, link2.x, link2.y, link2.width, link2.height);
        if (link2_clicked == true) {
            link1_selected = false;
            link2_selected = true;
            startNewGame = false;
            player1Score = 0;
            player2Score = 0;
            showGameOverScreen = false;
            box_1_selected = true;
            mouseClicked = false;
            titlePage();
        }

    }
    // reset to false so that the mouse click only registers once in the game loop
    mouseClicked = false;
}

Step 7: Update the computer paddle A.I. for the new ball speeds

Since there are now two new options for ball  speeds (2x and 3x), I decided to update the computer A.I. so that the game will be a little more challenging. (Otherwise the computer would almost certainly always lose.)

function computerPaddleAI() {
    var paddle2YCenter = paddle2Y + (PADDLE_HEIGHT/2);

    var normal = 6;
    var fast = 10; // just a little less than 2x
    var crazy = 16;
    var moveSpeed;

    if (ballSpeedX == 20 ){
        moveSpeed = fast;
    } else if (ballSpeedX > 20 ){
        moveSpeed = crazy;
    } else {
        moveSpeed = normal
    }

    if(paddle2YCenter < ballY - 35) {
	paddle2Y = paddle2Y + moveSpeed;
    } else if(paddle2YCenter > ballY + 35) {
	paddle2Y = paddle2Y - moveSpeed;
    }
}

Step 8: Create the titlePage() function

function titlePage() {

    if(showGameOverScreen) {
        return;
    }

    // clear the game screen
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.roundRect(0, 0, canvas_width, canvas_height, 40, GAME_COLOR, 0);

    // draw the pong title
    canvasContext.fillStyle = 'white';
    canvasContext.font="120px monospace";
    canvasContext.fillText("pong", 230, 120);

    //draw the begin game link
    canvasContext.font="44px monospace";
    linkText = "begin game";
    linkX = 230;
    linkY = 500;
    var begin_game_link = {x:210, y:455, width:300, height:60};
    // A gray box around the text link for debugging
    // canvasContext.fillStyle = 'gray';
    // canvasContext.fillRect(begin_game_link.x, begin_game_link.y, begin_game_link.width, begin_game_link.height);
    canvasContext.fillStyle = 'white';
    canvasContext.fillText(linkText, linkX, linkY);


    // ---- Game options ----

    // ----------------------- Score limit ----------------------- //
    canvasContext.fillStyle = 'white';
    canvasContext.font="20px monospace";
    canvasContext.fillText("score limit", 100, 225);
    var bt = 28; // distance text is to the right of the box

    // box 1
    var box1 = {x:100, y:240, width:20, height:20};
    canvasContext.fillText("3", box1.x + bt, 257);
    canvasContext.fillRect(box1.x, box1.y, box1.width, box1.height);
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.fillRect(102, 242, 16, 16);

    if (box_1_selected == true) {
        canvasContext.fillStyle = 'white';
        canvasContext.fillRect(104, 244, 12, 12);
    }

    // box 2
    var box2 = {x:180, y:240, width:20, height:20};
    canvasContext.fillStyle = 'white';
    canvasContext.fillText("10", box2.x + bt, 257);
    canvasContext.fillRect(box2.x, box2.y, box2.width, box2.height);
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.fillRect(182, 242, 16, 16);

    // box 2 selected
    if (box_2_selected == true) {
        canvasContext.fillStyle = 'white';
        canvasContext.fillRect(184, 244, 12, 12);
    }

    // ----------------------- color options ----------------------- //
    canvasContext.fillStyle = 'white';
    canvasContext.font="20px monospace";
    canvasContext.fillText("game color", 450, 225);

    // box 3
    var box3 = {x:450, y:240, width:20, height:20};
    canvasContext.fillText("black & white", box3.x + bt, 257);
    canvasContext.fillRect(box3.x, box3.y, box3.width, box3.height);
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.fillRect(452, 242, 16, 16);

    if (box_3_selected == true) {
        canvasContext.fillStyle = 'white';
        canvasContext.fillRect(454, 244, 12, 12);
    }

    // box 4
    var box4 = {x:450, y:267, width:20, height:20};
    canvasContext.fillStyle = 'white';
    canvasContext.fillText("retro green", box4.x + bt, 282);
    canvasContext.fillRect(box4.x, box4.y, box4.width, box4.height);
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.fillRect(452, 269, 16, 16);

    // box 4 selected
    if (box_4_selected == true) {
        canvasContext.fillStyle = 'white';
        canvasContext.fillRect(454, 271, 12, 12);
    }

    // box 5
    var box5 = {x:450, y:294, width:20, height:20};
    canvasContext.fillStyle = 'white';
    canvasContext.fillText("cool blue", box5.x + bt, 309);
    canvasContext.fillRect(box5.x, box5.y, box5.width, box5.height);
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.fillRect(452, 296, 16, 16);

    // box 5 selected
    if (box_5_selected == true) {
        canvasContext.fillStyle = 'white';
        canvasContext.fillRect(454, 298, 12, 12);
    }

    // ----------------------- ball speed ----------------------- //
    canvasContext.fillStyle = 'white';
    canvasContext.font="20px monospace";
    canvasContext.fillText("ball speed", 100, 325);

    // box 6
    var box6 = {x:100, y:340, width:20, height:20};
    canvasContext.fillText("normal", box6.x + bt, 355);
    canvasContext.fillRect(box6.x, box6.y, box6.width, box6.height);
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.fillRect(102, 342, 16, 16);

    if (box_6_selected == true) {
        canvasContext.fillStyle = 'white';
        canvasContext.fillRect(104, 344, 12, 12);
    }

    // box 7
    var box7 = {x:100, y:367, width:20, height:20};
    canvasContext.fillStyle = 'white';
    canvasContext.fillText("fast 2x", box7.x + bt, 382);
    canvasContext.fillRect(box7.x, box7.y, box7.width, box7.height);
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.fillRect(102, 369, 16, 16);

    // box 7 selected
    if (box_7_selected == true) {
        canvasContext.fillStyle = 'white';
        canvasContext.fillRect(104, 371, 12, 12);
    }

    // box 8
    var box8 = {x:100, y:394, width:20, height:20};
    canvasContext.fillStyle = 'white';
    canvasContext.fillText("crazy 3x", box8.x + bt, 409);
    canvasContext.fillRect(box8.x, box8.y, box8.width, box8.height);
    canvasContext.fillStyle = GAME_COLOR;
    canvasContext.fillRect(102, 396, 16, 16);

    // box 7 selected
    if (box_8_selected == true) {
        canvasContext.fillStyle = 'white';
        canvasContext.fillRect(104, 398, 12, 12);
    }

    // check to see if the mouse is over the begin game link
    // if so, change the pointer to a hand, indicating a link
    var x = mousePosition.x;
    var y = mousePosition.y;

    onlink1 = isPointInsideRect(x, y, begin_game_link.x, begin_game_link.y, begin_game_link.width, begin_game_link.height);
    if (onlink1) {
        document.body.style.cursor = "pointer";
    } else {
        document.body.style.cursor = "";
    }

    // handle the case of a mouse click
    if (mouseClicked) {
        // check to see if the mouse clicked box1
        box_1_clicked = isPointInsideRect(mouseClickX, mouseClickY, box1.x, box1.y, box1.width, box1.height);
        if (box_1_clicked == true) {
            box_1_selected = true;
            box_2_selected = false;
            WINNING_SCORE = 3;
        }
        // check to see if the mouse clicked box 2
        box_2_clicked = isPointInsideRect(mouseClickX, mouseClickY, box2.x, box2.y, box2.width, box2.height);
        if (box_2_clicked == true) {
            box_1_selected = false;
            box_2_selected = true;
            WINNING_SCORE = 10;
        }

        // check to see if the mouse clicked box 3
        box_3_clicked = isPointInsideRect(mouseClickX, mouseClickY, box3.x, box3.y, box3.width, box3.height);
        if (box_3_clicked == true) {
            box_3_selected = true;
            box_4_selected = false;
            box_5_selected = false;
            GAME_COLOR = "black";
        }

        // check to see if the mouse clicked box 4
        box_4_clicked = isPointInsideRect(mouseClickX, mouseClickY, box4.x, box4.y, box4.width, box4.height);
        if (box_4_clicked == true) {
            box_3_selected = false;
            box_4_selected = true;
            box_5_selected = false;
            GAME_COLOR = "#003300";
        }

        // check to see if the mouse clicked box 5
        box_5_clicked = isPointInsideRect(mouseClickX, mouseClickY, box5.x, box5.y, box5.width, box5.height);
        if (box_5_clicked == true) {
            box_3_selected = false;
            box_4_selected = false;
            box_5_selected = true;
            GAME_COLOR = "#004080";
        }

        // check to see if the mouse clicked box 6
        box_6_clicked = isPointInsideRect(mouseClickX, mouseClickY, box6.x, box6.y, box6.width, box6.height);
        if (box_6_clicked == true) {
            box_6_selected = true;
            box_7_selected = false;
            box_8_selected = false;
            ballSpeedX = 10;
            ballSpeedY = 4;
        }

        // check to see if the mouse clicked box 7
        box_7_clicked = isPointInsideRect(mouseClickX, mouseClickY, box7.x, box7.y, box7.width, box7.height);
        if (box_7_clicked == true) {
            box_6_selected = false;
            box_7_selected = true;
            box_8_selected = false;
            ballSpeedX = 20;
            ballSpeedY = 8;
        }

        // check to see if the mouse clicked box 8
        box_8_clicked = isPointInsideRect(mouseClickX, mouseClickY, box8.x, box8.y, box8.width, box8.height);
        if (box_8_clicked == true) {
            box_6_selected = false;
            box_7_selected = false;
            box_8_selected = true;
            ballSpeedX = 30;
            ballSpeedY = 12;
        }

        // check to see if the mouse clicked the begin game link
        begin_game_link_clicked = isPointInsideRect(mouseClickX, mouseClickY, begin_game_link.x, begin_game_link.y, begin_game_link.width, begin_game_link.height);
        if (begin_game_link_clicked == true) {
            box_1_selected = false;
            box_2_selected = false;
            startNewGame = true;
            player1Score = 0;
            player2Score = 0;
            showGameOverScreen = false;
            document.body.style.cursor = "";
            startGame();
        }

    }
    // reset to false so that the mouse click only registers once in the game loop
    mouseClicked = false;

}

Notice that each box is defined as an object with x, y, width, height:

var box8 = {x:100, y:394, width:20, height:20};

And then the isPointInsideRect() function is used in conjunction with mouse click coordinates to determine if a box has been clicked:

box_8_clicked = isPointInsideRect(mouseClickX, mouseClickY, box8.x, box8.y, box8.width, box8.height);

At this point, the game is essentially complete. The only other things I can think of to add are sound effects and perhaps options for changing the computer AI player from the right to the left and maybe adding a two player option that uses keyboard arrows.

Here is the game as it stands at this point: