簡體   English   中英

AABB碰撞分辨率滑動側面

[英]AABB collision resolution slipping sides

因此,我正在嘗試為我的游戲引擎制作一個簡單的物理引擎,從而重新發明輪子(並學習很多東西)。 我一直在網上搜索,嘗試(並失敗)解決我當前的問題。 關於這個問題有很多資源,但我發現的那些資源似乎都不適用於我的案例。

短暫的問題:當兩個矩形碰撞時,碰撞分辨率在某些角上無法正常工作。 它的失敗方式因矩形的大小而異。 我正在尋找的是碰撞的“最短重疊”解決方案或另一個相當簡單的解決方案(我願意接受建議!)。 (向下滾動以獲得更好的解釋和插圖)。

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

首先,這是我的物理循環。 它只是循環遍歷所有游戲實體並檢查它們是否與任何其他游戲實體發生沖突。 它效率不高(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工作代碼: -有關工作代碼的示例,請參閱下面的答案!

您所做的重要邏輯錯誤是這一行:

if (absoluteNormalizedX > absoluteNormalizedY) {

這僅適用於兩個實體都是正方形的情況。

考慮一個近乎極端的情況,你的X滑動示例:如果他們幾乎觸及角落:

在此輸入圖像描述

雖然圖表有點誇大,但在這種情況下你可以看到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;
  }
},

請注意,與原始代碼不同,上面也允許角落碰撞 - 即沿兩個軸分解速度和位置。

這個問題可能是你正在更正兩個 XY基於相同位置的碰撞:

  1. 玩家處於某個位置。 我們來檢查碰撞。
  2. 玩家的右下角與對象的左上角重疊。
  3. X位置已更正:播放器向左移動。
  4. 玩家的右下角與對象的左上角重疊。
  5. Y位置已更正:玩家向上移動。
  6. 最終結果:播放器向上移動到左側。

你可能需要在支票之間再次“獲得”玩家的位置。

我的工作代碼

因此,在驚人的@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;
    }
  }
},

感謝大家的幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM