Canvas多对象重力碰撞实现 堆叠场景对象穿透问题求助
问题根因
你当前的物理逻辑存在两个核心缺失:
- 无差别每帧为所有物体叠加y方向重力,没有区分物体是否处于被支撑的接触状态
- 物体间碰撞没有区分“高速冲击碰撞”和“低速持续接触”两种场景,导致堆叠时重力持续给下落物体加速,最终穿透相邻物体
核心修复思路
- 给每个物体增加接触状态标记,每帧物理计算前先重置标记,再通过AABB碰撞检测找出所有和其他物体/画布底面形成竖直支撑关系的对象
- 碰撞逻辑拆分两类处理:
- 当两个接触物体的竖直相对速度超过弹跳阈值时,执行原有的反向速度乘摩擦的弹跳逻辑,同时传递部分动量给下方物体
- 当相对速度低于弹跳阈值时,判定为持续接触堆叠状态,直接修正位置消除重叠,抵消重力带来的增量速度,不触发弹跳
- 重力叠加逻辑只作用于没有任何支撑的悬空物体,接触支撑的物体不叠加重力
- 静止判定完全嵌入主循环帧逻辑实现,不需要额外定时器——和物理更新同帧执行的判定逻辑不存在单线程时序问题
可直接复用的原生JS实现代码
// 复用你原有代码里的常量配置,不需要额外修改 const GRAVITY = 0.5 const FRICTION = 0.8 // 你原来用的摩擦系数 const BOUNCE_THRESHOLD = 1 // 超过这个速度才触发弹跳,低于则判定为接触堆叠 const canvas = document.querySelector('canvas') const ctx = canvas.getContext('2d') let objects = [] // 你原有逻辑里的物体列表 // 主循环物理更新部分,把原来的重力/位置更新逻辑替换成下面的代码 function physicsUpdate() { // 1. 每帧先重置所有物体的接触标记 objects.forEach(obj => { obj.isSupported = false obj.supportList = [] }) // 2. 处理物体间的AABB碰撞 for (let i = 0; i < objects.length; i++) { for (let j = i + 1; j < objects.length; j++) { const a = objects[i] const b = objects[j] // 计算重叠量 const overlapX = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x) const overlapY = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y) if (overlapX <= 0 || overlapY <= 0) continue // 无重叠,跳过 // 只处理竖直方向的碰撞(水平碰撞逻辑保留你原来的实现即可) if (overlapY < overlapX) { // 判断谁在上谁在下 if (a.y + a.height < b.y + b.height) { // a在b上方,b支撑a a.y -= overlapY // 位置修正,完全消除重叠 a.isSupported = true a.supportList.push(b) const relativeVy = a.vy - b.vy // 分冲击/接触两种情况处理 if (relativeVy > BOUNCE_THRESHOLD) { a.vy = -relativeVy * FRICTION // 原有弹跳逻辑 b.vy += relativeVy * (1 - FRICTION) // 简单动量传递 } else { a.vy = 0 // 接触状态,直接清掉下坠速度 } } else { // b在a上方,a支撑b b.y -= overlapY b.isSupported = true b.supportList.push(a) const relativeVy = b.vy - a.vy if (relativeVy > BOUNCE_THRESHOLD) { b.vy = -relativeVy * FRICTION a.vy += relativeVy * (1 - FRICTION) } else { b.vy = 0 } } } } } // 3. 处理画布底部边界碰撞 objects.forEach(obj => { if (obj.y + obj.height >= canvas.height) { obj.y = canvas.height - obj.height // 位置修正防穿透 obj.isSupported = true const hitSpeed = obj.vy if (hitSpeed > BOUNCE_THRESHOLD) { obj.vy = -hitSpeed * FRICTION // 原有边界弹跳逻辑 } else { obj.vy = 0 } } }) // 4. 重力叠加+位置更新 objects.forEach(obj => { // 只有悬空的物体才叠加重力 if (!obj.isSupported) { obj.vy += GRAVITY } // 原有位置更新逻辑 obj.y += obj.vy obj.x += obj.vx // 原有x方向边界摩擦逻辑完全保留,不需要改动 }) } // 你原有的requestAnimationFrame主循环,不需要修改 function gameLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height) physicsUpdate() objects.forEach(obj => obj.draw(ctx)) // 原有绘制逻辑 requestAnimationFrame(gameLoop) }
实现说明
- 你之前尝试加物体碰撞检测时大概率是无差别触发速度反向,没有对低速接触场景做隔离,导致堆叠物体只要有轻微重叠就触发弹跳,根本没法稳定堆叠。拆分碰撞类型后就不会出现这个问题。
- 整个静止状态是机制自然达成的:只要物体被支撑、速度降到阈值以下,就自动进入零速度静止状态,完全不需要额外监听坐标变化、定时校验,不存在单线程时序不可靠的问题。
- 原有的摩擦减速、x方向碰撞、单物体弹跳效果完全保留,只要冲击力超过阈值就会正常弹跳、按摩擦系数减速,直到速度降到阈值以下进入稳定堆叠状态,完全匹配你要的交互效果。
内容的提问来源于stack exchange,提问作者Paul Hashmi
相关产品推荐
相关产品推荐

