PixiJS开发JS浏览器游戏碰撞反弹偶发失效如何解决
PixiJS 火箭与流星碰撞反弹偶发失效排查
问题根因
反弹效果偶发生效,核心是三个逻辑漏洞共同导致的:
- 同帧碰撞重复处理,速度被反复交换抵消:当前碰撞检测逻辑是「遍历每个物体作为current,再遍历所有其他物体做检测」,同一对碰撞体(比如火箭A、流星B)会被检测两次:第一次current=A、o=B时交换一次速度,第二次current=B、o=A时两个物体仍处于重叠状态,会再次触发速度交换,把速度换回初始值。console中看到的触发记录包含了这两次相反的交换操作,最终速度实际没有变化,自然看不到反弹效果。只有极少数帧里,第一次交换后物体刚好在同帧移动离开重叠区,第二次检测没触发,才会出现反弹效果。
- 无碰撞分离逻辑,重叠状态持续多帧:检测到碰撞后仅修改了速度,没有把两个嵌在一起的物体推开,只要物体相对速度不够大,后续多帧都会持续处于碰撞触发状态,反复来回交换速度。
- 无碰撞处理去重标记:没有记录当前帧已经处理过的碰撞对,无法规避重复响应。
修复方案
按以下步骤修改即可稳定实现反弹效果:
- 给所有游戏对象添加唯一标识id,用于标记已处理的碰撞对
- 每帧碰撞检测前初始化一个空Set,存储当前帧已经处理过的碰撞对key,避免重复处理
- 检测到碰撞后,先计算两个物体的重叠深度,将两个物体沿碰撞法线方向推开,彻底脱离重叠状态
- 再执行速度交换逻辑,处理完后把碰撞对的key存入Set,跳过后续重复检测
- 调整执行顺序:整轮碰撞检测全部完成后,再统一执行所有物体的移动逻辑,避免边检测边移动导致的判断误差
修正后的核心代码
首先在火箭、流星的构造函数中添加唯一id:
let objId = 0 // 构造函数内添加 this.id = objId++
修正后的碰撞检测函数:
export const checkCollision = (current, objects, processedCollisions) => { objects.forEach((o) => { if (current === o) return // 生成碰撞对唯一key,跳过已处理的碰撞对 const key1 = `${current.id}-${o.id}` const key2 = `${o.id}-${current.id}` if (processedCollisions.has(key1) || processedCollisions.has(key2)) return // 原有AABB碰撞检测逻辑 const dx = current.x < o.x ? o.x - o.width / 2 - (current.x + current.width / 2) : current.x - current.width / 2 - (o.x + o.width / 2); const dy = current.y < o.y ? o.y - o.height / 2 - (current.y + current.height / 2) : current.y - current.height / 2 - (o.y + o.height / 2); if (dx < 0 && dy < 0) { // 新增:碰撞分离逻辑 const overlapX = -dx const overlapY = -dy // 沿重叠深度更小的轴分离,更符合视觉碰撞逻辑 if (overlapX < overlapY) { const separateX = overlapX / 2 current.x += current.x < o.x ? -separateX : separateX o.x += current.x < o.x ? separateX : -separateX } else { const separateY = overlapY / 2 current.y += current.y < o.y ? -separateY : separateY o.y += current.y < o.y ? separateY : -separateY } // 原有速度交换逻辑 const cSpeedX = current.speed.x; const cSpeedY = current.speed.y; const oSpeedX = o.speed.x; const oSpeedY = o.speed.y; current.speed.x = oSpeedX; current.speed.y = oSpeedY; o.speed.x = cSpeedX; o.speed.y = cSpeedY; // 标记该碰撞对本帧已处理 processedCollisions.add(key1) } }); }
每帧游戏循环的执行逻辑同步调整:
// 每帧执行 const processedCollisions = new Set() // 先完成所有碰撞检测与响应 allGameObjects.forEach(obj => { checkCollision(obj, allGameObjects, processedCollisions) }) // 碰撞处理完再统一移动 allGameObjects.forEach(obj => { obj.move(delta) })
若需要更真实的物理效果,可以在速度交换环节加入弹性系数、按照物体质量分配速度变化量,上述修复已经可以彻底解决偶发不反弹的问题。
内容的提问来源于stack exchange,提问作者Finn
相关产品推荐
相关产品推荐

