JavaScript Game Programming Tutorial: Making a Retro Wall Tennis Game Basic part

Game

Let’s make a simple game using just HTML and JavaScript, without any game engines or libraries.

This is for people who:

Want to try making a game but have no idea where to start.
Just want to make a simple game without diving into complex tools like Unity or Unreal Engine.

This guide is aimed at those who already have some programming knowledge.

What’s Covered in This Article

In this article, we’ll create the basic movements for a wall tennis game. Try pressing the left and right arrow keys on the black screen.

Setting Up the Environment

Before we start, this is a follow-up to another article. If you haven’t read it, check it out first.

Full Source Code

<html>
<body>
    <canvas id="gameCanvas" width="800" height="400"
            style="border:1px solid #000000; background-color: #000;"></canvas>
    <script>
        const canvas = document.getElementById('gameCanvas');
        const context = canvas.getContext('2d');

        // Ball
        const ballRadius = 10;
        let ballX = canvas.width / 2;
        let ballY = canvas.height / 2;
        let ballSpeedX = 2;
        let ballSpeedY = 2;

        // Paddle
        let padX = canvas.width / 2;
        const padY = canvas.height - 50;
        const padWidth = 100;
        const padHeight = 20;
        const padSpeed = 20;

        // Input event handler
        document.addEventListener('keydown', keyDownHandler, false);

        function drawBall() {
            context.fillStyle = '#fff';
            context.fillRect(ballX-ballRadius/2, ballY-ballRadius/2, ballRadius*2, ballRadius*2);
        }

        function drawPaddle() {
            context.fillStyle = '#fff';
            context.fillRect(padX, padY, padWidth, padHeight);
        }

        function keyDownHandler(event) {
            if (padX > 0) {
                if (event.key === 'Left' || event.key === 'ArrowLeft') {
                    padX -= padSpeed;
                } 
            }

            if (padX + padWidth < canvas.width) {
                if (event.key === 'Right' || event.key === 'ArrowRight') {
                    padX += padSpeed;
                }
            }
        }

        function isHitPaddle() {
            return ! (ballX < padX || ballX > padX+padWidth || ballY < padY || ballY+padHeight > padY+padHeight)
        }

        function update() {
            // update the ball position
            ballX += ballSpeedX;
            ballY += ballSpeedY;

            // Handling when the ball goes beyond the top or bottom boundaries.
            if (ballY + ballRadius > canvas.height || ballY - ballRadius < 0) {
                ballSpeedY = -ballSpeedY;
            }

            // Handling when the ball goes beyond the left or right boundaries.
            if (ballX - ballRadius < 0 || ballX + ballRadius > canvas.width) {
                ballSpeedX = -ballSpeedX;
            }

            if (isHitPaddle()) {
                ballSpeedY = -ballSpeedY;
            }

        }

        function draw() {
            context.clearRect(0, 0, canvas.width, canvas.height);
            drawBall();
            drawPaddle();
            update();
        }

        setInterval(draw, 10);
    </script>
</body>
</html>

Structure of the game

Here’s the basic setup for this demo:

The game area is 800×400 pixels. You set this size using the <canvas … width="800" height="400" …> tag. In JavaScript, we use canvas.width and canvas.height to refer to the size.

Let’s see how these properties are used in the source code. The following code checks if the ball hits the boundaries.

        // Handling when the ball goes beyond the top or bottom boundaries.
        if (ballY + ballRadius > canvas.height || ballY - ballRadius < 0) {
            ballSpeedY = -ballSpeedY;
        }

Why not just write the numbers 800 or 400 directly in the JavaScript code? Because if we want to change the canvas size later, we only need to change it in one place: the <canvas …> tag. If we had used numbers like 800 or 400 in the code, we would have to change them everywhere.

Moving the Ball and Detecting Hits

We keep track of the ball’s position with ballX and ballY. The ball moves based on its speed in the X and Y directions, which are stored in ballSpeedX and ballSpeedY. Here are their starting values:

    let ballX = canvas.width / 2;
    let ballY = canvas.height / 2;
    let ballSpeedX = 2;
    let ballSpeedY = 2;

The update() function gets called every 10 milliseconds. This means the ball moves by (ballSpeedX, ballSpeedY) every 10 milliseconds.

        // update the ball position
        ballX += ballSpeedX;
        ballY += ballSpeedY;

