javascript - 为什么在我的 pong JavaScript 画布游戏中球不能完全反弹?
问题描述
我在 javascript 的画布上有多个省略号,我希望它们都能相互反弹。我尝试使用距离公式,然后在距离小于球半径 *2 时更改球的 x 和 y 方向。
这对一个球来说效果很好,但对许多球来说效果不佳,因为它经常导致这里描述的可怕的“反弹循环”
为了解决这个问题,我决定根据它们相互碰撞的位置来改变球的弹跳方式,以避免弹跳循环并使游戏更接近现实生活中的物理。
如果有左右碰撞,我想反转两个球的 x 方向,如果有从上到下的碰撞,我想反转两个球的 y 方向。
因此,我计算了所有点,例如,与度数相关的 45 度和 135 度之间的点(即 90 点),并将它们与 225 度和 315 度之间的所有 90 点进行比较,反之亦然。
如果圆边缘上的任何点与所有其他球中心点之间的距离小于半径,我希望两个球的 Y 方向反转。
我对 135 度和 225 度重复相同的过程到 315 度和 405 度(相当于 45 度)并反转两个球的 X 方向。
截至目前,我认为球应该按照我想要的方式相互反弹,但他们就是没有。它们从彼此的侧面和顶部、底部反弹,偶尔会以一定角度反弹,但它们往往会在彼此内部倾斜,然后改变方向。这是输出的视频。
下面是从上到下比较的代码:
// radius is the same for all the balls and is at 25.
let ballToBallDistance = (x1, y1, x2, y2) => {
return Math.sqrt((Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)));
}
const ballCollisionY = (start, end) => {
for (let i = start; i <= end; i++) {
return ballObjects[0].ballRadius * Math.sin((i * Math.PI / 180));
}
}
const ballCollisionX = (start, end) => {
for (let i = start; i <= end; i++) {
return ballObjects[0].ballRadius * Math.cos((i * Math.PI / 180));
}
}
const upperYBall = {
bounceTopBottom() {
let n = 0;
for (let i = 0; i < ballObjects.length; i++) {
if (ballObjects.length == 1) {
return;
}
if (n == i) {
continue;
}
let yUpXPoint = ballObjects[n].ballXPos - ballCollisionX(45, 135);
let yUpYPoint = ballObjects[n].ballYPos - ballCollisionY(45, 135);
let centerBallX = ballObjects[i].ballXPos;
let centerBallY = ballObjects[i].ballYPos;
let pointDistance = ballToBallDistance(yUpXPoint, yUpYPoint, centerBallX, centerBallY);
if (pointDistance <= 25) {
ballObjects[n].ballMotionY = ballObjects[n].ballMotionY * -1;
}
if (i == ballObjects.length - 1) {
++n;
i = -1;
continue;
}
}
}
}
const lowerYBall = {
bounceBottomTop() {
let n = 0;
for (let i = 0; i < ballObjects.length; i++) {
if (ballObjects.length == 1) {
return;
}
if (n == i) {
continue;
}
let yDownXPoint = ballObjects[n].ballXPos - ballCollisionX(225, 315);
let yDownYPoint = ballObjects[n].ballYPos - ballCollisionY(225, 315);
let centerBallX = ballObjects[i].ballXPos;
let centerBallY = ballObjects[i].ballYPos;
let pointDistance = ballToBallDistance(yDownXPoint, yDownYPoint, centerBallX, centerBallY);
if (pointDistance <= 25) {
ballObjects[n].ballMotionY = ballObjects[n].ballMotionY * -1;
}
if (i == ballObjects.length - 1) {
++n;
i = -1;
continue;
}
}
}
}
我已经在这个功能上停留了两个星期。如果有人对我做错了什么有任何见解,也许是实现预期结果的解决方案,那将不胜感激。
解决方案
我建议您从特殊情况编码切换到更通用的方法。
当两个球相撞时:
- 计算碰撞法线(角度)
- 根据之前的速度和法线计算新的速度
- 重新定位球,使它们不再重叠,防止“反弹循环”。
你会需要:
一种计算两个球之间角度的方法:
function ballToBallAngle(ball1,ball2) {
return Math.atan2(ball2.y-ball1.y,ball2.x-ball1.x)
}
从一个角度推导出法向量的方法:
function calcNormalFromAngle(angle){
return [
Math.cos(angle),
Math.sin(angle)
]
}
一种计算两个向量点积的方法:
function dotproduct (a, b){
return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n)
}
最后是一种计算反弹角的方法。阅读本文,它完美地描述了它。
因此,将其放在一起,请参见下面的代码段:
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
let balls = [
{x:40,y:40,radius:25,vx:4,vy:3},
{x:300,y:300,radius:50,vx:-2,vy:-3},
{x:100,y:220,radius:25,vx:4,vy:-3},
{x:400,y:400,radius:50,vx:-1,vy:-3},
{x:200,y:400,radius:32,vx:2,vy:-3}
]
function tick() {
balls.forEach((ball, index) => {
ball.x += ball.vx
ball.y += ball.vy
//check for x bounds collision
if (ball.x - ball.radius < 0) {
bounceBall(ball, Math.PI)
ball.x = ball.radius
} else if (ball.x + ball.radius > 500) {
bounceBall(ball, 0)
ball.x = 500 - ball.radius
}
//check for y bounds collision
if (ball.y - ball.radius < 0) {
bounceBall(ball, Math.PI / 2)
ball.y = ball.radius
} else if (ball.y + ball.radius > 500) {
bounceBall(ball, -Math.PI / 2)
ball.y = 500 - ball.radius
}
balls.forEach((other_ball, other_index) => {
if (index == other_index)
return
// how many px the balls intersect
let intersection = ball.radius + other_ball.radius - ballToBallDistance(ball, other_ball)
// if its greater than 0, they must be colliding
if (intersection > 0) {
let angle = ballToBallAngle(ball, other_ball)
let normal = calcNormalFromAngle(angle)
bounceBall(ball, angle)
bounceBall(other_ball, angle + Math.PI)
// set positions so that they are not overlapping anymore
ball.x -= normal[0] * intersection / 2
ball.y -= normal[1] * intersection / 2
other_ball.x += normal[0] * intersection / 2
other_ball.y += normal[1] * intersection / 2
}
})
})
render()
requestAnimationFrame(tick)
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
balls.forEach(ball => {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);
ctx.stroke();
})
}
function bounceBall(ball, angle) {
let normal = calcNormalFromAngle(angle)
let velocity = [ball.vx, ball.vy]
let ul = dotproduct(velocity, normal) / dotproduct(normal, normal)
let u = [
normal[0] * ul,
normal[1] * ul
]
let w = [
velocity[0] - u[0],
velocity[1] - u[1]
]
let new_velocity = [
w[0] - u[0],
w[1] - u[1]
]
ball.vx = new_velocity[0]
ball.vy = new_velocity[1]
}
function dotproduct(a, b) {
return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n)
}
function ballToBallDistance(ball1, ball2) {
return Math.sqrt((Math.pow(ball2.x - ball1.x, 2) + Math.pow(ball2.y - ball1.y, 2)));
}
function ballToBallAngle(ball1, ball2) {
return Math.atan2(ball2.y - ball1.y, ball2.x - ball1.x)
}
function calcNormalFromAngle(angle) {
return [
Math.cos(angle),
Math.sin(angle)
]
}
tick();
body{
background-color: #eee;
}
canvas{
background-color: white;
}
<canvas width="500" height="500"></canvas>
推荐阅读
- asp.net - ASP.Net Core 3.1 - 使用视图模型上传文件总是为 NULL
- java - Appium 滚动动作发生两次
- php - 在 Ubuntu 16.04 服务器上安装特定的 PHP 版本
- qliksense - 我们可以通过 Rest API 从 Qlik sense 中提取数据吗?
- go - 如何检查一行或字符串是否包含作为 Golang 中的模板化变量?
- shell - 如何对具有日期和时间的时间戳的文件进行排序
- python - 如何自定义页面类型模型的 IndexView - Wagtail Admin 中的列表视图
- python-3.x - 如何在 Python 中检查来自文件的用户输入?
- go - 成功连接 webscoket 后如何向特定 URI 发送消息?
- c# - HTTPWebRequest - GetResponse() - 在 SSIS C# 脚本中被远程主机强制关闭