独習 Unity アプリ開発

独習でスマフォ向けアプリ開発を勉強中

Unity で テトリス風ゲームを作ってみる(実装編)#2

 Unity Version 2022.3.4

 

前回の続き


全体像の整理はできたので、各クラスの具体的な実装をユースケースごとに説明していく。

 

テトリミノの表示と落下


Tetrimino と Block は、動的に生成するためPrefab 化する。Prefab 化しておくと簡単にその複製(インスタンス)をスクリプト上から作成できるようになる。

 

Tetrimino Prefab の構成は下図のとおり。Tetrimino Prefab を親としBlock Prefab 4 つを子にするように親子関係を作る。こうすることで、Tetrimino Prefab を左右上下に動かせば、子であるBlock Prefabも一緒に動くため、Block Prefab を個別に操作する必要がなくなる。

また、キー入力イベントを受け取るため、Tetrimino Prefab にはPlayer Input コンポーネントもアタッチしておく。ブロックの表示自体は、Block Prefab で行うためTetrimino Prefab にはSprite Renderer コンポーネントをアタッチしていない。

TetoriminoPrefab

 

次にBlock Prefab は、Sprite Renderer とBox Collider 2D コンポーネントだけをアタッチしている単純な構造する。スクリプトもアタッチする必要はない。

Box Collider 2D コンポーネントは、Raycastで検出できるようにするため必要。後ほど説明するが、Layer には "Tetrimino" レイヤーを作りそれを指定しておく。

Block Prefab

 

NextArea.cs : Tetrimino 生成部


Tetrimino Prefab は、NextArea クラスが生成する。

生成する方法は単純で、Resources クラスを使ってPrefab asset をロードし、Object.Instantiate メソッドにてGameObject のインスタンス化を行う(MonoBehaviour クラスはObject クラスの派生クラスなので直接呼出しが可能)。

インスタンス化したテトリミノGameObject から GetComponent メソッドを使って Tetrimino にアタッチされている Tetrimino クラスのインタンスを取得する。

Tetrimino の種類はランダムに生成したいので、Random.Range メソッドにて乱数を生成し、Tetrimino.Type にキャストして、それをつかってTetrimino を初期化する。

最後に生成したテトリミノGameObject の画面上の位置をNextArea の中央に配置するように Transform のlocalPosition を設定してあげれば終了。

 

NextArea.cs の抜粋

  1.   // Tetrimino Object
  2.   private Tetrimino _next;
  3.  
  4.   public void CreateNextTetrimino () {
  5.     _next = CreateTetriminoByRandom();
  6.   }
  7.   
  8.   private Tetrimino CreateTetriminoByRandom () {
  9.     var prefab = Resources.Load("Prefabs/Tetrimino") as GameObject;
  10.     var copyObject = Instantiate(prefab, this.transform);
  11.     var next = copyObject.GetComponent<Tetrimino>();
  12.     var type = (Tetrimino.Type)UnityEngine.Random.Range(0, (int)Tetrimino.Type.MAX);
  13.     next.Init(type);
  14.  
  15.     // set position in Center of NextArea
  16.     var width = next.GetWidth();
  17.     var height = next.GetHeight();
  18.     next.transform.localPosition = new Vector3(
  19.       (WIDTH - width) * 0.5f,
  20.       (HEIGHT - height) * 0.5f,
  21.       0);
  22.     return next;
  23.   }

 

Tetrimino.cs : ブロックの配置


テトリミノ内の4 つのブロックの位置は、種類と回転角の数だけ座標を配列として定義しておく。

テトリミノの定義

 

テトリミノの種類は7 種類、回転角は4 種類(0度, 90度, 180度, 270度)で合計28 個。ちょっと面倒くさいが、地道に書き出した結果が下記。

