AABB碰撞分辨率滑动边

时间:2017-09-12 09:37:51

标签: javascript html5 game-engine game-physics 2d-games

所以,我正在通过尝试为我的游戏引擎制作一个简单的物理引擎来重新发明轮子(并学习很多东西)。我一直在网上搜索,尝试(并失败)解决我当前的问题。关于这个问题有很多资源,但我发现的那些资源似乎都不适用于我的案例。

短暂的问题:当两个矩形碰撞时,碰撞分辨率在某些角落上不能正常工作。它的失败方式因矩形的大小而异。我正在寻找的是碰撞的“最短重叠”解决方案或另一个相当简单的解决方案(我愿意接受建议!)。(向下滚动以获得更好的解释和插图)。

警告:以下代码可能效率不高......

首先,这是我的物理循环。它只是循环遍历所有游戏实体并检查它们是否与任何其他游戏实体发生冲突。效率不高(n ^ 2和所有这些),但它现在有效。

updatePhysics: function(step) {
  // Loop through entities and update positions based on velocities
  for (var entityID in Vroom.entityList) {
    var entity = Vroom.entityList[entityID];
    if (entity.physicsEnabled) {
      switch (entity.entityType) {
        case VroomEntity.KINEMATIC:
          entity.pos.x += entity.vel.x * step;
          entity.pos.y += entity.vel.y * step;
          break;

        case VroomEntity.DYNAMIC:
          // Dynamic stuff
          break;
      }
    }
  }
  // Loop through entities and detect collisions. Resolve collisions as they are detected.
  for (var entityID in Vroom.entityList) {
    var entity = Vroom.entityList[entityID];
    if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
      for (var targetID in Vroom.entityList) {
        if (targetID !== entityID) {
          var target = Vroom.entityList[targetID];
          if (target.physicsEnabled) {
            // Check if current entity and target is colliding
            if (Vroom.collideEntity(entity, target)) {
              switch (entity.collisionType) {
                case VroomEntity.DISPLACE:
                  Vroom.resolveTestTest(entity, target);
                  break;
              }
            }
          }
        }
      }
    }
  }
},

以下是实际碰撞检测的代码。这似乎也没问题。

collideEntity: function(entity, target) {
  if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() ||  entity.getRight() < target.getLeft() ||  entity.getLeft() > target.getRight()) {
    return false;
  }

  return true;
},

这是问题开始出现的地方。我希望将实体简单地“推”出目标实体并将速度设置为0.只要实体和目标都是完美的正方形,这就可以正常工作。如果让我们说实体(gif中的玩家形象)是一个矩形,那么当最长边(X轴)与目标(正方形)碰撞时,碰撞将“滑动”。如果我将播放器尺寸交换为短而宽,则Y轴会出现同样的问题。

resolveTestTest: function(entity, target) {
  var normalizedX = (target.getMidX() - entity.getMidX());
  var normalizedY = (target.getMidY() - entity.getMidY());
  var absoluteNormalizedX = Math.abs(normalizedX);
  var absoluteNormalizedY = Math.abs(normalizedY);

  console.log(absoluteNormalizedX, absoluteNormalizedY);

  // The collision is comming from the left or right
  if (absoluteNormalizedX > absoluteNormalizedY) {
    if (normalizedX < 0) {
      entity.pos.x = target.getRight();
    } else {
      entity.pos.x = target.getLeft() - entity.dim.width;
    }

    // Set velocity to 0
    entity.vel.x = 0;

    // The collision is comming from the top or bottom
  } else {
    if (normalizedY < 0) {
      entity.pos.y = target.getBottom();
    } else {
      entity.pos.y = target.getTop() - entity.dim.height;
    }

    // Set velocity to 0
    entity.vel.y = 0;
  }

},

Y轴上的碰撞适用于这些形状 GIF

X轴上的碰撞会以这些形状滑动 GIF

我该怎么做才能解决这个滑倒问题?在过去的5天里,我一直在反对这一点,所以如果有人帮助我朝着正确的方向前进,我将非常感激!

谢谢:)

- 编辑: -

如果只沿左侧或右侧向一个方向移动,也会发生滑倒。

