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

Canvas实现点击射击小球游戏时添加新球旧球消失问题求助

问题核心原因

  • 每个Ball实例都独立运行了requestAnimationFrame动画循环,并且在各自的animateBall方法里执行了clearRect清空整个画布,每个球的动画帧都会先清掉所有内容,再只绘制自己,自然看不到之前的旧球
  • 原有代码中animateBall直接传this.animateBall给requestAnimationFrame,执行时this会丢失绑定指向全局,后续运行会抛出属性未定义的错误
  • 原有逻辑把速度计算放到了drawBall方法中,每次绘制都会重新计算速度,也会导致小球运动逻辑异常

修复方案

把全局动画循环挪到Game类中统一管理,单次帧渲染的逻辑统一为:清画布 → 遍历所有ball实例依次更新位置 → 遍历所有ball实例依次绘制,同时移除Ball类里的独立动画循环和清画布逻辑。

修改后的Game类代码

class Game {
    constructor(props) {
        // 保留原有canvas、ctx、炮口位置cannonPos的初始化逻辑
        this.balls = []
        this.canvas.addEventListener('mousedown', (e) => {        
            this.handleMouseClicks(e)
        });
        // 启动全局统一动画循环
        this.animate()
    }

    handleMouseClicks(e) {             
         this.balls.push(new Ball({
             // 把上下文、点击坐标、炮口位置参数传给Ball实例,降低耦合
             ctx: this.ctx,
             mousePos: {x: e.offsetX, y: e.offsetY},
             cannonPos: this.cannonPos
         }))
   }

   animate() {
       // 全局单次帧仅清一次画布
       this.ctx.clearRect(0,0, this.canvas.width, this.canvas.height)
       // 遍历所有球更新位置
       this.balls.forEach(ball => ball.updateBall())
       // 遍历所有球绘制
       this.balls.forEach(ball => ball.drawBall())
       // 全局统一请求下一帧,箭头函数保留this指向
       requestAnimationFrame(() => this.animate())
   }
}

修改后的Ball类代码

class Ball {
    constructor(props) {
        // 接收初始化参数
        this.ctx = props.ctx
        this.mouseClickPosX = props.mousePos.x
        this.mouseClickPosY = props.mousePos.y
        this.cannonPosX = props.cannonPos.x
        this.cannonPosY = props.cannonPos.y
        // 初始化小球初始位置、速度,仅计算一次
        this.ballPosX = this.cannonPosX
        this.ballPosY = this.cannonPosY
        this.angle = Math.atan2(this.mouseClickPosY - this.cannonPosY, this.mouseClickPosX - this.cannonPosX)
        this.velocity = {
            x: Math.cos(this.angle),
            y: Math.sin(this.angle)
        }
        this.ballRadius = 2
        this.color = '#000' // 可自定义小球颜色
    }

    updateBall() {   
        this.ballPosX = this.ballPosX + this.velocity.x
        this.ballPosY = this.ballPosY + this.velocity.y
    }

    drawBall() {           
        this.ctx.beginPath()
        this.ctx.arc(this.ballPosX, this.ballPosY, this.ballRadius, 0, Math.PI * 2, false)
        this.ctx.closePath()
        this.ctx.fillStyle = this.color;
        this.ctx.fill()
    }
}

内容的提问来源于stack exchange,提问作者pileup

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 15:45:10