You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

旋转矩形碰撞检测求助:围绕角色旋转的剑与敌人碰撞实现

旋转矩形碰撞检测实现方案(基于SAT)

嘿,我完全懂这种头疼的感觉——你已经把非旋转矩形的碰撞检测玩明白了,结果碰到旋转的剑和敌人的碰撞,一下子就卡壳了对吧?SAT(分离轴定理)听起来确实有点唬人,但拆解开一步步实现,其实没那么难,而且它刚好是处理这类凸多边形碰撞的最优方案,完美适配你的旋转剑场景。

核心思路:分离轴定理(SAT)

先给你简单捋清楚SAT的核心:对于两个凸多边形,如果能找到任意一条轴,让两个多边形在这条轴上的投影完全不重叠,那它们肯定没碰撞;反过来,如果所有可能的分离轴上的投影都重叠,那碰撞就发生了。

对于矩形来说,我们只需要检测每个矩形的两条边的法线方向作为分离轴就够了——不用检测所有方向,这直接把计算量砍到了最低。

第一步:先搞定基础向量操作

SAT全靠向量计算撑着,所以先封装几个常用的向量工具函数,后面会反复用到:

// 向量点积计算
function dot(v1, v2) {
  return v1.x * v2.x + v1.y * v2.y;
}

// 计算向量长度的平方(避免开根号,提升性能)
function lengthSquared(v) {
  return v.x * v.x + v.y * v.y;
}

// 将向量转为单位向量(归一化)
function normalize(v) {
  const len = Math.sqrt(lengthSquared(v));
  return { x: v.x / len, y: v.y / len };
}

// 获取旋转矩形的两条分离轴(边的法线)
function getRectangleAxes(rect) {
  // rect结构:{ x: 中心x, y: 中心y, width: 宽, height: 高, angle: 旋转角度(度数) }
  const rad = (rect.angle * Math.PI) / 180;
  // 计算矩形两条边的方向向量,再取垂直的法线
  const axis1 = { x: Math.cos(rad), y: Math.sin(rad) };
  const axis2 = { x: -Math.sin(rad), y: Math.cos(rad) };
  // 返回归一化后的两条轴
  return [normalize(axis1), normalize(axis2)];
}

第二步:实现矩形到轴的投影函数

我们需要把矩形的所有顶点投影到指定轴上,然后找到投影的最小和最大值——这是判断重叠的关键:

function projectRectangle(rect, axis) {
  const rad = (rect.angle * Math.PI) / 180;
  const halfW = rect.width / 2;
  const halfH = rect.height / 2;
  
  // 计算旋转矩形的四个顶点坐标
  const vertices = [
    { x: rect.x + halfW * Math.cos(rad) - halfH * Math.sin(rad), y: rect.y + halfW * Math.sin(rad) + halfH * Math.cos(rad) },
    { x: rect.x - halfW * Math.cos(rad) - halfH * Math.sin(rad), y: rect.y - halfW * Math.sin(rad) + halfH * Math.cos(rad) },
    { x: rect.x - halfW * Math.cos(rad) + halfH * Math.sin(rad), y: rect.y - halfW * Math.sin(rad) - halfH * Math.cos(rad) },
    { x: rect.x + halfW * Math.cos(rad) + halfH * Math.sin(rad), y: rect.y + halfW * Math.sin(rad) - halfH * Math.cos(rad) }
  ];
  
  // 计算所有顶点在轴上的投影值,找出最小和最大值
  let min = dot(vertices[0], axis);
  let max = min;
  for (let i = 1; i < 4; i++) {
    const proj = dot(vertices[i], axis);
    if (proj < min) min = proj;
    if (proj > max) max = proj;
  }
  
  return { min, max };
}

第三步:实现完整的SAT碰撞检测函数

把前面的工具函数组合起来,检查两个旋转矩形是否碰撞:

function checkRotatedRectCollision(rectA, rectB) {
  // 获取两个矩形的所有分离轴(每个矩形两条,共四条)
  const axes = [...getRectangleAxes(rectA), ...getRectangleAxes(rectB)];
  
  // 遍历每条轴,检查投影是否重叠
  for (const axis of axes) {
    const projA = projectRectangle(rectA, axis);
    const projB = projectRectangle(rectB, axis);
    
    // 如果任意一条轴上投影完全不重叠,说明没有碰撞
    if (projA.max < projB.min || projB.max < projA.min) {
      return false;
    }
  }
  
  // 所有轴的投影都重叠,说明碰撞发生了
  return true;
}

第四步:结合你的场景使用

比如你的旋转剑是一个细长矩形,敌人是普通矩形,你可以这样调用:

// 定义旋转剑的矩形(中心和角色重合,角度随帧更新)
const sword = {
  x: player.x,
  y: player.y,
  width: 12,
  height: 90,
  angle: currentSwordRotation // 比如每帧增加3度,实现旋转效果
};

// 定义敌人的矩形(如果敌人不旋转,angle设为0即可)
const enemy = {
  x: enemyPosition.x,
  y: enemyPosition.y,
  width: 36,
  height: 36,
  angle: 0
};

// 检测碰撞,触发击杀逻辑
if (checkRotatedRectCollision(sword, enemy)) {
  handleEnemyKill(enemy);
}

额外优化小技巧

  • AABB预检测:先计算两个旋转矩形的轴对齐包围盒(把旋转矩形包起来的最小轴对齐矩形),如果这个AABB都不碰撞,直接跳过SAT检测,能大幅提升性能。
  • 碰撞响应扩展:如果需要知道碰撞的深度(比如把敌人推开),可以在遍历轴的时候记录最小的重叠量和对应的轴,用这个轴来计算碰撞方向和深度。

这样一步步走下来,SAT是不是没你想象的那么复杂?这个方案刚好适配你的旋转剑击杀敌人的需求,赶紧试试吧!

内容的提问来源于stack exchange,提问作者Elie Jamous

火山引擎 最新活动