You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 10:31:06