GIF

- 编辑2工作代码: - 请参阅下面的答案,了解工作代码的示例!

3 个答案:

答案 0 :(得分:1)

您所犯的重要逻辑错误就是这一行:

if (absoluteNormalizedX > absoluteNormalizedY) {

  

仅当两个实体都是正方形时才有效。

考虑一个近乎极端的例子来说明你的X滑动示例:如果他们几乎碰到角落:

enter image description here

虽然图表有点夸张,但在这种情况下你可以看到absoluteNormalizedX < absoluteNormalizedY - 你的实现会继续解决垂直碰撞而不是预期的水平碰撞。

另一个错误是,无论碰撞位于哪一侧,您始终将相应的速度分量设置为零:如果组件与碰撞法线方向相反,则必须将组件归零,否则您不会能够离开表面。

解决此问题的一个好方法是在进行碰撞检测时记录碰撞的面部:

collideEntity: function(entity, target) {
   // adjust this parameter to your liking
   var eps = 1e-3;

   // no collision
   var coll_X = entity.getRight() > target.getLeft() && entity.getLeft() < target.getRight();
   var coll_Y = entity.getBottom() > target.getTop() && entity.getTop() < target.getBottom();
   if (!(coll_X && coll_Y)) return 0;

   // calculate bias flag in each direction
   var bias_X = entity.targetX() < target.getMidX();
   var bias_Y = entity.targetY() < target.getMidY();

   // calculate penetration depths in each direction
   var pen_X = bias_X ? (entity.getRight() - target.getLeft())
                      : (target.getRight() - entity.getLeft());
   var pen_Y = bias_Y ? (entity.getBottom() - target.getUp())
                      : (target.getBottom() - entity.getUp());
   var diff = pen_X - pen_Y;

   // X penetration greater
   if (diff > eps)
      return (1 << (bias_Y ? 0 : 1));

   // Y pentration greater
   else if (diff < -eps) 
      return (1 << (bias_X ? 2 : 3));

   // both penetrations are approximately equal -> treat as corner collision
   else
      return (1 << (bias_Y ? 0 : 1)) | (1 << (bias_X ? 2 : 3));
},

updatePhysics: function(step) {
   // ...
            // pass collision flag to resolver function
            var result = Vroom.collideEntity(entity, target);
            if (result > 0) {
              switch (entity.collisionType) {
                case VroomEntity.DISPLACE:
                  Vroom.resolveTestTest(entity, target, result);
                  break;
              }
            }
   // ...
}

使用位标志而不是布尔数组来提高效率。然后可以将解析器功能重写为:

resolveTestTest: function(entity, target, flags) {
  if (!!(flags & (1 << 0))) {  // collision with upper surface
      entity.pos.y = target.getTop() - entity.dim.height;
      if (entity.vel.y > 0)  // travelling downwards
         entity.vel.y = 0;
  } 
  else
  if (!!(flags & (1 << 1))) {  // collision with lower surface
      entity.pos.y = target.getBottom();
      if (entity.vel.y < 0)  // travelling upwards
         entity.vel.y = 0;
  }

  if (!!(flags & (1 << 2))) {  // collision with left surface
      entity.pos.x = target.getLeft() - entity.dim.width;
      if (entity.vel.x > 0)  // travelling rightwards
         entity.vel.x = 0;
  } 
  else
  if (!!(flags & (1 << 3))) {  // collision with right surface
      entity.pos.x = target.getRight();
      if (entity.vel.x < 0)  // travelling leftwards
         entity.vel.x = 0;
  }
},

请注意,与原始代码不同,上述内容也允许角落发生碰撞 - 即速度和位置沿两个轴分解。

答案 1 :(得分:0)

问题可能在于您根据相同的位置纠正 XY碰撞:

  1. 玩家处于某个位置。让我们检查一下碰撞。
  2. 玩家的右下角与对象的左上角重叠。
  3. X位置已更正:播放器向左移动。
  4. 玩家的右下角与对象的左上角重叠。
  5. Y位置已更正:玩家已向上移动。
  6. 最终结果:播放器向上移动到左侧。
  7. 你可能需要&#34;得到&#34;在支票之间再次确定玩家的位置。

答案 2 :(得分:0)

我的工作代码

因此,在惊人的@meowgoesthedog的帮助和指导下,我终于走上正轨,找到了我想要的东西。问题(正如@meowgoesthedog指出的那样)是我的代码实际上只适用于正方形。解决方案是检查碰撞体的交点并基于最短交点求解。 注意:如果您需要使用小而快速移动的物体的精确物理,这可能不是一个合适的解决方案。查找交叉点深度的代码基于:https://github.com/kg/PlatformerStarterKit/blob/0e2fafb8dbc845279fe4116c37b6f2cdd3e636d6/RectangleExtensions.cs与此相关这个项目:https://msdn.microsoft.com/en-us/library/dd254916(v=xnagamestudio.31).aspx

以下是我的工作代码:

我的物理循环没有太大变化,除了某些功能更好的名称。

updatePhysics: function(step) {
  // Loop through entities and update positions based on velocities
  for (var entityID in Vroom.entityList) {
    var entity = Vroom.entityList[entityID];
    if (entity.physicsEnabled) {
      switch (entity.entityType) {
        case VroomEntity.KINEMATIC:
          entity.pos.x += entity.vel.x * step;
          entity.pos.y += entity.vel.y * step;
          break;

        case VroomEntity.DYNAMIC:
          // Dynamic stuff
          break;
      }
    }
  }
  // Loop through entities and detect collisions. Resolve collisions as they are detected.
  for (var entityID in Vroom.entityList) {
    var entity = Vroom.entityList[entityID];
    if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
      for (var targetID in Vroom.entityList) {
        if (targetID !== entityID) {
          var target = Vroom.entityList[targetID];
          if (target.physicsEnabled) {
            // Check if current entity and target is colliding
            if (Vroom.collideEntity(entity, target)) {
              switch (entity.collisionType) {
                case VroomEntity.DISPLACE:
                  Vroom.resolveDisplace(entity, target);
                  break;
              }
            }
          }
        }
      }
    }
  }
},

碰撞检测也一样。

collideEntity: function(entity, target) {
  if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() ||  entity.getRight() < target.getLeft() ||  entity.getLeft() > target.getRight()) {
    return false;
  }

  return true;
},

这是基本解决问题的代码。代码中的注释应该解释它的功能。

getIntersectionDepth: function(entity, target) {
  // Calculate current and minimum-non-intersecting distances between centers.
  var distanceX = entity.getMidX() - target.getMidX();
  var distanceY = entity.getMidY() - target.getMidY();
  var minDistanceX = entity.halfDim.width + target.halfDim.width;
  var minDistanceY = entity.halfDim.height + target.halfDim.height;

  // If we are not intersecting at all, return 0.
  if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY) {
    return {
      x: 0,
      y: 0,
    };
  }

  // Calculate and return intersection depths.
  var depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
  var depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;

  return {
    x: depthX,
    y: depthY,
  };
},

这是更新的解析功能。现在,在确定碰撞轴时需考虑交叉深度,然后在确定要解决的方向时使用碰撞轴的交叉深度符号。

resolveDisplace: function(entity, target) {
  var intersection = Vroom.getIntersectionDepth(entity, target);
  if (intersection.x !== 0 && intersection.y !== 0) {
    if (Math.abs(intersection.x) < Math.abs(intersection.y)) {
      // Collision on the X axis
      if (Math.sign(intersection.x) < 0) {
        // Collision on entity right
        entity.pos.x = target.getLeft() - entity.dim.width;
      } else {
        // Collision on entity left
        entity.pos.x = target.getRight();
      }

      entity.vel.x = 0;
    } else if (Math.abs(intersection.x) > Math.abs(intersection.y)) {
      // Collision on the Y axis
      if (Math.sign(intersection.y) < 0) {
        // Collision on entity bottom
        entity.pos.y = target.getTop() - entity.dim.height;
      } else {
        // Collision on entity top
        entity.pos.y = target.getBottom();
      }

      entity.vel.y = 0;
    }
  }
},

谢谢大家的帮助!