Canvas小游戏避坑指南:手写圆形、矩形碰撞检测,告别第三方库

张开发
2026/4/16 22:45:19 15 分钟阅读

分享文章

Canvas小游戏避坑指南:手写圆形、矩形碰撞检测,告别第三方库
Canvas游戏开发实战从零实现高性能碰撞检测系统在H5小游戏和互动可视化项目中碰撞检测往往是性能瓶颈所在。许多开发者习惯直接引入物理引擎但当项目只需要基础碰撞功能时这些重型武器反而会成为负担。本文将带你从数学原理出发构建一套轻量级碰撞检测体系涵盖圆形、矩形到复杂多边形的精确检测并分享我在实际项目中积累的优化技巧。1. 碰撞检测基础从圆形开始圆形碰撞是最简单的检测模型但其中蕴含的优化思想却贯穿整个碰撞系统。核心算法只需比较两圆圆心距离与半径之和function circleCollision(c1, c2) { const dx c1.x - c2.x const dy c1.y - c2.y const distanceSquared dx*dx dy*dy const radiusSum c1.radius c2.radius return distanceSquared radiusSum * radiusSum }关键优化点使用距离平方比较避免开方运算对象池化减少GC压力预计算半径平方值提示在60FPS的游戏中即使每次检测节省0.01ms1000次检测也能节省10ms的帧时间实际项目中我常用空间分区来优化大量圆形检测将画布划分为N×N网格只检测同一网格或相邻网格中的对象动态调整网格大小保持每个网格5-10个对象2. 矩形碰撞的两种实现范式2.1 AABB轴对称包围盒AABB是各边与坐标轴平行的矩形检测逻辑极其高效function aabbCollision(box1, box2) { return !( box1.right box2.left || box1.left box2.right || box1.bottom box2.top || box1.top box2.bottom ) }性能对比表检测类型平均耗时(ms)适用场景AABB0.002静态UI元素OBB0.012旋转物体2.2 OBB定向包围盒对于旋转的矩形需要采用分离轴定理(SAT)实现class OBB { constructor(center, width, height, rotation) { this.axes [ {x: Math.cos(rotation), y: Math.sin(rotation)}, {x: -Math.sin(rotation), y: Math.cos(rotation)} ] this.extents [width/2, height/2] this.center center } getProjectionRadius(axis) { return this.extents[0] * Math.abs(this.axes[0].x*axis.x this.axes[0].y*axis.y) this.extents[1] * Math.abs(this.axes[1].x*axis.x this.axes[1].y*axis.y) } } function obbCollision(a, b) { const axes [...a.axes, ...b.axes] const offset {x: b.center.x - a.center.x, y: b.center.y - a.center.y} for(let axis of axes) { const projA a.getProjectionRadius(axis) const projB b.getProjectionRadius(axis) const projOffset Math.abs(offset.x*axis.x offset.y*axis.y) if(projOffset projA projB) return false } return true }3. 复杂形状检测策略3.1 胶囊体检测胶囊体本质是圆柱体半球帽的组合检测时转化为线段到点的距离计算function capsulePointDistance(capsule, point) { const segmentVec {x: capsule.x2 - capsule.x1, y: capsule.y2 - capsule.y1} const pointVec {x: point.x - capsule.x1, y: point.y - capsule.y1} const t Math.max(0, Math.min(1, (pointVec.x*segmentVec.x pointVec.y*segmentVec.y) / (segmentVec.x*segmentVec.x segmentVec.y*segmentVec.y) )) const projection { x: capsule.x1 t*segmentVec.x, y: capsule.y1 t*segmentVec.y } const dx point.x - projection.x const dy point.y - projection.y return Math.sqrt(dx*dx dy*dy) }3.2 扇形检测扇形检测需要分三步判断圆心是否在扇形角范围内圆心到扇形顶点的距离圆心到两条边的距离function sectorCircleCollision(sector, circle) { // 第一步距离筛选 const dx circle.x - sector.x const dy circle.y - sector.y const distSq dx*dx dy*dy const maxDist sector.radius circle.radius if(distSq maxDist*maxDist) return false // 第二步角度检测 const angle Math.atan2(dy, dx) const angleDiff normalizeAngle(angle - sector.startAngle) if(angleDiff sector.angle) return false // 第三步边检测 if(distSq circle.radius*circle.radius) return true const edge1 getLineProjection(sector.x, sector.y, sector.startAngle) const edge2 getLineProjection(sector.x, sector.y, sector.startAngle sector.angle) return pointToLineDistance(...edge1, circle.x, circle.y) circle.radius || pointToLineDistance(...edge2, circle.x, circle.y) circle.radius }4. 性能优化实战技巧4.1 空间分割优化四叉树实现要点设置节点最大容量通常4-8个对象动态分裂与合并节点对象只存储在叶子节点class Quadtree { constructor(bounds, capacity) { this.bounds bounds // {x,y,width,height} this.capacity capacity this.objects [] this.nodes [] } insert(obj) { if(!this.bounds.contains(obj)) return false if(this.objects.length this.capacity || !this.nodes.length) { this.objects.push(obj) return true } this.nodes[0].insert(obj) || this.nodes[1].insert(obj) || this.nodes[2].insert(obj) || this.nodes[3].insert(obj) } query(range, found []) { if(!this.bounds.intersects(range)) return found found.push(...this.objects.filter(obj range.contains(obj))) for(let node of this.nodes) { node.query(range, found) } return found } }4.2 混合精度检测策略我通常采用三级检测体系粗略检测基于包围盒快速筛选中等精度简化几何形状检测精确检测完整几何计算function hybridDetection(objA, objB) { // 第一级AABB快速排除 if(!aabbCollision(objA.getAABB(), objB.getAABB())) return false // 第二级距离粗略判断 const centerDist distanceSq(objA.center, objB.center) if(centerDist (objA.radius objB.radius)**2) return false // 第三级精确形状检测 return preciseCollision(objA, objB) }5. 调试与可视化技巧在开发《太空防御》游戏时我总结了一套实用的调试方法碰撞可视化方案function drawCollisionDebug(ctx) { ctx.strokeStyle #ff0000 gameObjects.forEach(obj { if(obj.collider) { ctx.beginPath() if(obj.collider.type circle) { ctx.arc(obj.x, obj.y, obj.collider.radius, 0, Math.PI*2) } else if(obj.collider.type aabb) { ctx.rect(obj.x, obj.y, obj.width, obj.height) } ctx.stroke() } }) }性能监控面板class PerformanceMonitor { constructor() { this.collisionTimes [] this.frameCount 0 } recordCollision(time) { this.collisionTimes.push(time) if(this.collisionTimes.length 100) { this.collisionTimes.shift() } } draw(ctx) { const avg this.collisionTimes.reduce((a,b) ab, 0) / this.collisionTimes.length ctx.fillText(碰撞检测: ${avg.toFixed(3)}ms, 10, 20) } }在实现《弹球大师》的物理系统时发现当超过200个物体时基础检测会导致帧率骤降。通过引入四叉树和混合检测策略最终在1000个物体时仍保持60FPS流畅运行。关键点在于根据物体运动速度动态调整检测频率——静态物体每3帧检测一次高速物体每帧检测。

更多文章