Tetrimino.cs の抜粋

  1.   private readonly int [,,] TETRIMINO = new int [,,] {
  2.     new int[,,] { // I
  3.       {
  4.         {0,0}, {1,0}, {2,0}, {3,0} // 0
  5.       },{
  6.         {1,0}, {1,1}, {1,2}, {1,3} // 90
  7.       },{
  8.         {0,0}, {1,0}, {2,0}, {3,0} // 180
  9.       },{
  10.         {1,0}, {1,1}, {1,2}, {1,3} // 270
  11.       }
  12.     },
  13.     new int[,,] { // L
  14.       {
  15.         {0,0}, {1,0}, {2,0}, {2,1} // 0
  16.       },{
  17.         {1,0}, {1,1}, {1,2}, {0,2} // 90
  18.       },{
  19.         {0,0}, {0,1}, {1,1}, {2,1} // 180
  20.       },{
  21.         {0,0}, {1,0}, {0,1}, {0,2} // 270
  22.       }
  23.     },
  24.     new int[,,] { // J
  25.       {
  26.         {0,0}, {1,0}, {2,0}, {0,1} // 0
  27.       },{
  28.         {0,0}, {1,0}, {1,1}, {1,2} // 90
  29.       },{
  30.         {0,1}, {1,1}, {2,1}, {2,0} // 180
  31.       },{
  32.         {0,0}, {0,1}, {0,2}, {1,2} // 270
  33.       }
  34.     },
  35.     new int[,,] {
  36.       // T
  37.       {
  38.         {0,0}, {1,0}, {2,0}, {1,1} // 0
  39.       },{
  40.         {1,0}, {0,1}, {1,1}, {1,2} // 90
  41.       },{
  42.         {1,0}, {0,1}, {1,1}, {2,1} // 180
  43.       },{
  44.         {0,0}, {0,1}, {0,2}, {1,1} // 270
  45.       }
  46.     },
  47.     new int[,,] {
  48.       // S
  49.       {
  50.         {0,0}, {1,0}, {1,1}, {2,1} // 0
  51.       },{
  52.         {1,0}, {0,1}, {1,1}, {0,2} // 90
  53.       },{
  54.         {0,0}, {1,0}, {1,1}, {2,1} // 180
  55.       },{
  56.         {1,0}, {0,1}, {1,1}, {0,2} // 270
  57.       }
  58.     },
  59.     new int[,,] {
  60.       // Z
  61.       {
  62.         {1,0}, {2,0}, {0,1}, {1,1} // 0
  63.       },{
  64.         {0,0}, {0,1}, {1,1}, {1,2} // 90
  65.       },{
  66.         {1,0}, {2,0}, {0,1}, {1,1} // 180
  67.       },{
  68.         {0,0}, {0,1}, {1,1}, {1,2} // 270
  69.       }
  70.     },
  71.     new int[,,] {
  72.       // O
  73.       {
  74.         {0,0}, {1,0}, {0,1}, {1,1} // 0
  75.       },{
  76.         {0,0}, {1,0}, {0,1}, {1,1} // 90
  77.       },{
  78.         {0,0}, {1,0}, {0,1}, {1,1} // 180
  79.       },{
  80.         {0,0}, {1,0}, {0,1}, {1,1} // 270
  81.       }
  82.     }
  83.   };
  84.  

 

実際にブロックを配置するには、種類(Tetrimino.Type)と回転(Tetrimino.Rotation)を配列のインデックスに変換して、4 つのブロック座標を示す配列データを取得する。

  1.       positions[i] = new Vector2Int(
  2.         TETRIMINO[(int)type][(int)rotation, i, 0],   // X
  3.         TETRIMINO[(int)type][(int)rotation, i, 1]);  // Y

 

それを使ってBlock Prefab のtransform.localPosition にVector2 形式で座標値を設定すればよい。ブロックのテクスチャを128 pixel で1 Unit に設定しているため、座標定義の値をそのままlocalPosition の値に代入すればOK。

Tetrimino.cs の抜粋

  1.   private void LocateBlocks () {
  2.     var positions = BlocksPosition(_type, _rotation);
  3.     var idx = 0;
  4.     foreach (var block in _blockPrefabs) {
  5.       block.transform.localPosition = new Vector2(positions[idx].x, positions[idx].y);
  6.       idx++;
  7.     }
  8.   }
  9.  
  10.   private Vector2Int[] BlocksPosition (Type type, Rotation rotation) {
  11.     var positions = new Vector2Int[4];
  12.     for (var i = 0; i < 4; i++) {
  13.       positions[i] = new Vector2Int(
  14.         TETRIMINO[(int)type][(int)rotation, i, 0],
  15.         TETRIMINO[(int)type][(int)rotation, i, 1]);
  16.     }
  17.     return positions;
  18.   }

 

Tetrimino.cs : 落下


