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: