news 2026/5/7 14:18:35

游戏开发实战:用JavaScript手写AABB和OBB碰撞检测(附完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
游戏开发实战:用JavaScript手写AABB和OBB碰撞检测(附完整代码)

游戏开发实战:用JavaScript手写AABB和OBB碰撞检测(附完整代码)

在HTML5游戏开发中,碰撞检测是实现游戏交互的核心技术之一。无论是简单的休闲游戏还是复杂的物理模拟系统,都需要精确判断游戏对象之间的接触关系。本文将深入探讨两种主流的碰撞检测算法——AABB(轴对齐包围盒)和OBB(定向包围盒),通过原生JavaScript实现完整代码,并分析它们在Canvas/WebGL游戏中的实际应用技巧。

1. 碰撞检测基础与算法选型

当两个游戏对象在屏幕上移动时,系统需要实时计算它们的空间位置关系。碰撞检测算法根据精度和性能需求可分为多个层级:

  • 粗略检测:快速筛选可能发生碰撞的对象对
  • 精细检测:准确计算复杂形状间的接触关系
  • 持续检测:处理高速移动物体的穿透问题

对于2D游戏开发,AABB和OBB是最常用的两种基础算法。我们通过一个性能对比表格来直观了解它们的特性:

特性AABBOBB
计算复杂度O(1) 简单比较O(n) 需要投影计算
内存占用4个数值(x,y,w,h)中心点+尺寸+旋转角度
旋转支持不支持完全支持
适用场景静态或轴对齐物体任意旋转状态的物体
典型应用初级碰撞筛选精确碰撞判定

在实际项目中,开发者常采用两阶段检测策略:先用AABB快速筛选可能的碰撞对,再对候选对象使用OBB进行精确判断。这种混合方式在《愤怒的小鸟》等知名游戏中得到验证,可平衡性能与精度需求。

2. AABB算法实现与优化技巧

AABB的核心思想是将物体包裹在与坐标轴对齐的矩形中,通过比较坐标范围来判定碰撞。以下是基础实现:

class AABB { constructor(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; } collidesWith(other) { return ( this.x < other.x + other.width && this.x + this.width > other.x && this.y < other.y + other.height && this.y + this.height > other.y ); } }

性能优化实践

  1. 空间分区:将游戏世界划分为网格,只检测相邻网格中的对象
  2. 脏矩形技术:仅对发生移动的对象进行检测
  3. 固定点优化:对于UI元素等静态对象缓存检测结果

注意:AABB检测时应确保宽度和高度为非负数,否则需要添加Math.abs()处理

在Canvas环境中,可以扩展AABB类添加可视化方法便于调试:

class AABB { // ...原有代码... draw(ctx, color = 'red') { ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.strokeRect(this.x, this.y, this.width, this.height); } }

3. OBB算法与分离轴定理详解

OBB通过分离轴定理(SAT)实现旋转物体的精确碰撞检测。关键步骤包括:

  1. 计算每个OBB的顶点坐标
  2. 获取所有需要检测的分离轴
  3. 将物体投影到各个轴上
  4. 检查投影是否重叠

完整实现如下:

class OBB { constructor(center, width, height, angle = 0) { this.center = center; this.width = width; this.height = height; this.angle = angle * Math.PI / 180; // 转为弧度 } get vertices() { const cos = Math.cos(this.angle); const sin = Math.sin(this.angle); const hw = this.width / 2; const hh = this.height / 2; return [ { // 右上顶点 x: this.center.x + cos * hw - sin * hh, y: this.center.y + sin * hw + cos * hh }, { // 左上顶点 x: this.center.x - cos * hw - sin * hh, y: this.center.y - sin * hw + cos * hh }, { // 左下顶点 x: this.center.x - cos * hw + sin * hh, y: this.center.y - sin * hw - cos * hh }, { // 右下顶点 x: this.center.x + cos * hw + sin * hh, y: this.center.y + sin * hw - cos * hh } ]; } get axes() { const v = this.vertices; return [ { // 第一条边的法线 x: v[1].y - v[0].y, y: -(v[1].x - v[0].x) }, { // 第二条边的法线 x: v[2].y - v[1].y, y: -(v[2].x - v[1].x) } ].map(axis => this.normalize(axis)); } normalize(vector) { const len = Math.sqrt(vector.x * vector.x + vector.y * vector.y); return { x: vector.x / len, y: vector.y / len }; } project(axis) { const dots = this.vertices.map(v => v.x * axis.x + v.y * axis.y); return { min: Math.min(...dots), max: Math.max(...dots) }; } collidesWith(other) { const axes = [...this.axes, ...other.axes]; for (const axis of axes) { const proj1 = this.project(axis); const proj2 = other.project(axis); if (proj1.max < proj2.min || proj2.max < proj1.min) { return false; } } return true; } }

常见问题解决方案

  • 浮点精度问题:添加容差阈值(如1e-6)比较投影范围
  • 性能瓶颈:对静态物体缓存投影结果
  • 顶点顺序:确保顶点按顺时针或逆时针统一排列

4. 实战:弹球游戏Demo开发

我们将创建一个完整的弹球示例,演示两种算法的实际应用:

<!DOCTYPE html> <html> <head> <title>碰撞检测Demo</title> <style> canvas { background: #f0f0f0; display: block; margin: 0 auto; } .controls { text-align: center; margin: 10px; } </style> </head> <body> <div class="controls"> <button id="toggleMode">切换AABB/OBB模式</button> </div> <canvas id="gameCanvas" width="800" height="500"></canvas> <script> // 此处插入前述AABB和OBB类实现 class Ball { constructor(x, y, radius, color) { this.x = x; this.y = y; this.radius = radius; this.color = color; this.vx = (Math.random() - 0.5) * 5; this.vy = (Math.random() - 0.5) * 5; this.rotation = 0; this.rotationSpeed = (Math.random() - 0.5) * 0.1; } update() { this.x += this.vx; this.y += this.vy; this.rotation += this.rotationSpeed; // 边界检测 if (this.x < this.radius || this.x > canvas.width - this.radius) { this.vx *= -1; } if (this.y < this.radius || this.y > canvas.height - this.radius) { this.vy *= -1; } } getAABB() { return new AABB( this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2 ); } getOBB() { return new OBB( { x: this.x, y: this.y }, this.radius * 2, this.radius * 2, this.rotation * 180 / Math.PI ); } draw(ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.rotation); ctx.fillStyle = this.color; ctx.beginPath(); ctx.rect(-this.radius, -this.radius, this.radius * 2, this.radius * 2); ctx.fill(); // 绘制对角线便于观察旋转 ctx.strokeStyle = 'white'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(-this.radius, -this.radius); ctx.lineTo(this.radius, this.radius); ctx.moveTo(this.radius, -this.radius); ctx.lineTo(-this.radius, this.radius); ctx.stroke(); ctx.restore(); } } // 初始化游戏 const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const toggleBtn = document.getElementById('toggleMode'); let balls = []; let useAABB = true; function init() { balls = []; for (let i = 0; i < 10; i++) { balls.push(new Ball( Math.random() * (canvas.width - 100) + 50, Math.random() * (canvas.height - 100) + 50, Math.random() * 20 + 15, `hsl(${Math.random() * 360}, 70%, 60%)` )); } } function checkCollisions() { for (let i = 0; i < balls.length; i++) { for (let j = i + 1; j < balls.length; j++) { let isColliding = false; if (useAABB) { isColliding = balls[i].getAABB().collidesWith(balls[j].getAABB()); } else { isColliding = balls[i].getOBB().collidesWith(balls[j].getOBB()); } if (isColliding) { // 简单碰撞响应 const dx = balls[j].x - balls[i].x; const dy = balls[j].y - balls[i].y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { const nx = dx / distance; const ny = dy / distance; // 交换速度 const tempVx = balls[i].vx; const tempVy = balls[i].vy; balls[i].vx = balls[j].vx; balls[i].vy = balls[j].vy; balls[j].vx = tempVx; balls[j].vy = tempVy; } } } } } function gameLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 更新和绘制所有球 balls.forEach(ball => { ball.update(); ball.draw(ctx); // 绘制碰撞盒 ctx.save(); ctx.strokeStyle = useAABB ? 'blue' : 'green'; ctx.lineWidth = 1; ctx.setLineDash([5, 5]); if (useAABB) { const aabb = ball.getAABB(); ctx.strokeRect(aabb.x, aabb.y, aabb.width, aabb.height); } else { const obb = ball.getOBB(); const v = obb.vertices; ctx.beginPath(); ctx.moveTo(v[0].x, v[0].y); for (let i = 1; i <= 4; i++) { ctx.lineTo(v[i % 4].x, v[i % 4].y); } ctx.stroke(); } ctx.restore(); }); checkCollisions(); requestAnimationFrame(gameLoop); } toggleBtn.addEventListener('click', () => { useAABB = !useAABB; toggleBtn.textContent = useAABB ? '切换至OBB模式' : '切换至AABB模式'; }); init(); gameLoop(); </script> </body> </html>

这个Demo展示了:

  • 10个随机颜色和大小的方块在屏幕中弹跳
  • 可切换AABB/OBB两种碰撞检测模式
  • 碰撞后简单的速度交换响应
  • 可视化显示碰撞包围盒

性能优化建议

  1. 对远离的对象跳过精确检测
  2. 使用四叉树等空间分区技术
  3. 对静态对象预计算包围盒
  4. 在WebGL中可将计算转移到着色器

5. 高级应用与扩展方向

掌握了基础碰撞检测后,开发者可以进一步探索:

多边形碰撞检测

class Polygon { constructor(vertices) { this.vertices = vertices; } get edges() { const edges = []; for (let i = 0; i < this.vertices.length; i++) { const p1 = this.vertices[i]; const p2 = this.vertices[(i + 1) % this.vertices.length]; edges.push({ x: p2.x - p1.x, y: p2.y - p1.y }); } return edges; } get axes() { return this.edges.map(edge => { // 获取边的法线(分离轴) return { x: -edge.y, y: edge.x }; }); } project(axis) { let min = Infinity; let max = -Infinity; this.vertices.forEach(vertex => { const dot = vertex.x * axis.x + vertex.y * axis.y; min = Math.min(min, dot); max = Math.max(max, dot); }); return { min, max }; } collidesWith(other) { const axes = [...this.axes, ...other.axes]; for (const axis of axes) { const proj1 = this.project(axis); const proj2 = other.project(axis); if (proj1.max < proj2.min || proj2.max < proj1.min) { return false; } } return true; } }

其他进阶技术

  • GJK算法:更高效的凸体碰撞检测
  • EPA算法:计算碰撞穿透深度和方向
  • 连续碰撞检测(CCD):防止高速物体穿透
  • 物理引擎集成:与Box2D、Matter.js等配合使用

在真实项目开发中,碰撞检测往往需要与特定游戏引擎结合。例如在Phaser中:

// Phaser中的碰撞检测示例 this.physics.add.collider(player, platforms); this.physics.add.overlap(player, stars, collectStar, null, this);

对于需要更高性能的场景,可以考虑使用WebAssembly来重计算密集型部分。现代浏览器已经能够很好地支持WASM,这为复杂物理模拟提供了可能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 14:17:30

PyQtGraph避坑指南:从安装到OpenGL加速,解决Windows/macOS上的常见报错

PyQtGraph实战避坑指南&#xff1a;从环境配置到OpenGL加速全解析 刚接触PyQtGraph的开发者常会陷入这样的困境——明明按照官方文档操作&#xff0c;却频频遭遇环境报错、黑屏卡顿或性能瓶颈。这些问题往往与Qt绑定版本冲突、Python环境隔离不足、显卡驱动兼容性等底层因素相关…

作者头像 李华
网站建设 2026/5/7 14:13:27

如何快速备份QQ空间:GetQzonehistory一键保存青春记忆的终极指南

如何快速备份QQ空间&#xff1a;GetQzonehistory一键保存青春记忆的终极指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否担心珍贵的QQ空间回忆会随着时间流逝而消失&#xff…

作者头像 李华
网站建设 2026/5/7 14:13:25

Python脚本备份华为交换机配置时,你可能遇到的3个坑及解决办法

Python脚本备份华为交换机配置时&#xff0c;你可能遇到的3个坑及解决办法 在运维工作中&#xff0c;自动化备份交换机配置是提高效率的关键环节。许多团队选择使用Python脚本来完成这项任务&#xff0c;但在实际操作中&#xff0c;即使是经验丰富的开发者也会遇到各种意料之外…

作者头像 李华
网站建设 2026/5/7 14:12:36

AI蜂巢:多智能体协同框架的设计原理与工程实践

1. 项目概述&#xff1a;AI蜂巢&#xff0c;一个面向开发者的智能体编排与协同平台 最近在开源社区里&#xff0c;一个名为“AI蜂巢”的项目引起了我的注意。这个项目由开发者 hncboy 发起&#xff0c;仓库地址是 hncboy/ai-beehive 。初看这个名字&#xff0c;你可能会联想…

作者头像 李华