“等一下,我碰!”——常见的 2D 碰撞检测
引言:碰撞检测——游戏世界的物理法则
在2D游戏开发中,碰撞检测(Collision Detection)是构建物理交互的核心技术。无论是角色跳跃踩踏平台、子弹击中敌人,还是玩家与NPC的接触互动,都依赖精确的碰撞判断。一句”等一下,我碰!”不仅是对碰撞瞬间的生动描述,更揭示了开发者在实现物理交互时需要解决的复杂问题。本文将系统梳理2D碰撞检测的常见方法、优化策略及实际应用场景,为开发者提供从理论到实践的完整指南。
一、基础碰撞检测方法
1. 轴对齐包围盒(AABB)
轴对齐包围盒(Axis-Aligned Bounding Box)是最简单且广泛使用的碰撞检测方法。其核心思想是用一个矩形框包裹游戏对象,通过比较两个矩形框的坐标范围判断是否发生碰撞。
实现原理:
- 每个对象有一个矩形框,由左上角坐标
(x1, y1)和右下角坐标(x2, y2)定义。 - 判断两个矩形框是否重叠:
def aabb_collision(rect1, rect2):return (rect1.x1 < rect2.x2 and rect1.x2 > rect2.x1 andrect1.y1 < rect2.y2 and rect1.y2 > rect2.y1)
优点:
- 计算简单,性能高效。
- 适合静态或移动速度较慢的对象。
缺点:
- 无法处理旋转对象。
- 精度较低,可能产生”假碰撞”(如矩形框重叠但实际模型未接触)。
2. 圆形碰撞检测
圆形碰撞检测通过比较两个圆的圆心距离和半径之和判断碰撞,适用于圆形或近似圆形的对象(如子弹、粒子效果)。
实现原理:
- 计算两个圆的圆心距离
distance。 -
判断
distance <= radius1 + radius2:import mathdef circle_collision(circle1, circle2):dx = circle1.x - circle2.xdy = circle1.y - circle2.ydistance = math.sqrt(dx * dx + dy * dy)return distance <= (circle1.radius + circle2.radius)
优点:
- 计算简单,适合高速移动的对象。
- 旋转不影响结果。
缺点:
- 无法精确表示非圆形对象。
二、进阶碰撞检测方法
1. 分离轴定理(SAT)
分离轴定理(Separating Axis Theorem)是一种通用的凸多边形碰撞检测方法,适用于任意形状的凸多边形。
实现原理:
- 将两个多边形的边投影到所有可能的分离轴上。
- 如果存在一条轴使得两个多边形的投影不重叠,则它们不相交。
- 否则,它们相交。
关键步骤:
- 获取两个多边形的边。
- 对每条边计算法线(分离轴)。
- 将两个多边形投影到分离轴上。
- 判断投影是否重叠。
代码示例:
def project_polygon(polygon, axis):min_proj = max_proj = dot(polygon[0], axis)for vertex in polygon[1:]:proj = dot(vertex, axis)min_proj = min(min_proj, proj)max_proj = max(max_proj, proj)return min_proj, max_projdef sat_collision(poly1, poly2):for poly in [poly1, poly2]:for i in range(len(poly)):edge = (poly[i+1] if i+1 < len(poly) else poly[0]) - poly[i]normal = (-edge.y, edge.x) # 计算法线proj1 = project_polygon(poly1, normal)proj2 = project_polygon(poly2, normal)if proj1[1] < proj2[0] or proj2[1] < proj1[0]:return Falsereturn True
优点:
- 适用于任意凸多边形。
- 结果精确。
缺点:
- 计算复杂度较高(O(n*m),n和m为多边形边数)。
- 不适用于凹多边形。
2. 像素级碰撞检测
像素级碰撞检测通过比较两个对象的像素数据判断是否发生碰撞,适用于需要高精度的场景(如平台游戏中的精确跳跃)。
实现原理:
- 将两个对象的图像渲染到离屏缓冲区。
- 逐像素比较两个图像的非透明区域是否重叠。
代码示例(使用Pygame):
import pygamedef pixel_collision(sprite1, sprite2):# 获取精灵的矩形和图像rect1 = sprite1.rectrect2 = sprite2.rectimage1 = sprite1.imageimage2 = sprite2.image# 计算重叠区域overlap_rect = rect1.clip(rect2)if overlap_rect.width == 0 or overlap_rect.height == 0:return False# 提取重叠区域的像素subsurface1 = image1.subsurface(pygame.Rect(overlap_rect.x - rect1.x, overlap_rect.y - rect1.y, overlap_rect.width, overlap_rect.height))subsurface2 = image2.subsurface(pygame.Rect(overlap_rect.x - rect2.x, overlap_rect.y - rect2.y, overlap_rect.width, overlap_rect.height))# 逐像素比较for x in range(overlap_rect.width):for y in range(overlap_rect.height):if subsurface1.get_at((x, y))[3] > 0 and subsurface2.get_at((x, y))[3] > 0:return Truereturn False
优点:
- 精度极高,适用于复杂形状。
缺点:
- 性能开销大,不适合实时检测。
- 需要预处理图像数据。
三、碰撞检测优化策略
1. 空间分区技术
空间分区技术(如四叉树、网格)通过将游戏世界划分为多个区域,减少需要检测的对象数量。
四叉树实现示例:
class QuadTreeNode:def __init__(self, bounds, capacity):self.bounds = bounds # 节点边界self.capacity = capacity # 节点容量self.objects = [] # 存储的对象self.children = [] # 子节点def insert(self, obj):if not self.bounds.contains(obj.rect):return Falseif len(self.objects) < self.capacity:self.objects.append(obj)return Trueelse:if not self.children:self.subdivide()for child in self.children:if child.insert(obj):return Truereturn Falsedef subdivide(self):# 将当前节点划分为4个子节点pass
优点:
- 显著减少碰撞检测的计算量。
- 适合大规模游戏世界。
2. 粗检测与精检测结合
先使用简单的碰撞检测方法(如AABB)进行粗检测,过滤明显不相交的对象,再对可能碰撞的对象使用更精确的方法(如SAT)。
流程:
- 使用AABB检测所有对象对。
- 对AABB重叠的对象对使用SAT检测。
- 仅处理SAT检测为碰撞的对象。
优点:
- 平衡精度与性能。
- 减少不必要的精确检测。
四、实际应用场景与建议
1. 平台游戏中的角色与平台碰撞
- 使用AABB检测角色与平台的碰撞。
- 结合像素级检测处理斜坡或特殊地形。
- 建议:为平台添加”碰撞层”属性,区分可站立和不可站立的平台。
2. 射击游戏中的子弹与敌人碰撞
- 使用圆形检测子弹与敌人的碰撞。
- 结合SAT检测子弹与敌人复杂形状的碰撞。
- 建议:为子弹和敌人设置不同的碰撞半径,避免”假碰撞”。
3. 物理引擎中的刚体碰撞
- 使用分离轴定理检测刚体碰撞。
- 结合冲量解算器处理碰撞后的物理响应。
- 建议:为刚体设置质量、摩擦力和弹性系数,模拟真实物理效果。
五、总结与展望
2D碰撞检测是游戏开发中不可或缺的技术,从简单的AABB到复杂的像素级检测,每种方法都有其适用场景。开发者应根据游戏类型、性能需求和精度要求选择合适的碰撞检测方法。未来,随着硬件性能的提升和算法的优化,碰撞检测将更加高效和精确,为玩家带来更真实的游戏体验。
通过本文的介绍,开发者可以掌握2D碰撞检测的核心原理和实现方法,并在实际开发中灵活应用,解决”等一下,我碰!”背后的技术难题。