前回の記事で個々のテトリスブロックの形を変えて生成できるようになりました。
前回の記事:
今回の記事では実際にブロックをグリッド内に落下させるようにしていきます。
さらに、落下したブロックが一面に揃ったら消えるようにします。
ブロックを落とす処理の作成
実際にブロックを落下させ、グリッドに配置する処理はシーンを管理する「TetrisSceneManager」コンポーネントで行います。
「TetrisSceneManager」コンポーネントはブロックの落下処理の他にゲーム全体の流れの管理なども行います。
それでは新しく「TetrisSceneManager.cs」というスクリプトを作成してください。保存先はお好みでOKですが、記事ではAssetsフォルダーに保存しています。
「TetrisSceneManager.cs」の内容は次のようにしてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
// TetrisSceneManager.cs TetrisSceneManagerコンポーネントの作成 using System.Collections; using System.Collections.Generic; using UnityEngine; class TetrisSceneManager : MonoBehaviour { public Grid Grid; public Block BlockPrefab; public BlockTemplate[] DropBlocks; public float DropSpeed = 1f; public Vector3Int DropStartPos = new Vector3Int(0, 11, 0); void Start() { StartCoroutine(MainLoop()); } IEnumerator MainLoop() { var rnd = new System.Random(); while(true) { yield return null; var template = DropBlocks[rnd.Next(DropBlocks.Length)]; var block = Object.Instantiate(BlockPrefab, transform); block.SetTemplate(template); block.Pos = DropStartPos; //block.FillMass(Grid, false); while (!block.IsTouch(Grid, Vector3Int.zero)) { float t = 0f; while (t < DropSpeed) { t += Time.deltaTime; var offset = Vector3Int.zero; if (Input.GetKeyDown(KeyCode.LeftArrow)) offset.x += 1; else if (Input.GetKeyDown(KeyCode.RightArrow)) offset.x -= 1; if (Input.GetKeyDown(KeyCode.DownArrow)) offset.z += 1; else if (Input.GetKeyDown(KeyCode.UpArrow)) offset.z -= 1; block.Move(Grid, offset); yield return null; } block.DropMove(1); } { var pos = block.Pos; pos.y++; block.Pos = pos; } block.FillMass(Grid); Object.Destroy(block.gameObject); CheckGridDepth(); } } void CheckGridDepth() {} } |
「TetrisSceneManager」コンポーネントが作成できたら、シーンにある「Tetris」GameObjectにアタッチしてください。
ブロックがグリッドに配置できるようにする
今のままではエラーが出ていますが、ブロックをグリッド内で落下できるようにしました。
次はブロックをグリッド内に配置できるようにしましょう!
この講座では次の条件を満たしたら、ブロックをグリッドに配置するようにします。
- ブロックが落下した時に、他のブロックと重なる場合
- グリッドの一番下まで到達した時
それではスクリプトの方を修正していきましょう!
ブロックが落ちた時に他のブロックと重なる処理を行う部分は上の「TetrisSceneManager」コンポーネントの「MainLoop」コルーチンにすでに書いているので、その中でまだ実装されていない部分を作成していきます。
「Block」コンポーネントを次のように修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
// Block.cs ブロックを配置できるようにする //次のものを追加 public void FillMass(Grid grid, bool doRemove = false) { var pos = Vector3Int.zero; for (pos.y = 0; pos.y < Size.y; ++pos.y) { for (pos.z = 0; pos.z < Size.z; ++pos.z) { for (pos.x = 0; pos.x < Size.x; ++pos.x) { var p = Instance.ToRotateCoordination(pos) + Pos; if (grid.Contains(p) && Instance.Contains(pos) && Instance[pos] == MassType.Mass) { grid[p].Type = doRemove ? MassType.Empty : MassType.Mass; } } } } } public bool IsTouch(Grid grid, Vector3Int offset) { var minPos = MinPos; if (minPos.y + offset.y < 0) return true; var pos = Vector3Int.zero; for (pos.y = 0; pos.y < Size.y; ++pos.y) { for (pos.z = 0; pos.z < Size.z; ++pos.z) { for (pos.x = 0; pos.x < Size.x; ++pos.x) { var p = Instance.ToRotateCoordination(pos) + Pos + offset; if (Instance.Contains(pos) && Instance[pos] == MassType.Mass && grid.Contains(p) && grid[p].Type == MassType.Mass) { return true; } } } } return false; } public void Move(Grid grid, Vector3Int move) { if (IsTouch(grid, move)) return; var minPos = MinPos; var maxPos = MaxPos; if (minPos.x + move.x < 0) move.x -= (minPos.x + move.x); if (minPos.y + move.y < 0) move.y -= (minPos.y + move.y); if (minPos.z + move.z < 0) move.z -= (minPos.z + move.z); if (maxPos.x + move.x > grid.Size.x) move.x -= (maxPos.x + move.x) - grid.Size.x; if (maxPos.z + move.z > grid.Size.z) move.z -= (maxPos.z + move.z) - grid.Size.z; var pos = Pos + move; pos.x = Mathf.Clamp(pos.x, 0, grid.Size.x-1); pos.z = Mathf.Clamp(pos.z, 0, grid.Size.z-1); Pos = pos; } public void DropMove(int moveY) { var pos = Pos; pos.y -= moveY; Pos = pos; } |
この段階でエラーが消えているはずです。
各ゲームオブジェクトの設定内容は次のものにしてください。
1 2 3 4 5 6 7 8 |
// TetrisSceneManagerコンポーネントの設定内容 Grid : シーンの「Grid」 BlockPrefab : 「Block」プレハブ DropBlocks : 「Block1」から「Block5」を設定 ※ 確認時はどれか一つだけでもOK DropSpeed : 1 DropStartPos : 2, 11, 2 //「Grid」のサイズに合わせたものでOK |
上のコードでは矢印キーでブロックを左右前後に移動できるようにしています。
ただし、カメラの向きによっては操作が反対になったりするので、その辺りはカメラの位置によってキー入力処理を修正してください。
ここまでできたら、再生して実際の動作を確認してください。
ちなみに上の画像では一番下の部分がわかるように次にGameObjectを追加しています。
1 2 3 4 5 6 7 8 |
// 背景用のGameObject ## Tetris > background : 3DObject > Cube - Transform Position : 2, -1, 2 Scale : 6, 1, 6(余白を作りたくなければ(5, 1, 5)) - MeshRenderer Materials : 色分け用のマテリアルを新しく作成して設定 |
また、ブロックをグリッドに積み重ねることができるようになっていればOKです。
同じ高さのマスがブロックで埋まったら消すようにする
次にブロックを配置した際に同じ高さのマスが全てブロックで埋まったらその高さのマスのブロックを消すようにします。
その際、その高さより上にあるブロックを一つ下のマスに移動させるようにします。
「Grid」コンポーネントを次のように修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// Grid.cs 指定した高さのマスを削除する処理を追加 //次のものを追加 public bool DoFillDepth(int depth) { if (depth < 0 || Size.y <= depth) return false; for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { if (this[x, depth, z].Type == MassType.Empty) return false; } } return true; } public bool RemoveDepth(int depth) { if (depth < 0 || Size.y <= depth) return false; for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { this[x, depth, z].Type = MassType.Empty; } } for (var y = depth+1; y < Size.y; ++y) { for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { this[x, y-1, z].Type = this[x, y, z].Type; this[x, y, z].Type = MassType.Empty; } } } return true; } |
次に「TetrisSceneManager」コンポーネントを次のように修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// TetrisSceneManager.cs ブロックを配置した時に同じ高さのマス全てがブロックで埋まったか判定する処理を追加 //次のものを修正 void CheckGridDepth() { for (var y=0; y<Grid.Size.y; ++y) { if(Grid.DoFillDepth(y)) { Grid.RemoveDepth(y); y--; } } } |
ここまでできましたら、再生してみて動作を確認してください。
同じ高さのマスが全てブロックで埋まった時にその高さのマスのブロックを削除し、それより上にあるブロックが一つ下のマスに移動するようになっていればOKです。
まとめ
今回の記事ではブロックがグリッドの中を落ちるようしました。
ブロックが落ちた時にブロックを積み重ねていくようにしていますが、その際に色分けすると見やすくなるので余力がある方は改造してみるのもいいでしょう。
落下地点に予め印を付けるなどの処理を入れると遊びやすくなりそうですね。
また、一面揃えて消えるだと難易度が高すぎるところがあるので、一列で消えるように仕様設定したほうがよかったかもしれません。
作ってみるとこんな感じで改善点がたくさん出てきます。が、講座としてはやりません。すみません(^^;
腕試しとして改造にチャレンジしてみてください。
まとめると以下のようになります。
- ブロックを落下させ、グリッド内に配置するようにした
- 落下したブロックが一面揃っているかの判定を行う
- 一面に落下がそろっていればブロックをまとめて一面分消す
それでは次の記事に行ってみましょう!
次の記事:
コメント