【Processing】当たり判定を”色”で検出するテクニック

はじめに

Processingによる当たり判定の検出は,座標を用いて行われることがほとんどだと思います.しかし,座標の当たり判定を書くのは結構大変です.大変さの要因として,

  • 「当たる側」と「当てられる側」の座標を両方考慮しないといけないこと
  • 座標の大小関係を考慮する必要があること

があります.

一方で,色による当たり判定は簡単です.この判定方法の利点は,

  • 「当たる側」の座標だけを考慮すれば良いこと
  • 座標の大小関係を考慮する必要がないこと

です.一方で,条件式の数が増えがちという欠点はあります.

隙があるので少し自分語りをすると,このテクニックは,僕が学部一年の時に作ったゲームに広く使用しました.一つの適用例として,序盤で挙げさせていただきます(せ,宣伝ではないです).壁や床を黒で統一し,主人公(の青い四角)との当たり判定を色によって行っています.

https://www.konan-u.ac.jp/faculty/ii/public/prog1/showcase/2017/1/index.html

本記事では,座標の当たり判定をざっとおさらいしつつ,色による当たり判定を紹介します.

目次

まずは座標を使った当たり判定

まずはおさらいも兼ねて,代表的な座標による当たり判定に触れておきましょう.

分かりやすさのため,以下のようなゲーム(のようなもの)を考えます.2つの四角があって,白い四角はマウスで操作できます.また,赤い四角と少しでも重なるとき,Collisionという文字列が上部に表示されます.

f:id:gotutiyan:20201015010444g:plain

int x1,y1,w1,h1,x2,y2,w2,h2;
void setup(){
  size(500, 500);
  textSize(30);
  x1 = y1 = 200;
  w1 = h1 = w2 = h2 = 100;
}

void draw(){
  background(255);
  fill(255, 0, 0);
  rect(x1, y1, w1, h1);
  x2 = mouseX;
  y2 = mouseY;
  fill(255);
  rect(x2, y2, w2, h2);
  if(rect_collision_with_pos(x1, y1, w1, h1, x2, y2, w2, h2)){
    fill(0);
    text("Coliision", 200, 100);
  }
}

boolean rect_collision_with_pos(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2){
  return(x2 <= x1+w1 && 
         x1 <= x2+w2 && 
         y2 <= y1+h1 && 
         y1 <= y2+h2);
}

当たり判定は以下の関数が担っています.

boolean rect_collision_with_pos(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2){
  return(x2 <= x1+w1 && 
         x1 <= x2+w2 && 
         y2 <= y1+h1 && 
         y1 <= y2+h2);
}

この関数では,四角形と四角形の当たり判定を実現していますが,条件式が複雑です.慣れたらすぐに書けるのですが,変数名や不等号の向きを間違えるリスクが高いです.

色による当たり判定

次に色による当たり判定を紹介します.その前に,色を扱うためのメソッドを一つ紹介します.

get()メソッド

get()メソッドは,指定した座標の色を取得します.

get(x座標, y座標)

返り値はcolor型です.

例えば,

// マウスの座標が赤の時に何かしたい
if(get(mouseX, mouseY) == color(255,0,0)){
  // 処理
}

のように使えます.

実装

先ほどの例において,当たり判定の関数を書き換えてみます.

int x1,y1,w1,h1,x2,y2,w2,h2;
void setup(){
  size(500, 500);
  textSize(30);
  x1 = y1 = 200;
  w1 = h1 = w2 = h2 = 100;
}

void draw(){
  background(255);
  fill(255, 0, 0);
  rect(x1, y1, w1, h1);
  x2 = mouseX;
  y2 = mouseY;
  fill(255);
  rect(x2, y2, w2, h2);
  if(rect_collision_with_color(x2, y2, w2, h2)){
    fill(0);
    text("Coliision", 200, 100);
  }
}

boolean rect_collision_with_color(int x, int y, int w, int h){
  color red = color(255, 0, 0);
  int eps = 1;
  return(get(x-eps, y-eps) == red || // 左上の頂点の色
         get(x-eps, y+h+eps) == red || // 左下の頂点の色
         get(x+w+eps, y-eps) == red || // 右上の頂点の色
         get(x+w+eps, y+h+eps) == red); // 右下の頂点の色
}

