Почему шарики не отскочили полностью в моей игре в понг с холстом на JavaScript? - PullRequest
0 голосов
/ 06 февраля 2019

У меня есть несколько elipses на холсте в javascript, и я хочу, чтобы все они отскакивали друг от друга.Я попытался использовать формулу расстояния, а затем изменить направление мяча по осям x и y, когда расстояние меньше радиуса шарика * 2.

Это сработало хорошо для одного шара, но не так хорошодля многих шаров, которые часто приводят к страшной «петле отскока», изображенной Здесь

Чтобы решить эту проблему, я решил изменить способ отскока шаров в зависимости от того, где они сталкиваются друг с другомчтобы избежать отказов и сделать игру ближе к реальной физике.

Если есть столкновение из стороны в сторону, я хочу изменить направление x обоих шаров, а если есть столкновение сверху вниз, я хочу изменить направление y обоих шаров.

Итак, я вычислил все точки, например, между 45 и 135 градусами, которые коррелируют с градусом (это 90 баллов), и сравнил их со всеми 90 точками между 225 и 315 градусами.наоборот.

Если расстояние между любой из точек на краю круга и центральной точкой всех других шаров меньше радиуса, я хочу направление 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;
            }
        }
    }
}

Я застрял на этой функции в течение двух недель.Если у кого-то есть понимание того, что я делаю неправильно, и, возможно, решение для достижения желаемого результата, это будет очень цениться.

1 Ответ

0 голосов
/ 06 февраля 2019

Я предлагаю вам перейти от кодирования специального случая к более общему подходу.

Когда сталкиваются два шара:

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

Наконец, способ расчета угла отскока. Прочитайте это , оно описывает это прекрасно.

Итак, чтобы сложить все вместе, посмотрите фрагмент ниже:

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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...