JavaScript - Game Part 5

In this lesson we will add collision between the ball and the paddles.


Now all we have to do to start playing the game is add collision between the ball and the paddles. Before we do that, let's make the ball reposition itself to the center of the screen every time it touches the left or right edge. First let's make a simple reposition() function and start the ball going toward the player paddle.

    let ballDX = -4;
    let ballDY = 0;
    

...

function reposition() { ballX = canvas.width / 2; ballY = canvas.height / 2; ballDX = -4; ballDY = 0; }

Now let's use this new function in the collision function. It will run when the ball touches the left or right walls. See if you can do it yourself before looking at our code.


Updated function:

    function handleCollision() {
        ballTop = ballY - BALL_SIZE;
        ballBottom = ballY + BALL_SIZE;
    
        if (ballX < 0) {
            reposition();
        }
        else if (ballX > canvas.width) {
            reposition();
        }
        // Handle collision with the walls
        else if (ballTop + ballDY < 0 || ballBottom > canvas.height) {
            ballDY = -ballDY;
        }
    }
            

Now the left side of your screen should look like this:

Now we need to add some collision between the paddle and the ball. The collision math for this step may be a little bit confusing, but try your best to understand the general concept. You can skip the explanation and come back later if you want.

Basically, we want the ball to go off at a wider angle if it is closer to the edge of the paddle compared to the middle.

We also want to make the ball go at a constant speed. To do so we can use simple trigonometry.

I'll explain the math while we code. First, let's add some new variables and a new bounceBall() function that will change the ball's motion depending on which paddle is hit. We can later use this function in the collision function.

    const PLAYER_PADDLE_DY = 6;
    const MAX_ANGLE = Math.PI / 3; // 60 degrees
    

...

let downIsPressed = false; let ballYTop; // stores the ball y position relative to the top of the paddle let ballYCenter; // stores the ball y position relative to the center of the paddle let relativeBallYCenter; // stores the ball y position relative to the center of the paddle between -1 and 1 let angle; // stores angle that the ball will bounce off of a paddle with let maxBallSpeed = 6; let mouseY;

...

function bounceBall(paddle) { }

All these variables will only be calculated when the ball is hitting a paddle.

The ballYTop variable stores the number of pixels below the top of the paddle that the center of the ball is. So, the calculation for this variable would be:

ballYTop = ballY - paddle.y;

The ballYCenter variable stores the distance between the ball and the center of the paddle. Since a paddle is 120 pixels high, this variable should range from -60 to +60 pixels. We can calculate this variable like this:

ballYCenter = ballYTop - PADDLE_HEIGHT / 2;

Now we can calculate the relativeBallYCenter variable, which stores the relative distance from the center of the paddle to the ball. Its value should only range from -1 to +1. Since the ballYCenter variable ranges from -60 to +60, we can just divide that variable by 60, or half the length of the paddle, to calculate relativeBallYCenter:

relativeBallYCenter = ballYCenter / (PADDLE_HEIGHT / 2);

We could have done this calculation with fewer variables, but we did it this way to make the math easier to understand.

Now we need to calculate the angle at which the ball will travel after it hits the paddle. This can be calculated by multiplying the maximum angle that we want the ball to have, in our case 60 degrees, by the relativeBallYCenter variable value. This determines an angle between +60 and -60 degrees at which the ball will travel depending on where it hits the paddle. You can also change the MAX_ANGLE variable to change the maximum angle that the ball will be able to travel at.

angle = MAX_ANGLE * relativeBallYCenter;

Now we will use some simple trigonometry to set the ball's x and y speeds:

ballDY = maxBallSpeed * Math.sin(angle);

ballDX = maxBallSpeed * Math.cos(angle);

To understand this look at this diagram:

This describes the relationship between the hypotenuse (longest side opposite the right angle) of a right triangle and the two "leg" sides. If the longest side is represented with x, then the leg opposite angle a is equal to the length of x multiplied by the sine of angle a. It is the same but with cosine with the leg perpendicular to angle a.

If we consider x to be equal to maxBallSpeed and a to be angle, then we are setting the speeds ballDY and ballDX in such a way to keep maxBallSpeed constant. We are setting ballDY and ballDX as the legs of a right triangle which have the same hypotenuse (maxBallSpeed) but variable values of angle.

Putting it all together, this is what the function should look like:

    function bounceBall(paddle) {
        // changes the angle of the ball depending on where it hits
        ballYTop = ballY - paddle.y;
        ballYCenter = ballYTop - PADDLE_HEIGHT / 2;
        relativeBallYCenter = ballYCenter / (PADDLE_HEIGHT / 2);
        angle = MAX_ANGLE * relativeBallYCenter;
    
        // The trig keeps the speed of the ball constant
        ballDY = maxBallSpeed * Math.sin(angle);
        ballDX = maxBallSpeed * Math.cos(angle);
    }
            

