Bouncing Ball Component

Issue

I have a bouncing ball function that displays a green bouncing ball and bounces it around on the screen. However, on repeated use of this function, the ball seems to gather speed upon every call and the ball speed increases drastically.

function throwBall() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        if (!ballState.paused) {
            requestAnimationFrame(throwBall);
        }

        if (ballState.cx + ballRadius >= canvas.width) {
            ballState.vx = -ballState.vx * damping;
            ballState.cx = canvas.width - ballRadius;
        } else if (ballState.cx - ballRadius <= 0) {
            ballState.vx = -ballState.vx * damping;
            ballState.cx = ballRadius;
        }
        if (ballState.cy + ballRadius + floor >= canvas.height) {
            ballState.vy = -ballState.vy * damping;
            ballState.cy = canvas.height - ballRadius - floor;
            // traction here
            ballState.vx *= traction;
        } else if (ballState.cy - ballRadius <= 0) {
            ballState.vy = -ballState.vy * damping;
            ballState.cy = ballRadius;
        }

        ballState.vy += gravity;

        ballState.cx += ballState.vx;
        ballState.cy += ballState.vy;

        ctx.beginPath();
        ctx.arc(ballState.cx, ballState.cy, ballRadius, 0, 2 * Math.PI, false);
        ctx.fillStyle = '#2ed851';
        ctx.fill();
    }

All help will be appreciated.

Solution

However, on repeated use of this function, the ball seems to gather speed upon every call and the ball speed increases drastically.

I think the problem is here:

function throwBall() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  if (!ballState.paused) {
    requestAnimationFrame(throwBall);
  }

You call this function once, and then it calls itself for every frame after that via the requestAnimationFrame(throwBall);. If you call this function again, then you are now doing the same thing again and now two throwBall functions get called every frame, which updates the position and velocity twice each frame. Then if you call it a third time, you are updating the position and velocity three times per frame.

What you typically want to do is separate your per frame updates and rendering from your events that would change the animation. The function you have here is actually just updates the ball’s values, and then then renders the ball. It doesn’t actually "throw" anything.

So let’s rename that function:

function updateAndDrawBall() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  if (!ballState.paused) {
    requestAnimationFrame(updateAndDrawBall);
  }
  //...
}

Then call this only once when the canvas exists:

updateAndDrawBall();

Now to "throw" the ball, all you need to do is change the velocity of it, and the updateAndDrawBall will operate on those new values when it next renders.

function throwBall() {
  ballState.vx = 10
  ballState.vy = -10
}

Now call that when you wan to give the ball a shove, as many times as you like.

Working example:

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

const ballState = {
  cx: 100,
  cy: 100,
  vx: 10,
  vy: -10,
  paused: false
};

const floor = 0;
const ballRadius = 20;
const damping = 0.85;
const traction = 0.9;
const gravity = 0.2;

function updateAndDrawBall() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  if (!ballState.paused) {
    requestAnimationFrame(updateAndDrawBall);
  }

  if (ballState.cx + ballRadius >= canvas.width) {
    ballState.vx = -ballState.vx * damping;
    ballState.cx = canvas.width - ballRadius;
  } else if (ballState.cx - ballRadius <= 0) {
    ballState.vx = -ballState.vx * damping;
    ballState.cx = ballRadius;
  }
  if (ballState.cy + ballRadius + floor >= canvas.height) {
    ballState.vy = -ballState.vy * damping;
    ballState.cy = canvas.height - ballRadius - floor;
    // traction here
    ballState.vx *= traction;
  } else if (ballState.cy - ballRadius <= 0) {
    ballState.vy = -ballState.vy * damping;
    ballState.cy = ballRadius;
  }

  ballState.vy += gravity;

  ballState.cx += ballState.vx;
  ballState.cy += ballState.vy;

  ctx.beginPath();
  ctx.arc(ballState.cx, ballState.cy, ballRadius, 0, 2 * Math.PI, false);
  ctx.fillStyle = "#2ed851";
  ctx.fill();
}
updateAndDrawBall(); // call this only once

function throwBall() {
  ballState.vx = 10
  ballState.vy = -10
}

document.getElementById('throw')?.addEventListener('click', throwBall)
<html>
  <head>
    <title>Parcel Sandbox</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <button id="throw" value="Throw Ball">Throw Ball</button>
    <br />
    <canvas id="canvas" width="200" height="200" />

    <script src="src/index.ts"></script>
  </body>
</html>

Typescript example on code sandbox

Answered By – Alex Wayne

Answer Checked By – Candace Johnson (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.