テトリスを作る-3 : ブロックの当たり判定
前回はブロック(Tetrimino)の回転ができるようにしました
processing-p5.hateblo.jp
今回はTetriminoの当たり判定の実装を行います
当たり判定の実装を行うことで,ブロックがフィールド外にはみ出したり,ブロック同士が重なることを防止できるようになります
下図は今回のプログラムの様子です
今回のプログラムでは,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()関数は,下図のようにフィールド上のブロックとの衝突判定にも応用できます
次回は今回実装した衝突判定のプログラムを利用し,Tetriminoブロックの回転や移動の制御を行いたいと思います