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.