如何检测发生碰撞的一侧

How to detect the side on which collision occured

这是我的第一个 post 所以我试图尽可能清楚地说明我的问题。我正在制作游戏,我想改进我的碰撞检测。这是因为我想检查哪一侧被击中并阻止玩家移动过去而不使用 if(collision(player, enemy)) player.x = enemy.x - player.w(width) 因为如果玩家与顶部发生碰撞,玩家将无法保持在顶部。

在代码中,它检查是否有任何一个语句为真,然后 returns 它但它没有告诉我哪个语句等于真,所以我可以阻止玩家相应地移动,如果这是有道理的。如果您有更有效的碰撞检测供我使用,将不胜感激。

我已经尝试使位置变量等于碰撞到的任何一侧,然后阻止玩家移动过去,但它只适用于左侧并且不会让我的玩家跳过敌人或方块。

function collision(object1, object2) {

       return !(
            object1.x > object2.x + object2.w  ||
            object1.x + object1.w < object2.x  ||
            object1.y > object2.y + object2.h  ||
            object1.y + object1.h < object2.y 
        )
}

//Only works for the left side

if(collision(player, enemy)) player.x = enemy.x - player.w

我希望它能够告诉我碰撞到哪一侧,然后阻止玩家移动 past/into 它并让玩家能够在 block/enemy而不只是被推到左边。

您需要计算 x 和 y 之间的距离,并使用它们可能沿每个轴碰撞的最小距离来计算沿两个轴的深度。然后你可以选择较小的深度并沿着那个深度移动。这是一个例子:

if(collision(player, enemy)){
    // Most of this stuff would probably be good to keep stored inside the player
    // along side their x and y position. That way it doesn't have to be recalculated
    // every collision check
    var playerHalfW = player.w/2
    var playerHalfH = player.h/2
    var enemyHalfW = enemy.w/2
    var enemyHalfH = enemy.h/2
    var playerCenterX = player.x + player.w/2
    var playerCenterY = player.y + player.h/2
    var enemyCenterX = enemy.x + enemy.w/2
    var enemyCenterY = enemy.y + enemy.h/2

    // Calculate the distance between centers
    var diffX = playerCenterX - enemyCenterX
    var diffY = playerCenterY - enemyCenterY

    // Calculate the minimum distance to separate along X and Y
    var minXDist = playerHalfW + enemyHalfW
    var minYDist = playerHalfH + enemyHalfH

    // Calculate the depth of collision for both the X and Y axis
    var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
    var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY

    // Now that you have the depth, you can pick the smaller depth and move
    // along that axis.
    if(depthX != 0 && depthY != 0){
      if(Math.abs(depthX) < Math.abs(depthY)){
        // Collision along the X axis. React accordingly
        if(depthX > 0){
            // Left side collision
        }
        else{
            // Right side collision
        }
      }
      else{
        // Collision along the Y axis.
        if(depthY > 0){
           // Top side collision
        }
        else{
           // Bottom side collision
        }
      }
    }
  }

工作示例

这是一个您可以试用的工作示例。使用箭头键移动播放器。

player = {
  x: 9,
  y: 50,
  w: 100,
  h: 100
}
enemy = {
  x: 100,
  y: 100,
  w: 100,
  h: 100
}
output = document.getElementById("collisionType");
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d")

function collision(object1, object2) {
  return !(
    object1.x > object2.x + object2.w ||
    object1.x + object1.w < object2.x ||
    object1.y > object2.y + object2.h ||
    object1.y + object1.h < object2.y
  )
}

function draw() {
  ctx.clearRect(0, 0, 400, 400)
  ctx.lineWidth = "5"
  ctx.beginPath();
  ctx.strokeStyle = "red";
  ctx.rect(player.x, player.y, player.w, player.h);
  ctx.stroke();

  ctx.beginPath();
  ctx.strokeStyle = "blue";
  ctx.rect(enemy.x, enemy.y, enemy.w, enemy.h);
  ctx.stroke();

}

function handleCollision() {
  if (collision(player, enemy)) {
    var playerHalfW = player.w / 2
    var playerHalfH = player.h / 2
    var enemyHalfW = enemy.w / 2
    var enemyHalfH = enemy.h / 2
    var playerCenterX = player.x + player.w / 2
    var playerCenterY = player.y + player.h / 2
    var enemyCenterX = enemy.x + enemy.w / 2
    var enemyCenterY = enemy.y + enemy.h / 2

    // Calculate the distance between centers
    var diffX = playerCenterX - enemyCenterX
    var diffY = playerCenterY - enemyCenterY

    // Calculate the minimum distance to separate along X and Y
    var minXDist = playerHalfW + enemyHalfW
    var minYDist = playerHalfH + enemyHalfH

    // Calculate the depth of collision for both the X and Y axis
    var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
    var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY

    // Now that you have the depth, you can pick the smaller depth and move
    // along that axis.
    if (depthX != 0 && depthY != 0) {
      if (Math.abs(depthX) < Math.abs(depthY)) {
        // Collision along the X axis. React accordingly
        if (depthX > 0) {
          output.innerHTML = "left side collision"
        } else {
          output.innerHTML = "right side collision"
        }
      } else {
        // Collision along the Y axis.
        if (depthY > 0) {
          output.innerHTML = "top side collision"
        } else {
          output.innerHTML = "bottom side collision"
        }
      }
    }
  } else {
    output.innerHTML = "No collision"
  }
}

keyStates = []

function handleKeys() {
  if (keyStates[39]) {
    player.x += 2 //Move right
  } else if (keyStates[37]) {
    player.x -= 2 //Move left
  }
  if (keyStates[38]) {
    player.y -= 2 //Move up
  }
  if (keyStates[40]) {
    player.y += 2 //Move down
  }
}

function main() {
  handleKeys();
  draw();
  handleCollision();
  window.requestAnimationFrame(main);
}

window.onkeydown = function(e) {
  keyStates[e.keyCode] = true
}

window.onkeyup = function(e) {
  keyStates[e.keyCode] = false
}

main();
<h2 id="collisionType"></h2>
<canvas id="canvas" width='300' height='300'></canvas>

对碰撞做出反应

既然你知道碰撞发生在哪一侧,那么决定如何反应应该是相当微不足道的。这与您目前在左侧所做的非常相似,只需翻转一些符号并更改轴即可。

其他注意事项

  • 您可能需要考虑播放器的速度(如果有的话),否则检测可能会失败。
    • 如果玩家的速度太高,它可能会 'tunnel' 穿过敌人并且不会检测到碰撞。
    • 如果碰撞时速度没有停止,玩家的动作也会看起来很紧张
  • 您的物体可以旋转或有 4 个以上的边吗?如果是这样,您可能需要使用另一种方法,如下所述。

Here's a good answer to another post that talks in depth about collision engines

其他方法

至于其他碰撞检测方法,有很多,但我想到的是 Separating Axis Theorem which is a little more complex than what you have but will work with more complex convex shapes and rotation. It also tells you the direction and distance needed to move to resolve the collision. Here's a site,它有交互式示例,并且 in-depth 讨论了该主题。它似乎没有提供完整的实现,但可以在其他地方找到。