HTML Canvas圆形边界小球碰撞卡顿问题求助
小球圆形区域弹跳卡顿问题的修复方案
你遇到的小球碰撞圆形内壁后卡住、速度越快越频繁的问题,核心源于碰撞检测、角度计算、位置修正和反弹逻辑的多处错误,以下是具体原因和修复后的代码:
关键问题点
- 碰撞检测逻辑错误:原代码用「球心到圆心距离+球半径」和内圆半径比较,判断条件完全颠倒,导致球未触边或已超边才触发碰撞
- 角度计算错误:直接使用球相对于画布原点的角度,而非相对于内圆圆心的碰撞法线角度
- 位置修正不彻底:碰撞后仅单次叠加速度,无法确保球完全脱离碰撞区域,导致重复触发碰撞卡死
- 反弹公式错误:基于错误角度推导的反弹计算,无法得到正确的反射速度
修复后的完整代码
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; let balls = []; let innerCircle = new GenericObject({ x: canvas.width / 2, y: canvas.height / 2, radius: 300, color: '#ffffff' }); function getMousePosition(event) { let mouseX = event.clientX; let mouseY = event.clientY; // 修正生成球的碰撞检测:球心到圆心距离 <= 内圆半径 - 球半径 let distance = Math.sqrt((mouseX - innerCircle.position.x) ** 2 + (mouseY - innerCircle.position.y) ** 2); if (distance <= innerCircle.radius - ballRadius) { 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", getMousePosition); function animate() { requestAnimationFrame(animate); ctx.fillStyle = 'black'; ctx.fillRect(0, 0, canvas.width, canvas.height); innerCircle.update(); balls.forEach((ball) => { ball.update(); // 计算球心到内圆圆心的距离 const dx = ball.position.x - innerCircle.position.x; const dy = ball.position.y - innerCircle.position.y; const distance = Math.sqrt(dx ** 2 + dy ** 2); const maxAllowedDistance = innerCircle.radius - ball.radius; // 正确的碰撞判断:球心超出允许范围 if (distance >= maxAllowedDistance) { // 随机切换颜色(保留原有逻辑) const filteredArray = colors.filter(element => element !== ball.color); const randomIndex = Math.floor(Math.random() * filteredArray.length); ball.color = filteredArray[randomIndex]; // 计算碰撞法线(从圆心指向球心的单位向量) const normalX = dx / distance; const normalY = dy / distance; // 向量反射公式:计算反弹后的速度 const dotProduct = ball.velocity.x * normalX + ball.velocity.y * normalY; ball.velocity.x -= 2 * dotProduct * normalX; ball.velocity.y -= 2 * dotProduct * normalY; // 强制修正球心位置到合法边界,彻底脱离碰撞区域 ball.position.x = innerCircle.position.x + normalX * maxAllowedDistance; ball.position.y = innerCircle.position.y + normalY * maxAllowedDistance; } else { // 无碰撞时正常移动 ball.position.x += ball.velocity.x; ball.position.y += ball.velocity.y; } }); } animate();
balls.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
内容的提问来源于stack exchange,提问作者Liam
相关产品推荐
相关产品推荐

