检查两圆距离 实现Canvas中小球碰撞反弹功能
问题定位及修复方案
现有代码的核心问题
- 碰撞检测公式错误:勾股定理计算两点距离应为
Math.sqrt(dx*dx + dy*dy),原代码写的是减号,导致距离计算完全错误,大量不该触发碰撞的场景也被误判。 - 重复碰撞判定:双层循环每次都遍历所有小球,会对同一对小球做两次碰撞检测(i和j、j和i),会导致速度被连续翻转两次,相当于没生效甚至出现反向错乱。
- 碰撞后速度逻辑错误:只翻转x轴速度不符合物理规则,斜向碰撞时效果完全不符合预期,应该沿两个小球球心连线的法线方向计算速度分量再处理。
- 变量未声明:循环变量
i/j、dx/dy等未加let声明,全部泄漏为全局变量,容易触发不可预知的逻辑错误。
修复方案
只需修改gameloop中的小球碰撞检测逻辑即可,替换后的代码如下:
function gameloop() { //clear c.clearRect(0, 0, canvas.width, canvas.height); c.strokeStyle = 'white'; //move for (let i = 0; i < ballcount; i++) { const b1 = balls[i]; b1.x += b1.vx; b1.y += b1.vy; //wall collision if (b1.x <= b1.r) { b1.x = b1.r; b1.vx *= -1; } if (b1.y <= b1.r) { b1.y = b1.r; b1.vy *= -1; } if (b1.x >= canvas.width - b1.r) { b1.x = canvas.width - b1.r; b1.vx *= -1; } if (b1.y >= canvas.height - b1.r) { b1.y = canvas.height - b1.r; b1.vy *= -1; } //ball collision j从i+1开始避免重复判定同一对球 for (let j = i + 1; j < ballcount; j++) { const b2 = balls[j]; const dx = b2.x - b1.x; const dy = b2.y - b1.y; const distanceSquare = dx * dx + dy * dy; const rsSum = b1.r + b2.r; // 直接比较平方值,省略开根号提升性能 if (distanceSquare < rsSum * rsSum) { const distance = Math.sqrt(distanceSquare); // 计算碰撞法线单位向量 const nx = dx / distance; const ny = dy / distance; // 计算相对速度 const dvx = b2.vx - b1.vx; const dvy = b2.vy - b1.vy; // 相对速度在法线方向的投影 const dotProduct = dvx * nx + dvy * ny; // 如果两个球正在远离就不处理,防止重叠时反复翻转速度 if (dotProduct > 0) continue; // 质量相等的弹性碰撞,直接交换法线方向速度分量 b1.vx += dotProduct * nx; b1.vy += dotProduct * ny; b2.vx -= dotProduct * nx; b2.vy -= dotProduct * ny; // 处理重叠偏移,避免球卡在一起 const overlap = (rsSum - distance) / 2; b1.x -= overlap * nx; b1.y -= overlap * ny; b2.x += overlap * nx; b2.y += overlap * ny; } } } //draw c.beginPath(); for (let i = 0; i < ballcount; i++) { c.moveTo(balls[i].x + balls[i].r, balls[i].y); c.arc(balls[i].x, balls[i].y, balls[i].r, 0, Math.PI * 2); } c.stroke(); }
额外优化建议
- 小球初始化生成时可增加碰撞判定,避免初始位置就重叠,减少启动时的异常弹跳。
- 如果后续需要调整碰撞弹性,可以给速度更新逻辑增加弹性系数参数控制。
内容的提问来源于stack exchange,提问作者Fire Red
相关产品推荐
相关产品推荐

