ゲーム開発ラボ

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

テトリスを作る-4 当たり判定にもとづくブロックの移動

前回はテトリスのブロックTetriminoの衝突判定を実装しました
processing-p5.hateblo.jp

今回は,前回実装した衝突判定にもとづいてブロックの移動を制御していきます
これにより,ブロックがフィールド外にはみ出たり,ブロックどうしが重なったりするのを防いでいます
下図が今回のプログラムの様子です
f:id:filopodia:20201127210029g:plain

今回のコードは以下となります

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('j'); 
}

void draw(){
  background(100);
  fill(255);
  stroke(200);
  for(int i=3; i<col_num+3; i++){ 
    for(int j=3; j<row_num+3; j++){
      if(!field[i][j]){fill(255);}
      else{fill(0, 0, 100);}
      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 = 4;
    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 SHIFT: 
        if(!isCollide(rotate(), x, y))layout=rotate(); // 回転しても衝突しない場合
        else{ // 回転した場合衝突してしまうが,座標移動すると回転できる場合
          if(!isCollide(rotate(), x-1, y)){layout=rotate(); x--;}
          else if(!isCollide(rotate(), x+1, y)){layout=rotate(); x++;}
          else if(!isCollide(rotate(), x, y--)){y--;layout=rotate();}
        }
        break; // ブロックの回転
      case LEFT :
        if(!isCollide(layout, x-1, y))x--; break;
      case RIGHT:
        if(!isCollide(layout, x+1, y))x++; break;
      case DOWN :
        if(!isCollide(layout, x, y+1))y++; break;
    }    
  }
  
  boolean[][] rotate(){ // 回転したあとの配列を戻り値として返す
    boolean[][] tmp = new boolean[4][4]; 
    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];
            tmp[j][3-i] = layout[i][j];
          }
        }
        break;
      default: // その他
        for(int i=1; i<4; i++){ 
          for(int j=0; j<3; j++){
            //layout[i][j] = tmp[j+1][3-i];
             tmp[j+1][3-i] = layout[i][j];
          }
        }
        break;
    }
    return tmp;
  }
  
  boolean isCollide(boolean[][] _tmp, int _x, int _y){ // ブロックが壁や他のブロックに衝突しているか
    boolean result = false;    
    boolean[][] tmp = _tmp;
    int xx = _x;
    int yy = _y;
    
    for(int i=0; i<4; i++){
      for(int j=0; j<4; j++){
        if(tmp[i][j]&&field[xx+i-2][yy+j-1]){
          result=true;
        }
      }
    }
    return result;
  }  
}


前回のコードと比較して,大きく変化した点は以下の3つです
・Tetriminoクラスのクラス変数keyPressed()において,ブロックの衝突が起こらないときにのみ,ブロックを移動するよう変更
・Tetriminoクラスのクラス変数isCollide()について,引数としてboolean配列,座標の値を用いるよう変更
・Tetriminoクラスのクラス変数rotate()について,boolean型配列を返すように変更

keyPressed()関数についてみてみましょう
座標を格納しているクラス変数x, yについて,これら変数を1増減しても衝突しない場合(isCollide()関数がfalseを返す場合)にのみ,ブロックを移動するように記述しています
この記述により,ブロックがフィールドの外にはみ出させるような操作はできなくなります

  void keyPressed(){
    switch(keyCode){
      case SHIFT:  // ブロックの回転についての記述
      case LEFT :
        if(!isCollide(layout, x-1, y))x--; break; // x座標を-1しても衝突しないようであれば移動
      case RIGHT:
        if(!isCollide(layout, x+1, y))x++; break;
      case DOWN :
        if(!isCollide(layout, x, y+1))y++; break;
    }    
  }

次回はTetriminoブロックを積み上げる動作を実現したいと思います