この例では,以下に青色の点で示した座標の色を見て,当たり判定を検出します.頂点の座標は四角の枠線に重なるので,枠線の色を見てしまわないように,epsという変数を使って外側に1ピクセルずらしています.

f:id:gotutiyan:20201014232840p:plain

実行すると,座標の時とほぼ同じように当たり判定が検出できることが分かります.

ただし,自分より小さい物体の検出が苦手という欠点があります.例えば,辺の真ん中に小さい物体が突っ込んでくるようなことを考えると,4つの頂点を見るだけでは検出できません.

f:id:gotutiyan:20201014233145p:plain

このような場合に備えるために,色を見る座標を増やしてみます.例えば,辺の真ん中も見るようにしましょう.

boolean rect_collision_with_color(int x, int y, int w, int h){
  color red = color(255, 0, 0);
  int eps = 1;
  return(get(x-eps, y-eps) == red || // 左上の頂点の色
         get(x-eps, y+h+eps) == red || // 左下の頂点の色
         get(x+w+eps, y-eps) == red || // 右上の頂点の色
         get(x+w+eps, y+h+eps) == red || // 右下の頂点の色
         get(x+w/2, y-eps) == red || // 上の辺の真ん中
         get(x+w+eps, y+h/2) == red || // 右の辺の真ん中
         get(x+w/2, y+h+eps) == red || // 下の辺の真ん中
         get(x-eps, y+h/2) == red); // 左の辺の真ん中
}

この例では,以下の8点を見ていることになります.

f:id:gotutiyan:20201015000556p:plain

ここまですれば,ほとんどの場合に対応できるでしょう.座標での当たり判定よりも条件式の数は増えますが,一つ一つの条件式はずっと簡単に書けます.座標の大小を考慮する必要はありませんし,ある一つの物体の座標だけを考えれば実装できます.この点をどこまで細かく設定するかは,求める当たり判定の精度や,対象とする物体の大きさによります.場合によっては,辺の1/4地点も必要になるかもしれません.

実用例:床に着地させる

せっかくなので,より実用的な例を一つ載せたいと思います.

アクション系のゲームで,ある床があって,そこに着地させたいことがあると思います.仮にキャラの当たり判定領域を四角形だとして,床の色が黒だとすると,四角形と黒色の当たり判定として書けます.

int x, y, w, h;
void setup(){
  size(500, 500);
  textSize(30);
  x = 100;
  y = 0;
  w = h = 100;
}

void draw(){
  background(255);
  fill(0);
  rect(0, 400, width, 100); // 床のつもり
  fill(255);
  rect(x, y, w, h);
  // 当たり判定が検出されない間,落ち続ける
  if(!rect_collision_with_color(x, y, w, h)){
    y += 5; // 重力のつもり
  }
}

boolean rect_collision_with_color(int x, int y, int w, int h){
  color black = color(0, 0, 0);
  int eps = 1;
  return(get(x-eps, y-eps) == black || // 左上の頂点の色
         get(x-eps, y+h+eps) == black || // 左下の頂点の色
         get(x+w+eps, y-eps) == black || // 右上の頂点の色
         get(x+w+eps, y+h+eps) == black || // 右下の頂点の色
         get(x+w/2, y-eps) == black || // 上の辺の真ん中
         get(x+w+eps, y+h/2) == black || // 右の辺の真ん中
         get(x+w/2, y+h+eps) == black || // 下の辺の真ん中
         get(x-eps, y+h/2) == black); // 左の辺の真ん中
}

当たり判定が検出されない間落ち続けて,検出されると止まれば良いので,if分には当たり判定の関数を否定するものを入れます.中の処理にy座標を足すことを書けば,床への着地が実現できます.

f:id:gotutiyan:20201015003307g:plain

他の図形への応用

本記事では,四角形同士の当たり判定を扱いましたが,おそらく,他の図形でもできると思います.結局,色を見たい点をいくつか置いて,当たり判定の輪郭を作るようなイメージ(Unityでいうコライダー的なもの)なので,複雑な図形同士でもできると思います.

おわりに

本記事では,当たり判定を色で検出する話を書きました.うまく使えば,表現の幅を簡単に広げられる気がするので,ぜひ試してみてください.