提问人:Liam 提问时间:11/14/2023 更新时间:11/15/2023 访问量:41
HTML Canvas 球碰撞问题
HTML Canvas Ball Collision Issue
问:
我正在创建一个简单的程序来模拟围绕圆圈弹跳的球。我遇到过一个问题,球偶尔会与内圈碰撞并卡住。我不完全确定为什么会发生这种情况,但当我提高速度时,它确实会更频繁地发生。 这是我的代码:
main.js
import Ball from "./balls.js";
import GenericObject from "./genericObject.js";
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = innerWidth;
canvas.height = innerHeight;
const colors = ['#e81416', '#ffa500', '#faeb36', '#79c314', '#487de7', '#4b369d', '#70369d'];
const ballRadius = 40; //Radius for all balls
let balls = [];
let innerCircle = new GenericObject({
x: canvas.width / 2,
y: canvas.height / 2,
radius: 300,
color: '#ffffff'
});
//Function to spawn new ball at click inside inner circle
function getMousePosition(event) {
let mouseX = event.clientX;
let mouseY = event.clientY;
//Calculate distance between mouse click and center of inner circle
let distance = Math.sqrt((mouseX - innerCircle.position.x) ** 2 + (mouseY - innerCircle.position.y) ** 2) + ballRadius;
//If clicked inside circle, spawn a new ball
if (distance <= innerCircle.radius) {
balls.push(new Ball({
x: mouseX,
y: mouseY,
velX: 0,
velY: 6,
radius: ballRadius,
color: colors[Math.floor(Math.random() * colors.length)]
}));
}
}
canvas.addEventListener("mousedown", function (e) {
getMousePosition(e);
});
function animate() {
requestAnimationFrame(animate);
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
innerCircle.update();
balls.forEach((ball) => {
ball.update();
//Calculate distance between ball and center of inner circle
let distance = Math.sqrt((ball.position.x - innerCircle.position.x) ** 2 + (ball.position.y - innerCircle.position.y) ** 2) + ball.radius;
//Check for collision with inner circle
if (distance >= innerCircle.radius) {
//Filter color array to exclude current ball's color
const filteredArray = colors.filter(element => element !== ball.color);
//Randomly choose color from filtered color array
const randomIndex = Math.floor(Math.random() * filteredArray.length);
//Change ball's color
ball.color = filteredArray[randomIndex];
//Find collision angle
const angle = Math.atan2(ball.position.y, ball.position.x);
//Find angle perpendicular to collision angle
const normalAngle = angle + Math.PI / 2;
//Create new velocity for x and y
const newVelX = ball.velocity.x * Math.cos(2 * normalAngle) - ball.velocity.y * Math.sin(2 * normalAngle);
const newVelY = ball.velocity.x * Math.sin(2 * normalAngle) + ball.velocity.y * Math.cos(2 * normalAngle);
ball.velocity.x = newVelX;
ball.velocity.y = newVelY;
//Move ball away from collision point
ball.position.y += ball.velocity.y;
ball.position.x += ball.velocity.x;
} else {
//If no collision, continue moving ball
ball.position.y += ball.velocity.y;
ball.position.x += ball.velocity.x;
}
});
}
animate();
球.js
const canvas = document.querySelector('canvas')
const c = canvas.getContext('2d')
class Ball {
constructor({x, y, velX, velY, radius, color, mass}) {
this.position = {
x: x,
y: y
}
this.velocity = {
x: velX,
y: velY
}
this.radius = radius
this.color = color
}
draw() {
c.beginPath();
c.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
c.closePath();
}
update() {
this.draw()
}
}
export default Ball
genericObject.js
const canvas = document.querySelector('canvas')
const c = canvas.getContext('2d')
class GenericObject {
constructor({x, y, radius, color}) {
this.position = {
x: x,
y: y
}
this.radius = radius
this.color = color
}
draw() {
c.beginPath();
c.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
c.closePath();
}
update() {
this.draw()
}
}
export default GenericObject
答:
1赞
user3297291
11/15/2023
#1
问题
当球与边缘碰撞时,您会改变方向。如果新的方向和速度无法将球移动到框架内的碰撞之外,您将获得双重碰撞。
当有时颜色多次闪烁并且返回角度看起来不正常时,您可以看到这种情况发生。
在某些角度下,快速连续的碰撞会不断翻转方向,球永远无法解开。
可能的修复
您经常看到的两个修复已实现:
- 检查碰撞时向前看一帧,并改变路线以防止碰撞。
- 发生碰撞时,将碰撞的物体向后移动到刚好接触它的位置。
第二个通常是更好的修复,但第一个很容易实现,并且在您的示例中效果很好。
展望未来
下面我做了一个简单的更改:我没有检查与电流的碰撞,而是虚拟地添加到它并使用球的“下一个”位置。ball.position
ball.speed
const nextX = ball.position.x + ball.velocity.x;
const nextY = ball.position.y + ball.velocity.y;
let distance = Math.sqrt((nextX - innerCircle.position.x) ** 2 + (nextY - innerCircle.position.y) ** 2) + ball.radius;
换档到碰撞的确切位置
如果你想选择另一个,你可以:
- 计算过冲距离 (
distance - innerCircle.radius
) - 从内圆以球的速度与当前位置成一定角度的线上找到位置
-overshoot
- 在翻转速度之前,更新到这个“仅接触内圆表面”点。
ball.position
运行示例
注意:下面的示例显示了第一种方法的实现。
class GenericObject {
constructor({
x,
y,
radius,
color
}) {
this.position = {
x: x,
y: y
}
this.radius = radius
this.color = color
}
draw() {
c.beginPath();
c.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
c.closePath();
}
update() {
this.draw()
}
}
class Ball {
constructor({
x,
y,
velX,
velY,
radius,
color,
mass
}) {
this.position = {
x: x,
y: y
}
this.velocity = {
x: velX,
y: velY
}
this.radius = radius
this.color = color
}
draw() {
c.beginPath();
c.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
c.closePath();
}
update() {
this.draw()
}
}
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
const ctx = c;
canvas.width = innerWidth;
canvas.height = innerHeight;
const colors = ['#e81416', '#ffa500', '#faeb36', '#79c314', '#487de7', '#4b369d', '#70369d'];
const ballRadius = 40; //Radius for all balls
let balls = [];
let innerCircle = new GenericObject({
x: canvas.width / 2,
y: canvas.height / 2,
radius: 300,
color: '#ffffff'
});
//Function to spawn new ball at click inside inner circle
function getMousePosition(event) {
let mouseX = event.clientX;
let mouseY = event.clientY;
//Calculate distance between mouse click and center of inner circle
let distance = Math.sqrt((mouseX - innerCircle.position.x) ** 2 + (mouseY - innerCircle.position.y) ** 2) + ballRadius;
//If clicked inside circle, spawn a new ball
if (distance <= innerCircle.radius) {
balls.push(new Ball({
x: mouseX,
y: mouseY,
velX: 0,
velY: 6,
radius: ballRadius,
color: colors[Math.floor(Math.random() * colors.length)]
}));
}
}
canvas.addEventListener("mousedown", function(e) {
getMousePosition(e);
});
function animate() {
requestAnimationFrame(animate);
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
innerCircle.update();
balls.forEach((ball) => {
ball.update();
// Calculate distance between ball and center of inner circle
// Do this for the _next_ frame so we're sure we bounce _before_ we collide
const nextX = ball.position.x + ball.velocity.x;
const nextY = ball.position.y + ball.velocity.y;
let distance = Math.sqrt((nextX - innerCircle.position.x) ** 2 + (nextY - innerCircle.position.y) ** 2) + ball.radius;
//Check for collision with inner circle
if (distance >= innerCircle.radius) {
//Filter color array to exclude current ball's color
const filteredArray = colors.filter(element => element !== ball.color);
//Randomly choose color from filtered color array
const randomIndex = Math.floor(Math.random() * filteredArray.length);
//Change ball's color
ball.color = filteredArray[randomIndex];
//Find collision angle
const angle = Math.atan2(ball.position.y, ball.position.x);
//Find angle perpendicular to collision angle
const normalAngle = angle + Math.PI / 2;
//Create new velocity for x and y
const newVelX = ball.velocity.x * Math.cos(2 * normalAngle) - ball.velocity.y * Math.sin(2 * normalAngle);
const newVelY = ball.velocity.x * Math.sin(2 * normalAngle) + ball.velocity.y * Math.cos(2 * normalAngle);
ball.velocity.x = newVelX;
ball.velocity.y = newVelY;
//Move ball away from collision point
ball.position.y += ball.velocity.y;
ball.position.x += ball.velocity.x;
} else {
//If no collision, continue moving ball
ball.position.y += ball.velocity.y;
ball.position.x += ball.velocity.x;
}
});
}
animate();
<canvas></canvas>
评论