Now we can implement this in the collision function. You can fiddle with the collision conditions yourself, but this is what I will be using:

    function handleCollision() {
        ballTop = ballY - BALL_SIZE;
        ballBottom = ballY + BALL_SIZE;
    
        if (ballX < 0) {
            reposition();
        }
        else if (ballX > canvas.width) {
            reposition();
        }
        // Handle collision with the walls
        else if (ballTop + ballDY < 0 || ballBottom > canvas.height) {
            ballDY = -ballDY;
        }
    
        // Handle collision with the player paddle on the left
        // true if left side of the ball is within the paddle
        if ((ballX - BALL_SIZE + ballDX < playerPaddle.x + PADDLE_WIDTH) && 
            (ballX - BALL_SIZE + ballDX > playerPaddle.x) &&
            (ballY < playerPaddle.y + PADDLE_HEIGHT) && 
            (ballY > playerPaddle.y)) {
            bounceBall(playerPaddle);
        }
    }
            

This new if statement activates if the ball hits the player paddle. Now the player paddle should work as expected. However, we still need to edit this function for the opponent paddle. We need to edit the collision function to activate the bounceBall() function when the ball hits the opponent paddle:

    function handleCollision() {
        ballTop = ballY - BALL_SIZE;
        ballBottom = ballY + BALL_SIZE;
    
        if (ballX < 0) {
            reposition();
        }
        else if (ballX > canvas.width) {
            reposition();
        }
        // Handle collision with the walls
        else if (ballTop + ballDY < 0 || ballBottom > canvas.height) {
            ballDY = -ballDY;
        }
    
        // Handle collision with the player paddle on the left
        // true if left side of the ball is within the paddle
        if ((ballX - BALL_SIZE + ballDX < playerPaddle.x + PADDLE_WIDTH) && 
            (ballX - BALL_SIZE + ballDX > playerPaddle.x) &&
            (ballY < playerPaddle.y + PADDLE_HEIGHT) && 
            (ballY > playerPaddle.y)) {
            ballBounce(playerPaddle);
        }
    
        // Handle collision with the opponent paddle on the right
        // true if right side of the ball is within the paddle
        if ((ballX + BALL_SIZE + ballDX < opponentPaddle.x + PADDLE_WIDTH) && 
            (ballX + BALL_SIZE + ballDX > opponentPaddle.x) &&
            (ballY < opponentPaddle.y + PADDLE_HEIGHT) && 
            (ballY > opponentPaddle.y)) {
            ballBounce(opponentPaddle);
        }
    }
            

However, since we need to make the ball go in the opposite x direction when it hits the opponent paddle, we need to edit the bounceBall() function to make ballDX negative when the ball hits the opponent paddle. This is where the name property of the paddles is useful.

    function bounceBall(paddle) {
        // changes the angle of the ball depending on where it hits
        ballYTop = ballY - paddle.y;
        ballYCenter = ballYTop - PADDLE_HEIGHT / 2;
        relativeBallYCenter = ballYCenter / (PADDLE_HEIGHT / 2);
        angle = MAX_ANGLE * relativeBallYCenter;
    
        // The trig keeps the speed of the ball the constant
        if (paddle.name === "player") {
            ballDY = maxBallSpeed * Math.sin(angle);
            ballDX = maxBallSpeed * Math.cos(angle); 
        }
        else if (paddle.name === "opponent") {
            ballDY = maxBallSpeed * Math.sin(angle);
            ballDX = -maxBallSpeed * Math.cos(angle);
        }
    }
            

Now the paddles can pass the ball between each other.

Note: the paddle is chasing the ball because of the optional ball chasing code from part 4.

I am going to add one more line of code that speeds up the ball every time it hits a paddle:

    function bounceBall(paddle) {
        // Changes the angle of the ball depending on where it hits
        ballYTop = ballY - paddle.y;
        ballYCenter = ballYTop - PADDLE_HEIGHT / 2;
        relativeBallYCenter = ballYCenter / (PADDLE_HEIGHT / 2);
        angle = MAX_ANGLE * relativeBallYCenter;
    
        // The trig keeps the speed of the ball the constant
        if (paddle.name === "player") {
            ballDY = maxBallSpeed * Math.sin(angle);
            ballDX = maxBallSpeed * Math.cos(angle); 
        }
        else if (paddle.name === "opponent") {
            ballDY = maxBallSpeed * Math.sin(angle);
            ballDX = -maxBallSpeed * Math.cos(angle);
        }
    
        maxBallSpeed += 1; // Speeds up the ball every hit
    }
            

We should also update the reposition function to reset the max ball speed:

    function reposition() {
        ballX = canvas.width / 2;
        ballY = canvas.height / 2;
        ballDX = -4;
        ballDY = 0;
        maxBallSpeed = 6;
    }
            

Now it's finally time to add the score and winning.

Source code

< Previous Next >