【问题标题】:Why won't the balls bounce fully in my pong JavaScript canvas game?为什么在我的 pong JavaScript 画布游戏中球不能完全反弹?
【发布时间】:2019-06-30 18:13:24
【问题描述】:

我在 javascript 中的画布上有多个省略号,我希望所有省略号相互反弹。我尝试使用距离公式,然后在距离小于球半径 *2 时改变球的 x 和 y 方向。

这对于一个球来说效果很好,但对于很多球来说效果并不好,因为它经常会导致描述为Here 的可怕“弹跳循环”

为了解决这个问题,我决定根据球相互碰撞的位置来改变球的弹跳方式,以避免弹跳循环并使游戏更接近现实生活中的物理学。

如果有侧面碰撞,我想反转两个球的 x 方向,如果有从上到下的碰撞,我想反转两个球的 y 方向。

因此,我计算了所有与度数相关的点,例如 45 度和 135 度之间的点(即 90 点),并将它们与 225 度和 315 度之间的所有 90 点进行比较,反之亦然。

如果圆边缘上的任何点与所有其他球中心点之间的距离小于半径,我希望两个球的 Y 方向反转。

我对 135 度和 225 度重复相同的过程到 315 度和 405 度(相当于 45 度)并反转两个球的 X 方向。

截至目前,我认为这些球应该按照我希望的方式相互反弹,但事实并非如此。它们从彼此的侧面和顶部、底部反弹,偶尔会以一定角度反弹,但它们往往会在彼此内部倾斜,然后改变方向。这是video of the output.

下面是上下对比的代码:

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

我已经在这个功能上停留了两个星期。如果有人对我做错了什么有任何见解,也许有一个解决方案来达到预期的结果,那将不胜感激。

【问题讨论】:

    标签: javascript arrays object for-loop continue


    【解决方案1】:

    我建议您从特殊情况编码转换为更通用的方法。

    当两个球相撞时:

    1. 计算碰撞法线(角度)
    2. 根据之前的速度和法线计算新的速度
    3. 重新定位球,使它们不再重叠,防止“反弹循环”。

    你需要:

    一种计算两个球之间角度的方法:

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

    最后一种计算反弹角的方法。 Read this,描述的很到位。

    所以把它放在一起,看下面的sn-p:

    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;
    }
    &lt;canvas width="500" height="500"&gt;&lt;/canvas&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-26
      • 1970-01-01
      • 2020-05-23
      • 2023-02-07
      • 1970-01-01
      • 2022-07-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多