今回はゲームの状態進行度を表すクラスや射出されフィールドにはまったブロックが落下する処理を書いていきます。
単一ブロックではなくユニットとして処理を行わないといけない&通常テトリスではなくシューティングテトリスにしたことにより処理がやや複雑化していきます💦
がんばって作っていきましょう!
前回の記事↓
ゲームの状態管理
まずは、実装しやすくするため、変数を利用して状態を分けていきます。
今回は、「ブロックを発射する状態」「ブロックが落下する状態」「ブロックが消える状態」の3種類の状態を作成していきましょう。ゲーム全体で使用される変数は静的クラスとして定義するとコーディングしやすくなります。
そのため、新しくGameResource.csファイルを作成してください。コードは以下のように記述してください。
1 2 3 4 5 6 7 8 9 |
public class GameStatus { // ゲームの状態を保持 // 「ブロックを発射する状態」 : Shot // 「発射後の待機状態」 : Wait // 「ブロックが落下する状態」 : Fall // 「ブロックが消える状態」 : Delete static public string status = "Shot"; } |
そして、BlockBazooka.csのUpdate()に以下を追加しておきましょう。今後、「Wait」や「Fall」や「Delete」も追加していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void Update() { switch (GameStatus.status) { case "Shot": //A/Xボタンを押したらブロックを発射 if (OVRInput.GetDown(OVRInput.Button.One, controller)) { Fire(transform.position, transform.forward, CreateBlock(Random.RandomRange(0, 7))); } break; case "Wait": break; case "Fall": break; case "Delete": break; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void Update() { switch (GameStatus.status) { case "Shot": break; case "Wait": break; case "Fall": break; case "Delete": break; } } |
【微修正】ブロックを連射できないようにする
まず、現在の状態だと、ブロックを連射できてしまうので、Fire()を以下のように変更してください。
1 2 3 4 5 6 7 8 9 10 11 12 |
//ブロックを発射する関数 void Fire(Vector3 startPos, Vector3 direction, GameObject target) { //ブロックのコピーを生成させないようにコメントアウト // GameObject go = Instantiate(target); //ブロックの発射位置を設定 target.transform.position = startPos; //ブロックをコントローラ正面方向に放つ target.GetComponent<Rigidbody>().AddForce(direction*10f,ForceMode.Impulse); //状態を「Wait」に変更 GameStatus.status = "Wait"; } |
このように書き換えると、Fire()を一回行うと状態が「Wait」になるため連射しなくなります。そして、Field.csのUpdate()を以下のように書き換えましょう。
1 2 3 4 5 6 7 |
void OnCollisionEnter(Collision other) { if(other.gameObject.tag != "blockUnits") return; GameStatus.status = "Fall"; // 以下略 |
このようにすることで、フィールドにブロックユニットがくっついたタイミングでstatusを”Fall”に変更させることができます。(現在の状態だと、フィールド外にブロックを発射すると新しいブロックを発射できなくなります。)
ブロックを落下させる処理
次に、ブロックを落下させる処理を実装していきます。まず、落下させるブロックを保持するリストとして、Field.csに以下を定義してください。
1 2 |
[SerializeField] List<GameObject> blockList; |
つぎに、落下させるブロックを登録する処理を追加します。ApplyBlockUnits()を以下のように修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// ブロックを登録する関数 void ApplyBlockUnits(GameObject target, int x, int y) { // 落下させるブロックリストを初期化 blockList = new List<GameObject>(); for(int i=0;i<target.transform.childCount;i++) { Vector3 g = target.transform.GetChild(i).localPosition; int bx = (int)(g.x*10); int by = (int)(g.y*10); blocks[x + bx, y + by] = true; //参照できるように名前を設定 target.transform.GetChild(i).name = $"name:{x + bx},{y + by}"; //落下用ゲームオブジェクトに設定 blockList.Add(target.transform.GetChild(i).gameObject); } } |
つぎに、落下処理の関数としてFallBlocks()を以下のように記述してください。
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 |
void SortBlockList() { for (int i = 0; i < blockList.Count; i++) { for (int j = i; j < blockList.Count; j++) { if (blockList[i].GetComponent<Block>().y > blockList[j].GetComponent<Block>().y) { var tmp = blockList[i]; blockList[i] = blockList[j]; blockList[j] = tmp; } } } } // ブロックを一段だけ落下させる処理 bool FallBlocks() { var retblocks = blocks.Clone() as bool[,]; // ソート SortBlockList(); // ブロックが落とせる状態にあるかチェック bool isFall = true; // 一個下にブロックがあるかチェック foreach (GameObject go in blockList) { int x = go.GetComponent<Block>().x; int y = go.GetComponent<Block>().y; if (y - 1 < 0 || retblocks[x, y - 1]) { isFall = false; break; } retblocks[x, y] = false; } if (isFall) { // ブロックを配置 foreach (GameObject go in blockList) { int x = go.GetComponent<Block>().x; int y = go.GetComponent<Block>().y; go.transform.position += Vector3.down * 0.1f; blocks[x, y] = false; blocks[x, y - 1] = true; go.GetComponent<Block>().y -= 1; go.name = $"name:{x},{y - 1}"; } } return isFall; } |
この関数では、以下の順の処理を行っています。
- ブロックリストを高さ方向にソート
- 全てのブロックの一個下の位置にブロックが存在するかチェック
- 存在しなければブロックを再配置
- 存在すれば落下処理を終了させる(返り値をFalseで返す)
ちなみに、冒頭で行っているソートは、isFallを正しく機能させるために必要な処理です。例えば、ソートしない場合のブロックの登録順が下図のような場合、1番のブロックを落下させようとすると4番が配置されているため落下できなくなります。今回のソートは、ブロックリストを高さ方向(y軸方向)の高さが低い順にソートさせるものです。そのため、ブロックの干渉が起こらずうまく動作するようになります。
そして、Update()のFall状態の箇所に以下を追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void Update() { switch (GameStatus.status) { case "Shot": break; case "Wait": break; case "Fall": if( !FallBlocks() ) { GameStatus.status = "Shot"; } break; case "Delete": break; } } |
FallBlocks()の返り値は「ブロックを落下させることができたか」を示しています。この処理では、「落下させることができなかった」場合にstatusをShotに変更するようにしています。これにより、落下処理が完了したタイミングでプレイヤーが新しいブロックを発射できるようになります。
以下をすべて完了させると、次のような結果になります。
次回
次回は一列そろったら消去する処理を実装していきます。
次回の記事↓
コメント