When the ball hits the screen’s edge, it bounces back. If it hits the sides, we reverse ballSpeedX; if it hits the top or bottom, we reverse ballSpeedY.

        // Handling when the ball goes beyond the top or bottom boundaries.
        if (ballY + ballRadius > canvas.height || ballY - ballRadius < 0) {
            ballSpeedY = -ballSpeedY;
        }

        // Handling when the ball goes beyond the left or right boundaries.
        if (ballX - ballRadius < 0 || ballX + ballRadius > canvas.width) {
            ballSpeedX = -ballSpeedX;
        }

The ball is drawn as a square centered at (ballX, ballY), extending by ballRadius in all directions, so we need to think about this when checking for collisions.

Moving the Paddle and Detecting Hits with the Ball

The paddle is just a rectangle. Drawing it is pretty much the same as drawing the ball. The only difference is the variables for x, y coordinates, width, and height used in context.fillRect().

    function drawPaddle() {
        context.fillStyle = '#fff';
        context.fillRect(padX, padY, padWidth, padHeight);
    }

The paddle only moves left and right. We’ll talk about what happens when it hits the edges later.

To see if the ball hits the paddle, we check if the ball’s coordinates (x, y) are inside the paddle’s rectangle.

Let me explain this idea using the y-coordinate as an example in the diagram below. The same applies to the x-coordinate.

Here’s how the code looks:

    function isHitPaddle() {
return ! (ballX < padX || ballX > padX+padWidth || ballY < padY || ballY+padHeight > padY+padHeight)
}

This code uses logic to check if the ball is outside the paddle and then uses ! to flip it, so we’re really checking if the ball is hitting the paddle.

This way of coding might be tricky for some people. Here’s another way using if statements:

   function isHitPaddle() {
        if (ballX < padX) {
            return false;
        }
        if (ballX > padX+padWidth) {
            return false;
        }
        if (ballY < padY) {
            return false;
        }
        if (ballY > padY+padHeight) {
            return false;
        }
        return true;
    }

This way is longer but maybe easier to understand. Which style you use is up to you, especially in hobby programming.

Why not just check if the ball is inside the paddle instead of outside? That would work too, but checking if it’s outside can be faster. Most of the time, the ball isn’t hitting the paddle, so it’s quicker to see if it’s outside first.

To check if the ball is inside the paddle, we need to confirm all four of these conditions every time:

  • ballX >= padX
  • ballX <= padX+padWidth
  • ballY >= padY
  • ballY <= padY+padWidth

On the other hand, to check if the ball is outside the paddle, we only need to see if it satisfies any one of the opposite conditions. In the order the program checks, ballX < padX is checked first, so if the ball is in this state, the check stops right there, and isHitPaddle() returns false.

Now, we’ve talked about how to detect if the ball hits the paddle, but some of you might think, “Wait, something’s off here.”

And you’re right. In this implementation, we’re not considering the ball’s size when checking for collisions with the paddle. We’re only using the ball’s coordinates (x, y), so it’s not quite finished yet. (We did consider the ball’s size when checking collisions with the screen edges, though.)

If you’re interested, try implementing a version that takes the ball’s size into account. It might be fun to see how the demo changes visually when you do this.

How to Handle Keyboard Input

The paddle moves left and right based on keyboard input. We handle this with the keyDownHandler function.

        function keyDownHandler(event) {
            if (padX > 0) {
                if (event.key === 'Left' || event.key === 'ArrowLeft') {
                    padX -= padSpeed;
                } 
            }

            if (padX + padWidth < canvas.width) {
                if (event.key === 'Right' || event.key === 'ArrowRight') {
                    padX += padSpeed;
                }
            }
        }

To make sure the paddle doesn’t go beyond the edges, we compare padX and padX + padWidth with the canvas area and only let it move if it’s within the area.

To catch keyboard events in the keyDownHandler function, we need to register it with the document object.

document.addEventListener('keydown', keyDownHandler, false);

What’s Next

That’s it for the basic parts of our wall tennis game. This is kind of like a prototype for the game’s main idea.

To make it more like a real game, we need to add things like game start and end status management and show the score. You have a lot of freedom to decide what counts as a point or when the game is over.

Next time, we’ll focus on managing the game status to make a small but complete game.


コメント

Copied title and URL