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

JavaScript Canvas小球弹跳算法优化:实现对撞与gridSize卡顿修复

小球互相碰撞反弹逻辑修复

你当前写的碰撞逻辑有3个核心问题,直接修改如下即可:

  • 未排除当前小球自身,导致每帧都会触发碰撞判断
  • 重复处理同一组碰撞(小球A遍历到小球B时处理一次,小球B遍历到小球A时又处理一次,导致方向反转两次等于没反转)
  • 碰撞处理后额外修改了被碰撞小球的位置,后续循环到该小球时会再次更新位置,导致位移异常
  • 原边界判断有笔误,y方向边界误判用了settings.width,需要修正为settings.height

修复后的#work方法代码:

#work() {
    // 用Set记录已经处理过的碰撞对,避免重复处理
    const handledPairs = new Set();
    for (let i = 0; i < this.dots.length; i++) {
        const dot = this.dots[i];
        // 先处理边界碰撞
        if (dot.position.x + dot.direction.x * settings.gridSize >= settings.width - settings.gridSize) {
            dot.direction.x = -1;
        } else if (dot.position.x + dot.direction.x * settings.gridSize < 0) {
            dot.direction.x = 1;
        }

        if (dot.position.y + dot.direction.y * settings.gridSize >= settings.height - settings.gridSize) {
            dot.direction.y = -1;
        } else if (dot.position.y + dot.direction.y * settings.gridSize < 0) {
            dot.direction.y = 1;
        }

        // 处理小球间碰撞,只遍历索引比当前大的小球,避免重复判断
        for (let j = i + 1; j < this.dots.length; j++) {
            const cur = this.dots[j];
            const pairId = `${i}-${j}`;
            if (handledPairs.has(pairId)) continue;

            // 判断下一帧位置是否重叠或者当前已经重叠
            const nextDotX = dot.position.x + dot.direction.x * settings.gridSize;
            const nextDotY = dot.position.y + dot.direction.y * settings.gridSize;
            const nextCurX = cur.position.x + cur.direction.x * settings.gridSize;
            const nextCurY = cur.position.y + cur.direction.y * settings.gridSize;

            const isCollide = (nextDotX === nextCurX && nextDotY === nextCurY) || 
                              (dot.position.x === cur.position.x && dot.position.y === cur.position.y);
            
            if (isCollide) {
                // 网格移动场景下直接交换两个小球的方向即可实现反弹效果
                [dot.direction.x, cur.direction.x] = [cur.direction.x, dot.direction.x];
                [dot.direction.y, cur.direction.y] = [cur.direction.y, dot.direction.y];
                handledPairs.add(pairId);
            }
        }
        // 更新当前小球位置
        dot.position.x += dot.direction.x * settings.gridSize;
        dot.position.y += dot.direction.y * settings.gridSize;
    }
}
卡顿性能优化方案

卡顿和gridSize关联的核心原因是:gridSize越小,单步移动距离越短,相同移动速度下需要更高的帧率才能流畅,同时碰撞检测的计算量也会上升。可以从以下几个方面优化:

  • 替换定时器方案:把setInterval换成requestAnimationFrame,和屏幕刷新率同步,默认60fps流畅度远高于原来的20fps(50ms间隔),如果要控制移动速度可以加时间差计算:
    constructor() {
        this.#generateDots();
        this.lastTime = 0;
        const tick = (time) => {
            if (time - this.lastTime >= settings.duration) {
                this.#clear();
                this.#work();
                this.#draw();
                this.lastTime = time;
            }
            requestAnimationFrame(tick);
        }
        requestAnimationFrame(tick);
    }
    
  • 优化碰撞检测效率:原来的双重循环是O(n²)复杂度,dot数量多的时候卡顿明显。因为你的小球是严格网格对齐的,可以维护一个坐标映射表,直接查当前位置有没有其他小球,不用嵌套遍历所有小球:
    #work() {
        // 先构建当前坐标和小球索引的映射
        const posMap = new Map();
        this.dots.forEach((dot, idx) => {
            const key = `${dot.position.x},${dot.position.y}`;
            posMap.set(key, idx);
        });
        // 后续碰撞检测直接查posMap即可,复杂度降为O(n)
    }
    
  • 初始化优化:生成小球时判断位置是否重复的逻辑,用Set代替filter遍历,生成速度更快:
    #generateDots()  {
        this.dots = [];
        const existPos = new Set();
        while (this.dots.length < settings.dotCount) {
            let newDot = new Dot();
            const key = `${newDot.position.x},${newDot.position.y}`;
            if (!existPos.has(key)) {
                this.dots.push(newDot);
                existPos.add(key);
            }
        }
    }
    
  • 渲染优化:如果gridSize很小导致小球数量很多,可以批量绘制减少状态切换,比如把同色的小球放在一起绘制,避免频繁修改context.fillStyle。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 00:45:09