碰撞檢測

這是 Gamedev Canvas 教程第 7 步,共 10 步。您可以在 Gamedev-Canvas-workshop/lesson7.html 找到完成此課程後代碼應有的樣子。

我們已經讓磚塊出現在螢幕上了,但由於球可以穿過它們,所以遊戲仍然不是那麼有趣。我們需要考慮新增碰撞檢測,以便球能夠從磚塊上彈開並打破它們。

當然,如何實現它由我們自己決定,但計算球是否碰到矩形會很棘手,因為 Canvas 沒有為此提供輔助函式。在本教程中,我們將採用最簡單的方法。我們將檢查球的中心是否與任何給定的磚塊發生碰撞。這並不總是能給出完美的結果,而且有更復雜的碰撞檢測方法,但對於教授基本概念來說,這已經足夠了。

一個碰撞檢測函式

為了開始這一切,我們要建立一個碰撞檢測函式,該函式將在繪製每一幀時迴圈遍歷所有磚塊,並將每個磚塊的位置與球的座標進行比較。為了提高程式碼的可讀性,我們將在碰撞檢測的每次迴圈中定義 b 變數來儲存磚塊物件。

js
function collisionDetection() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      const b = bricks[c][r];
      // calculations
    }
  }
}

如果球的中心位於我們某個磚塊的座標範圍內,我們將改變球的方向。要使球的中心位於磚塊內部,以下所有四個條件必須同時成立:

  • 球的 x 座標大於磚塊的 x 座標。
  • 球的 x 座標小於磚塊的 x 座標加上其寬度。
  • 球的 y 座標大於磚塊的 y 座標。
  • 球的 y 座標小於磚塊的 y 座標加上其高度。

讓我們用程式碼來實現這一點

js
function collisionDetection() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      const b = bricks[c][r];
      if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
        dy = -dy;
      }
    }
  }
}

將上述程式碼塊新增到您的程式碼中,放在 keyUpHandler() 函式下方。

讓被擊中的磚塊消失

上面的程式碼將按預期工作,球會改變方向。問題是磚塊仍然留在原地。我們必須想辦法處理掉那些已經被球擊中的磚塊。我們可以透過新增一個額外的引數來指示是否要在螢幕上繪製每個磚塊來做到這一點。在初始化磚塊的程式碼部分,讓我們為每個磚塊物件新增一個 status 屬性。根據高亮顯示的行更新以下程式碼部分:

js
let bricks = [];

for (let c = 0; c < brickColumnCount; c++) {
  bricks[c] = [];
  for (let r = 0; r < brickRowCount; r++) {
    bricks[c][r] = { x: 0, y: 0, status: 1 };
  }
}

接下來,我們將在 drawBricks() 函式中檢查每個磚塊的 status 屬性的值,然後再繪製它——如果 status1,則繪製它,但如果它是 0,則表示它被球擊中,我們不再希望它出現在螢幕上。按如下方式更新您的 drawBricks() 函式:

js
function drawBricks() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      if (bricks[c][r].status === 1) {
        const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
        const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
        bricks[c][r].x = brickX;
        bricks[c][r].y = brickY;
        ctx.beginPath();
        ctx.rect(brickX, brickY, brickWidth, brickHeight);
        ctx.fillStyle = "#0095DD";
        ctx.fill();
        ctx.closePath();
      }
    }
  }
}

在碰撞檢測函式中跟蹤和更新狀態

現在我們需要將磚塊的 status 屬性包含在 collisionDetection() 函式中:如果磚塊是活動的(其狀態為 1),我們將檢查是否發生碰撞;如果發生碰撞,我們將給定磚塊的狀態設定為 0,這樣它就不會被繪製在螢幕上。按如下方式更新您的 collisionDetection() 函式:

js
function collisionDetection() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      const b = bricks[c][r];
      if (b.status === 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
        ) {
          dy = -dy;
          b.status = 0;
        }
      }
    }
  }
}

啟用我們的碰撞檢測

最後要做的是在我們的主 draw() 函式中新增一個對 collisionDetection() 函式的呼叫。將以下行新增到 draw() 函式中,就在 drawPaddle() 呼叫下方:

js
collisionDetection();

Compare your code

現在,每一幀都會與每一個磚塊檢查球的碰撞檢測。現在我們可以摧毀磚塊了! :-)

後續步驟

我們確實越來越接近了;讓我們繼續前進!在第八章中,我們將學習如何跟蹤得分和獲勝