canvas中的碰撞檢測筆記


用 canvas 做小游戲或者特效,碰撞檢測是少不了的。本文將會涉及普通的碰撞檢測,以及像素級的碰撞檢測。(本文的碰撞檢測均以矩形為例)

普通碰撞檢測#

普通的矩形碰撞檢測比較簡單。即已知兩個矩形的各頂點坐標,判斷是否相交,如相交,則為碰撞。

leetcode 有道題是給出兩個矩形的坐標,求其相交面積(223. Rectangle Area),代碼 可以直接拿過來用,如果面積大於 0,則為碰撞。

如果只需判斷是否相交或者相交面積,非常簡單,可以參考 這里

為了程序的可擴展性,如果碰撞,最好還能求得相交矩形的坐標信息(為像素級碰撞檢測作准備),完善后的檢測代碼如下:

// 矩形一 top-left 坐標 (A, B), C 為 width, D 為 height
// 矩形二 同上
// 如果沒有相交,返回 [0, 0, 0, 0]
// 如果相交,假設相交矩形對角坐標 (x0, y0) (x1, y1) -- x1 > x0 & y1 > y0
// return [x0, y0, x1, y1]
function check(A, B, C, D, E, F, G, H) {
  // 轉為對角線坐標
  C += A, D += B, G += E, H += F;

  // 沒有相交
  if (C <= E || G <= A || D <= F || H <= B)
    return [0, 0, 0, 0];

  var tmpX, tmpY;

  if (E > A) {
   tmpX = G < C ? [E, G] : [E, C];
  } else {
   tmpX = C < G ? [A, C] : [A, G];
  }

  if (F > B) {
   tmpY = H < D ? [F, H] : [F, D];
  } else {
   tmpY = D < H ? [B, D] : [B, H];
  }

  return [tmpX[0], tmpY[0], tmpX[1], tmpY[1]];
}

// 相交矩形坐標信息
var rect = check(fish.pos.x, fish.pos.y, fish.size.x, fish.size.y, 
  cat.pos.x, cat.pos.y, cat.size.x, cat.size.y);

// 相交面積大於 0 即為碰撞
var isHit = (rect[2] - rect[0]) * (rect[3] - rect[1]) > 0;

像素級碰撞檢測#

為什么要有像素級檢測?一圖以蔽之。

一般游戲或者動畫中的精靈都是矩形,僅僅判斷矩形相交是不准確的,比如上圖中,圖片所在矩形已經相交,但是精靈其實並沒有碰撞,所以我們需要進行像素級別的碰撞檢測。

方法一:

同時檢測兩圖在相交矩形內的像素,若存在一點在兩個圖上的 alpha 值不為 0,則發生碰撞。

因為還要對原始的圖像(fish 圖和 cat 圖)分別提取像素點(進行判斷),所以需要一個離屏的 canvas 。這里用了 canvas 的 getImageData 方法提取像素點 rgba 信息。

// a, b 為精靈對象
// a, b 分別擁有鍵值 img(精靈圖像 DOM元素), pos(精靈瞬間位置 top-left 坐標), size(wdith, height 數據)
// rect 參數為 check() 函數返回值
function checkInDetail(a, b, rect) {
  // 離屏 canvas
  var canvas = document.createElement('canvas');
  _ctx = canvas.getContext('2d');

  _ctx.drawImage(a.img, 0, 0, a.size.x, a.size.y);
  // 相對位置
  var data1 = _ctx.getImageData(rect[0] - a.pos.x, rect[1] - a.pos.y, rect[2] - rect[0], rect[3] - rect[1]).data;

  _ctx.clearRect(0, 0, b.size.x, b.size.y);
  _ctx.drawImage(b.img, 0, 0, b.size.x, b.size.y);
  var data2 = _ctx.getImageData(rect[0] - b.pos.x, rect[1] - b.pos.y, rect[2] - rect[0], rect[3] - rect[1]).data;

  canvas = null;

  for(var i = 3; i < data1.length; i += 4) {
    if(data1[i] > 0 && data2[i] > 0) 
      return true; // 碰撞
  }

  return false;
}

// 精靈對象實例
var fish = {
  img: document.getElementById('fish')
  , pos: new Vector2()
  , size: new Vector2()

  // ...
};

方法二:

先畫一張圖,然后將混合模式改為 source-in,這時再畫圖,新圖片會僅僅出現與原有內容重疊的地方,其他地方透明度變為 0,這時就可以通過判斷是否所有像素都透明來判斷碰撞了。

// a, b 為精靈對象
// a, b 分別擁有鍵值 img(精靈圖像 DOM元素), pos(精靈瞬間位置 top-left 坐標), size(wdith, height 數據)
// rect 參數為 check() 函數返回值
function _checkInDetail(a, b, rect) {
  // 離屏 canvas
  var canvas = document.createElement('canvas');
  _ctx = canvas.getContext('2d');

  // 將 (0, 0) 作為基准點,將 a 放入 (0, 0) 位置
  _ctx.drawImage(a.img, 0, 0, a.size.x, a.size.y);
  _ctx.globalCompositeOperation = 'source-in';
  _ctx.drawImage(b.img, b.pos.x - a.pos.x, b.pos.y - a.pos.y, b.size.x, b.size.y);

  var data = _ctx.getImageData(rect[0] - a.pos.x, rect[1] - a.pos.y, rect[2] - rect[0], rect[3] - rect[1]).data;

  canvas = null;

  // 改回來(雖然並沒有什么卵用)
  _ctx.globalCompositeOperation = 'source-over';
    
  for(var i = 3; i < data.length; i += 4) { 
    if (data[i]) 
      return true;  // 碰撞
  }

  return false;
}

我測試了幾次,把相交的像素點都取了出來求得相交像素點總數,兩種方法有時會相差一兩個像素點。對於像素級碰撞檢測來說,兩種方法任取其一就可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM