使用JavaScript检测两个div元素碰撞的正确实现方法
太空侵略者碰撞检测实现方案
你当前碰撞逻辑失效的核心原因有两个:
- 仅使用
getBoundingClientRect()返回的元素左上角x、y坐标做比对,完全没有考虑元素自身的宽高占比,本质是在判断「两个坐标点是否完全重合」,自然无法识别矩形重叠 - 用
===做坐标值严格匹配不符合实际运行逻辑:元素通过CSS动画连续移动,10ms间隔的检测很难刚好捕获到两个坐标完全相等的瞬间
2D DOM游戏做矩形碰撞检测,直接用行业通用的AABB(轴对齐包围盒)判定即可,实现简单且性能极高,完全适配你的场景。
实现原理
你对getBoundingClientRect()的返回值理解有误,这个API不止返回左上角坐标,它会直接返回元素四个边界相对于视口的距离:
left:元素左边缘距视口左侧的距离top:元素上边缘距视口顶部的距离right:元素右边缘距视口左侧的距离(自动计算了元素宽度)bottom:元素下边缘距视口顶部的距离(自动计算了元素高度)
判定两个矩形碰撞不需要写复杂的重叠计算,反向判断「不碰撞」的场景即可:两个矩形只要不满足以下任意一种情况,就处于重叠状态:
- 子弹右边缘在敌人左边缘的左侧(子弹在敌人左边)
- 子弹左边缘在敌人右边缘的右侧(子弹在敌人右边)
- 子弹下边缘在敌人上边缘的上侧(子弹在敌人上方)
- 子弹上边缘在敌人下边缘的下侧(子弹在敌人下方)
代码修改
直接把你原有的collision_system函数替换成下面的代码即可,同时优化了原代码硬编码敌人ID的问题,后续增减敌人不需要改检测逻辑,命中后会自动销毁对应敌人和子弹:
const collision_system = () => { setInterval(() => { const enemies = document.querySelectorAll(".enemies > div"); const bullet = document.getElementById("bullet"); // 场上无子弹时直接跳过检测,减少不必要的性能消耗 if (!bullet) return; const bulletRect = bullet.getBoundingClientRect(); enemies.forEach(enemy => { // 已经被击毁的敌人直接跳过 if (!enemy.isConnected) return; const enemyRect = enemy.getBoundingClientRect(); // 碰撞判定 const isHit = !( bulletRect.right < enemyRect.left || bulletRect.left > enemyRect.right || bulletRect.bottom < enemyRect.top || bulletRect.top > enemyRect.bottom ); if (isHit) { enemy.remove(); bullet.remove(); } }) }, 10); };
可选优化点
- 目前你给子弹设置了固定id
bullet,后续如果要支持多子弹同时发射,不要用id标识子弹,统一用类名.div_bullet获取所有子弹遍历检测即可,避免同id导致的元素取值异常 - 用
setInterval做游戏循环容易出现掉帧卡顿,后续可以替换为requestAnimationFrame,时间精度更高,动画和检测的同步性更好 - 现有CSS动画驱动敌人移动的逻辑不需要调整,
getBoundingClientRect()可以实时拿到动画过程中的元素位置,不会出现检测偏差
内容的提问来源于stack exchange,提问作者LUCAS DE BIAZI FERREIRA
相关产品推荐
相关产品推荐

