弹跳球组件

Bouncing Ball Component

我有一个弹跳球功能,它显示一个绿色的弹跳球并在屏幕上来回弹跳。但是,反复使用这个功能,球似乎每次调用都在加速,球速急剧增加。

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();
    }

我们将不胜感激。

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);
  }

你调用这个函数一次,然后它通过 requestAnimationFrame(throwBall); 为之后的每一帧调用它自己。如果你再次调用这个函数,那么你现在又在做同样的事情,现在每帧调用两个 throwBall 函数,每帧更新位置和速度两次。然后,如果您第三次调用它,您将每帧更新三次位置和速度。

您通常想要做的是将每帧更新和渲染与会更改动画的事件分开。你在这里的功能实际上只是更新球的值,然后渲染球。它实际上并没有“抛出”任何东西。

那么让我们重命名该函数:

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

然后在 canvas 存在时只调用一次:

updateAndDrawBall();

现在要“扔”球,您需要做的就是改变它的速度,updateAndDrawBall 将在下次渲染时对这些新值进行操作。

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

现在,当你想推球时就调用它,次数不限。

工作示例:

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