Tetrimino を1 ブロック分下に移動するには、単純にlocalPosition のY 座標を-1 すればよい(1ブロック分 128 pixel = 1 Unit になっているので)。

移動する前には、各ブロックの下方向にRaycast を打ってブロックがないことを確認する。

Physics2D.Raycat メソッドは、引数のバリエーションがいくつか存在するが、今回は複数のブロックを同時に検出する必要はないので、検出したCollider が1 つ戻り値として返るバージョンを使用している。

Raycat のスタート位置は自分自身の中心に設定。テクスチャのPivot を左下原点にしているので、ブロックの中心は縦、横方向にそれぞれ高さ、幅の1/2 を足した位置が中心となる。あとRaycast で指定する座標は絶対値指定なことに注意。

 

public static RaycastHit2D Raycast (

Vector2 origin,                                           // Raycast の開始位置

Vector2 direction,                                      // Raycast の向き 

float distance= Mathf.Infinity,                 // Raycast の長さ

int layerMask= DefaultRaycastLayers,    // 判定する対象のレイヤー

float minDepth= -Mathf.Infinity,            // 判定する対象のZ軸方向の最初値

float maxDepth= Mathf.Infinity);           // 判定する対象のZ軸方向の最大値

 

Raycast の長さについては、1 ブロック分だけ検出できればよいのでブロック幅の1/2 に設定する。LayerMask は、検出Collider をフィルタすることができるパラメータである。検出の必要な "Field" Layer だけを指定する(フィールドの枠で使うBlock オブジェクトや、フィールドに着地したBlock オブジェクトのLayer は Layer を"Field" に指定しているため)。Tetrimino にあるブロックは、上で説明したとおり "Tetrimino" Layer をしているので検出対象から除外される。

Tetrimino.cs の抜粋

  1.   // return true if collision is detected
  2.   private bool FreeFall () {
  3.     var isCollision = false;
  4.     foreach (var block in _blockPrefabs) {
  5.       if (CollisionCheck (block.transform, Vector2.down)) {
  6.         isCollision = true;
  7.         break;
  8.       }
  9.     }
  10.  
  11.     if (isCollision) return true;
  12.  
  13.     this.transform.localPosition = new Vector3(
  14.       this.transform.localPosition.x,
  15.       this.transform.localPosition.y - 1.0f,
  16.       this.transform.localPosition.z);
  17.  
  18.     return false;
  19.   }
  20.  
  21.   private bool CollisionCheck (Transform block, Vector2 direct) {
  22.     var start = new Vector2 (
  23.       block.position.x + block.lossyScale.x * 0.5f,
  24.       block.position.y + block.lossyScale.y * 0.5f);
  25.     var length = block.lossyScale.x; // ブロックは正方形なので、xとyの長さは同じ
  26.  
  27.     var hit = Physics2D.Raycast (
  28.       start, // レイの原点
  29.       direct, // レイの方向
  30.       length, // レイの長さ
  31.       LayerMask.GetMask("Field") // チェックする対象を設定するフィルター
  32.       );
  33.  
  34.     //Debug.DrawRay(start, direct * length, Color.red, 1f); // Debug用
  35.     return (hit.collider != null)? true : false;
  36.   }
  37.  

 

フィールドの枠を生成する時に、Block オブジェクトのGameObject.layer プロパティに "Field" の値を設定しておく。

 copyObject.layer = ScreenConfig.FIELD_LAYER;

 

Field.cs の抜粋

  1.   // Create Field Block Object from Prefab.
  2.   private void CreateFieldBlock (Vector2Int pos) {
  3.     var prefab = Resources.Load("Prefabs/Block") as GameObject;
  4.     var copyObject = Instantiate(prefab, this.transform);
  5.     copyObject.transform.localPosition = new Vector3(pos.x, pos.y, 0);
  6.     copyObject.layer = ScreenConfig.FIELD_LAYER;
  7.     _fieldBlocks.Add(copyObject);
  8.   }
  9.  

 

Layer は、GameObject のLayer ドロップダウンリストから Add Layer を選択することで、自分で独自のものを追加できるようになる。下図のように"Field" を "User Layer 7" に指定したので、このLayer をプロパティで指定するときは、GameObject.layer にint型で7を設定すればOK.

Layer

 

次の記事


from20150817.hatenablog.com


目次に戻る