ゲーム開発ラボ

視覚的に楽しいアプリやゲーム開発をしながら,Javaやjavascriptを楽しく学んでいきます

テトリスを作る-3 : ブロックの当たり判定

前回はブロック(Tetrimino)の回転ができるようにしました
processing-p5.hateblo.jp

今回はTetriminoの当たり判定の実装を行います
当たり判定の実装を行うことで,ブロックがフィールド外にはみ出したり,ブロック同士が重なることを防止できるようになります
下図は今回のプログラムの様子です
f:id:filopodia:20201126222751g:plain

今回のプログラムでは,Tetriminoブロックがフィールド外にはみ出た場合,フィールドの背景が赤色に切り替わるようにしています
コードは以下となります

int col_num=10, row_num=20; // セルの数
float c_w, c_h; // セルの幅と高さ

// フィールド上のセルにブロックが存在しているか
boolean[][] field = new boolean[col_num+6][row_num+6]; 

Tetrimino tetra1;

void setup(){
  frameRate(15);
  background(100);
  size(260, 460);
  c_w = width/(col_num+6.0);
  c_h = height/(row_num+6.0);
  
  // フィールドの初期化
  stroke(200);
  fill(255);
  for(int i=0; i<col_num+6; i++){ 
    for(int j=0; j<row_num+6; j++){
      if(i<=2||i>=col_num+3||j<=2||j>=row_num+3){
        field[i][j] = true; // フィールド外の領域についてtrueを格納
      }
      else {
        field[i][j] = false;
        rect(i*c_w, j*c_h, c_w, c_h); // フィールドの描画
      }
    }
  } 
  // テトリミノの初期化
  tetra1 = new Tetrimino('t'); 
}

void draw(){
  if(!tetra1.isCollide()){background(100);} // ブロックがフィールド内にあるときは灰色
  else{background(255,0,0);} // ブロックがフィールド外にあるときは赤色
  fill(255);
  stroke(200);
  for(int i=0; i<col_num+6; i++){ 
    for(int j=0; j<row_num+6; j++){
      if(!field[i][j])rect(i*c_w, j*c_h, c_w, c_h); // フィールドの描画
    }
  } 
  tetra1.display();
}

void keyPressed(){
  tetra1.keyPressed();
}

class Tetrimino{
  char type; // i, o, s, z, j, l, t
  int x, y; // poition
  boolean[][] layout = new boolean[4][4];
  
  // constructor
  Tetrimino(char _type){
    type = _type;
    x = 8; y = 5;
    initiate();
  }
  
  // methods
  void initiate(){
    for(int i=0; i<4; i++){
      for(int j=0; j<4; j++){
        layout[i][j] = false;
      }
    }
    switch(type){  // 種類ごとにlayoutを決定
      case 'i':
        for(int i=0; i<4;i++){layout[i][1]=true;}
        break;
      case 'o':
        layout[1][1]=layout[1][2]=layout[2][1]=layout[2][2]=true;
        break;
      case 's':
        layout[1][2]=layout[2][1]=layout[2][2]=layout[3][1]=true;
        break;
      case 'z':
        layout[1][1]=layout[2][1]=layout[2][2]=layout[3][2]=true;
        break;
      case 'j':
        layout[1][1]=layout[2][1]=layout[3][1]=layout[3][2]=true;
        break;
      case 'l':
        layout[1][1]=layout[2][1]=layout[3][1]=layout[1][2]=true;
        break;
      case 't':
      default:
        layout[1][1]=layout[2][1]=layout[3][1]=layout[2][2]=true;
        break;
    }    
  }
  void display(){
    // 種類ごとに描画
    for(int i=0; i<4; i++){
      for(int j=0; j<4; j++){
        if(layout[i][j]){stroke(0);fill(200);
          rect((x+i-2)*c_w, (y+j-1)*c_h, c_w, c_h);}
        }
     }
  }
  
  void keyPressed(){
    switch(keyCode){
      case UP   : rotate(); break; // ブロックの回転
      case LEFT :
        x--; break;
      case RIGHT:
        x++; break;
      case DOWN :
        y++; break;
    }    
  }
  
  void rotate(){ // Super Rotation System
    boolean[][] tmp = new boolean[4][4]; // layoutの情報を一時的に格納
    for(int i=0; i<4;i++){ // deep copy
      for(int j=0; j<4; j++){
        tmp[i][j] = layout[i][j];
      }
    }
    switch(type){ // 右回転
      case 'i': // I型
      case 'o': // O型
        for(int i=0; i<4; i++){ 
          for(int j=0; j<4; j++){
            layout[i][j] = tmp[j][3-i];
          }
        }
        break;
      default: // その他
        for(int i=1; i<4; i++){ 
          for(int j=0; j<3; j++){
            layout[i][j] = tmp[j+1][3-i];
          }
        }
        break;
    }     
  }
  
  boolean isCollide(){ // ブロックが壁や他のブロックに衝突しているかをチェック
    boolean result = false;
    for(int i=0; i<4; i++){
      for(int j=0; j<4; j++){
        if(layout[i][j]&&field[x+i-2][y+j-1]){
          result=true;
        }
      }
    }
    return result;
  }  
}


前回のコードから大きく変化した点は,Tetriminoクラスのクラス関数isCollide()を実装したことです
isCollide()関数では,Tetriminoの形状を格納した配列である"layout"と,フィールドの情報を格納した配列である'field'を参照し,trueの値が格納されている領域が重なったときにのみ,戻り値としてtrueを返しています

  boolean isCollide(){ // ブロックが壁や他のブロックに衝突しているかをチェック
    boolean result = false;
    for(int i=0; i<4; i++){
      for(int j=0; j<4; j++){
        if(layout[i][j]&&field[x+i-2][y+j-1]){ // layout配列とfield配列について,どちらもtrueの場合衝突したとみなす
          result=true;
        }
      }
    }
    return result;
  }  


上記のisCollide()関数は,下図のようにフィールド上のブロックとの衝突判定にも応用できます
f:id:filopodia:20201126225853g:plain

次回は今回実装した衝突判定のプログラムを利用し,Tetriminoブロックの回転や移動の制御を行いたいと思います