HTML Pong Game Gameplay |
Pong is one of the first games that many people from the 80s or 90s had played as children. Lots of people know it as a simple arcade game but what they probably do not know is that this simple game helped establish the video game industry!
In this post, we'll be making our version of a very simple but fully working Pong game in HTML, CSS and JavaScript.
Basic Game Structure
Games, however simple or complex they may be, follow the basic Game Loop design as shown in the chart below. Event-oriented game engines usually encapsulate the design and provide you with an event mechanism for handling various parts like the input, update, and rendering but internally the basic design is being followed.
Pong Game Loop
For our Game Loop, we'll be using JavaScript setInterval so that the game code remains asynchronous and separate. As you may have guessed a while (or any other loop) will freeze the page. For our input, we'll be using onmousemove event to update the mouse Y coordinate global variable which we'll use inside the Game Loop to compute the player paddle position. Notice, how we are not doing the player position computation inside the mouse change event. I feel mixing Game Loop and events would add unnecessary complexity to the code.
Pong Game Assets
Download the pong game asset file and unzip it in the same directory as the game HTML file.
Complete Pong Game
The following is the complete source code for the game, you can copy-paste and save it in a file (be sure to put the game asset in the same directory, though). The code is well commented so be sure to have a read.
<h1 align="center">Pong Game Example in JavaScript</h1> <canvas width="840" height="400" id="box"> <img id="paddle1" src="https://s4.gifyu.com/images/paddle.png" /> <img id="paddle2" src="https://s4.gifyu.com/images/paddle.png" /> <img id="ball" src="https://s4.gifyu.com/images/ball_small.png" /> </canvas> <div id="msg" align="center"></div>
#box { padding: 0; margin: auto; display: block; border: 1px solid #333; }
// You can change these // Speed of ball (pixels/step) var ballSpeed = 3; // Speed of CPU Paddle (pixels/step) var cpuSpeed = 3; // Object coordinates var ballX, ballY; var paddle1X, paddle1Y; var paddle2X, paddle2Y; var mouseY; // HTML object references var paddle1; var paddle2; var ball; var box; var msg; var canvas, ctx; // For internal use var dx, dy; var isGameStarted = false; var intervalId; var isFrameInProgress = false; // Attach a function to onLoad event window.onload = init; function drawObject(ctx, sprite, x, y) { ctx.drawImage(sprite, x, y); } // Initialize game objects function init() { // Store references to objects canvas = document.getElementById("box"); ctx = canvas.getContext("2d"); paddle1 = document.getElementById("paddle1"); paddle2 = document.getElementById("paddle2"); ball = document.getElementById("ball"); box = document.getElementById("box"); msg = document.getElementById("msg"); // Initial values ballX = box.width / 2 - ball.width / 2; ballY = box.height / 2 - ball.height / 2; paddle1X = 20; paddle1Y = box.height / 2 - paddle1.height / 2; paddle2X = box.width - (20 + paddle2.width); paddle2Y = box.height / 2 - paddle2.height / 2; dx = dy = ballSpeed; // Add click event listener box.addEventListener("click", start); redraw(); // Show message msg.innerHTML = "<h2>Click on Paddle to Start Game.</h2>"; } // START GAME function start(e) { if (isGameStarted) { return; } // Update current mouse Y position updateMouseY(e); // Attach a function to onmousemove event of the box box.onmousemove = updateMouseY; // Call 'gameLoop()' function every 10 milliseconds intervalId = setInterval("gameLoop()", 10); msg.innerHTML = ""; isGameStarted = true; } // Redraw function redraw() { // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw everything drawObject(ctx, paddle1, paddle1X, paddle1Y); drawObject(ctx, paddle2, paddle2X, paddle2Y); drawObject(ctx, ball, ballX, ballY); } function end(win) { clearInterval(intervalId); box.onmousemove = ""; // Reset everything init(); if (win) { msg.innerHTML = "<h2>You Win!<br/>Click on Paddle to Re-Start Game.</h2>"; } else { msg.innerHTML = "<h2>You Loose!<br/>Click on Paddle to Re-Start Game.</h2>"; } isGameStarted = false; } // Main game loop, called by the JavaScript timer function gameLoop() { // Make sure that this is not invoked when one frame is // already in progress if (isFrameInProgress) { return; } isFrameInProgress = true; // INPUT // Player Paddle var y = mouseY - (box.offsetTop - document.documentElement.scrollTop); // Prevent the paddle from going outside of the canvas if (y > box.height - paddle1.height) { y = box.height - paddle1.height; } paddle1Y = y; // UPDATE // Ball ballX += dx; ballY += dy; // CPU Paddle if (dx > 0) { if (paddle2Y + paddle2.height / 2 > ballY + ball.height) paddle2Y -= cpuSpeed; else paddle2Y += cpuSpeed; } // Collision detection // If ball hits upper or lower wall if (ballY < 0 || ballY + ball.height > box.height) dy = -dy; // Make x direction opposite // If ball hits player paddle if (ballX < paddle1X + paddle1.width) if (ballY + ball.height > paddle1Y && ballY < paddle1Y + paddle1.height) dx = -dx; // If ball hits CPU paddle if (ballX + ball.width > paddle2X) if (ballY + ball.height > paddle2Y && ballY < paddle2Y + paddle2.height) dx = -dx; // END? // Win? // See if ball is past CPU paddle if (ballX + ball.width > box.width) { end(true); } // Lose? // See if ball is past player paddle if (ballX < 0) { end(false); } // RENDER redraw(); // Frame completed isFrameInProgress = false; } function updateMouseY(e) { mouseY = e.clientY; }
Click Here to Play the Live Example
Note
To keep the source code simple and easy to understand, I am using a lot of global variables that are being accessed in the scope of the functions which is not the best practice. A better design would be to use OOP and keep them separate and out of the global scope.
Possible Improvements
- Add a scoring system
- Add a pause or game restart system
- Make the CPU paddle AI more natural by maybe making it wait a random number of milliseconds before it starts responding to the oncoming ball.