Unityでテトリスを作成する方法

2024年6月28日

こんにちは!ジェイです。初心者講座の第1回目記念ということでテトリスを作っていきます!
ただちょっと1回目にしては難しくなってしまったので、別の回で完全初心者用の講座も作るので、最初は無理に理解しようとしないで何となく手を動かしてみてください。

あまりクセのないシンプルな作り方にしたので、他の色々なパズルゲームなどにも応用できると思います。

そして、今回の内容は、プロジェクトのセットアップからゲームオブジェクトの作成、スクリプトの実装、テトリミノの作成、スコア、そしてゲームオーバー処理まで、必要な最低限ではありますが実装しました。

完成品はunityroomで公開していて、プレイできるのでよかったらやってみてください。

この内容をマスターすれば、誰でも自分だけのテトリスを作成できるようになります。さあ、ゲーム開発の第一歩を踏み出しましょう!

1. Unityプロジェクトのセットアップ

まず、Unity Hubを使用して新しい2Dプロジェクトを作成します。プロジェクト名を「Tetris」として、プロジェクトを作成します。

2. ゲームオブジェクトの作成

シーンに必要なゲームオブジェクトを作成します。

  1. Gridオブジェクト:グリッドを管理するための空のオブジェクトを作成し、名前を「Grid」に変更します。
  2. Spawnerオブジェクト:テトリミノを生成するための空のオブジェクトを作成し、名前を「Spawner」に変更します。

3. スクリプトの実装

Grid.csの作成

このスクリプトはグリッドとピースの管理を担当します。

using UnityEngine;

public class Grid : MonoBehaviour
{
  // シングルトンインスタンス
    public static Grid Instance { get; private set; }
  // グリッドの幅と高さ
    public static int width = 10;
    public static int height = 20;
  // グリッドを格納する2次元配列
    public static Transform[,] grid;
    private const float lineOffset = -0.5f;

    private void Awake()
    {
     // シングルトンのインスタンスが既に存在する場合はこのオブジェクトを破棄
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            // インスタンスを設定し、DontDestroyOnLoadでシーン間で破棄されないようにする
            Instance = this;
            grid = new Transform[width, height];
            DontDestroyOnLoad(gameObject);
        }
    }
    // ベクトルを整数に丸める
    public Vector2 RoundVector2(Vector2 v)
    {
        return new Vector2(Mathf.Round(v.x), Mathf.Round(v.y));
    }
    // 指定された位置がグリッドの範囲内にあるかをチェック
    public bool InsideBorder(Vector2 pos)
    {
        return ((int)pos.x >= 0 &&
                (int)pos.x < width &&
                (int)pos.y >= 0);
    }
    // 指定した行を削除
    public void DeleteRow(int y)
    {
        for (int x = 0; x < width; ++x)
        {
            Destroy(grid[x, y].gameObject);
            grid[x, y] = null;
        }
    }

    public void DecreaseRow(int y)
    {
        for (int x = 0; x < width; ++x)
        {
            if (grid[x, y] != null)
            {
                // ブロックを一段下げる
                grid[x, y - 1] = grid[x, y];
                grid[x, y] = null;
                grid[x, y - 1].position += new Vector3(0, -1, 0);
            }
        }
    }
    // 指定した行より上の行をすべて一段下げる
    public void DecreaseRowsAbove(int y)
    {
        for (int i = y; i < height; ++i)
            DecreaseRow(i);
    }
    // 行が完全に埋まっているかをチェック
    public bool IsRowFull(int y)
    {
        for (int x = 0; x < width; ++x)
            if (grid[x, y] == null)
                return false;
        return true;
    }
    // 完全に埋まった行を削除し、上の行を一段下げる
    public void DeleteFullRows()
    {
        for (int y = 0; y < height; ++y)
        {
            if (IsRowFull(y))
            {
                DeleteRow(y);
                DecreaseRowsAbove(y + 1);
                --y;
            }
        }
    }
    // グリッドの情報を更新
    public void UpdateGrid(Transform t)
    {
        for (int y = 0; y < height; ++y)
            for (int x = 0; x < width; ++x)
                if (grid[x, y] != null)
                    if (grid[x, y].parent == t)
                        grid[x, y] = null;

        foreach (Transform child in t)
        {
            Vector2 v = RoundVector2(child.position);
            grid[(int)v.x, (int)v.y] = child;
        }
    }
    // 境界線を描画するメソッドを追加
    void OnDrawGizmos()
    {
        Gizmos.color = Color.red;

        // 左境界線
        Gizmos.DrawLine(new Vector3(lineOffset, lineOffset, 0), new Vector3(lineOffset, height + lineOffset, 0));
        // 右境界線
        Gizmos.DrawLine(new Vector3(width + lineOffset, lineOffset, 0), new Vector3(width + lineOffset, height + lineOffset, 0));
        // 下境界線
        Gizmos.DrawLine(new Vector3(lineOffset, lineOffset, 0), new Vector3(width + lineOffset, lineOffset, 0));
    }
}

Tetromino.csの作成

このスクリプトはテトリミノの動作を管理します。

using UnityEngine;

public class Tetromino : MonoBehaviour
{
    // 落下に関する時間の変数
    float fall = 0;
    public float fallSpeed = 1; // 落下速度

    void Update()
    {
        CheckUserInput();
    }
    // ユーザー入力をチェックしてブロックを移動または回転させる
    void CheckUserInput()
    {
        // 左矢印キーが押された場合
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            // ブロックを左に移動
            transform.position += new Vector3(-1, 0, 0);
            // 位置が有効かチェック
            if (!IsValidGridPos())
                transform.position += new Vector3(1, 0, 0); // 位置が無効なら元に戻す
            else
                Grid.Instance.UpdateGrid(transform); // 位置が有効ならグリッドを更新
        }
        // 右矢印キーが押された場合
        else if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            // ブロックを右に移動
            transform.position += new Vector3(1, 0, 0);
            // 位置が有効かチェック
            if (!IsValidGridPos())
                transform.position += new Vector3(-1, 0, 0); // 位置が無効なら元に戻す
            else
                Grid.Instance.UpdateGrid(transform); // 位置が有効ならグリッドを更新
        }
        // 上矢印キーが押された場合
        else if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            // ブロックを回転
            transform.Rotate(0, 0, -90);
            // 位置が有効かチェック
            if (!IsValidGridPos())
                transform.Rotate(0, 0, 90); // 位置が無効なら元に戻す
            else
                Grid.Instance.UpdateGrid(transform); // 位置が有効ならグリッドを更新
        }
        else if (Input.GetKeyDown(KeyCode.DownArrow) || Time.time - fall >= fallSpeed)
        {   
            // ブロックを一段下に移動
            transform.position += new Vector3(0, -1, 0);
            // 位置が有効かチェック
            if (!IsValidGridPos())
            {
                // 位置が無効なら元に戻す
                transform.position += new Vector3(0, 1, 0);
                // グリッドを更新
                Grid.Instance.UpdateGrid(transform);
                // 完全に埋まった行を削除
                Grid.Instance.DeleteFullRows();
                // 新しいテトリミノを生成
                FindObjectOfType<Spawner>().SpawnNext();
                enabled = false;
            }
            else
            {
                Grid.Instance.UpdateGrid(transform);
            }
            // 落下時間をリセット
            fall = Time.time;
        }
    }
    // グリッド内での位置が有効かどうかを判定する
    bool IsValidGridPos()
    {
        foreach (Transform child in transform)
        {
            Vector2 v = Grid.Instance.RoundVector2(child.position);

            if (!Grid.Instance.InsideBorder(v))
                return false;

            if (Grid.grid[(int)v.x, (int)v.y] != null &&
                Grid.grid[(int)v.x, (int)v.y].parent != transform)
                return false;
        }
        return true;
    }

    void UpdateGrid()
    {
        for (int y = 0; y < Grid.height; ++y)
            for (int x = 0; x < Grid.width; ++x)
                if (Grid.grid[x, y] != null)
                    if (Grid.grid[x, y].parent == transform)
                        Grid.grid[x, y] = null;

        foreach (Transform child in transform)
        {
            Vector2 v = Grid.Instance.RoundVector2(child.position);
            Grid.grid[(int)v.x, (int)v.y] = child;
        }
    }
}

Spawner.cs

このスクリプトは新しいテトリミノを生成します。このSpanwerをアタッチしたオブジェクトの座標からテトリミノが出現します。

using UnityEngine;

public class Spawner : MonoBehaviour
{
    public GameObject[] tetrominoes;

    void Start()
    {
        SpawnNext();
    }

    public void SpawnNext()
    {
        int i = Random.Range(0, tetrominoes.Length);
        Instantiate(tetrominoes[i], transform.position, Quaternion.identity);
    }
}

4. ゲームオブジェクトの設定

  1. 空のオブジェクトを作って、Gridと名前を付けて、Grid.csを追加します。
  2. 同じく空のオブジェクトを作ってねSpawnerと名前を付けて、Spawner.cs スクリプトを追加します。

5. テトリミノのPrefabを作成

1. スプライトの準備

テトリミノの各ブロックに使用するスプライトを用意します。ここでは、シンプルな正方形のスプライトを使用します。

スプライトの作成

  • Assetsフォルダ内にSpritesフォルダを作成します。
  • Spritesフォルダ内で右クリックし、Create > 2D > Sprite > Squareを選択します。このスプライトをBlockと名付けます。

2. テトリミノの作成

各テトリミノを構成するブロックを配置し、Prefabとして保存します。

テトリミノIの作成

1.新しいゲームオブジェクトを作成

Hierarchyウィンドウで右クリックし、Create Emptyを選択します。これをTetrominoと名付けます。

2.子オブジェクトとしてブロックを追加
  1. Teromino1オブジェクトを選択し、Create Emptyを4回選択して4つの子オブジェクトを追加します。
  2. 各子オブジェクトにSprite Rendererコンポーネントを追加し、スプライトにBlockを設定します。
  3. 各ブロックの位置を次のように設定します。(下の画像を参照)
    • ブロック1:(0, 0, 0)
    • ブロック2:(1, 0, 0)
    • ブロック3:(2, 0, 0)
    • ブロック4:(3, 0, 0)
3.Prefabとして保存
TetrominoIオブジェクトをPrefabsフォルダにドラッグ&ドロップして、Prefabとして保存します。そして、この作ったPrefabにTetromino.csスクリプトを追加します。

他のテトリミノの作成

I以外のテトリミノも同様の手順で作成します。ブロックの配置のみが異なります。もし前のプレハブの数値を変えた場合に、「元となるプレハブ」と表示されたら、それを選んで新しいプレハブを作ってください。

テトリミノO
  1. ブロック1:(0, 0, 0)
  2. ブロック2:(1, 0, 0)
  3. ブロック3:(0, 1, 0)
  4. ブロック4:(1, 1, 0)
テトリミノT
  1. ブロック1:(0, 0, 0)
  2. ブロック2:(-1, 0, 0)
  3. ブロック3:(1, 0, 0)
  4. ブロック4:(0, 1, 0)
テトリミノS
  1. ブロック1:(0, 0, 0)
  2. ブロック2:(1, 0, 0)
  3. ブロック3:(0, 1, 0)
  4. ブロック4:(-1, 1, 0)
テトリミノZ
  1. ブロック1:(0, 0, 0)
  2. ブロック2:(-1, 0, 0)
  3. ブロック3:(0, 1, 0)
  4. ブロック4:(1, 1, 0)
テトリミノJ
  1. ブロック1:(0, 0, 0)
  2. ブロック2:(-1, 0, 0)
  3. ブロック3:(1, 0, 0)
  4. ブロック4:(-1, 1, 0)
テトリミノL
  1. ブロック1:(0, 0, 0)
  2. ブロック2:(-1, 0, 0)
  3. ブロック3:(1, 0, 0)
  4. ブロック4:(1, 1, 0)

すべてのテトリミノPrefabを作ると以上のようになります。すべてのPrefabにTetromino.csスクリプトを追加するのを忘れないようにしましょう。

3. Spawnerの設定

  1. Spawnerオブジェクト(空のオブジェクト)を作成し、Spawner.csスクリプトを追加します。
  2. SpanwerオブジェクトのInspectorで、tetrominoes配列にすべてのテトリミノPrefabを設定します。

4. カメラの調整

カメラをテトリミノがすべて見えるように調整します。以下の通りにすればぴったり範囲に収まるようになります。ついでにアスペクト比もそれぞれの提出する環境に合わせておきましょう。unityroomなら950✕540など。

Unityでテトリスを作成する際にカメラを設定する時の注意点を以下にまとめました。これらのポイントを押さえておくことで、ゲームプレイが快適で、ビジュアル的にバランスの取れたものになります。

カメラ設定の注意点

  1. オーソグラフィックカメラの使用
    • テトリスは2Dゲームなので、カメラのProjectionをOrthographicに設定します。これにより、画面内のオブジェクトが等距離で表示され、パースペクティブの影響を受けません。
  2. カメラの位置とサイズ
    • カメラの位置をテトリスのグリッド全体が表示されるように調整します。通常、カメラの位置はグリッドの中心に配置し、Orthographic Sizeをグリッドの高さに合わせて設定します。
    • 例えば、グリッドの高さが20の場合、Orthographic Sizeを10に設定します。これにより、上下に20ユニット分の高さが表示されます。
  3. アスペクト比の設定
    • カメラの表示範囲が異なる画面サイズやアスペクト比で適切に表示されるようにします。GameビューのAspect RatioFree Aspectに設定し、異なるアスペクト比でもゲームが正しく表示されるか確認します。
  4. 背景の設定
    • カメラのBackgroundカラーを設定して、ゲームの雰囲気に合った背景色を選びます。または、背景画像を使用する場合、スプライトを追加して背景として設定します。
  5. カメラのクリッピング設定
    • Clipping PlanesNearFarを適切に設定します。テトリスでは通常、2Dゲームであるため、Nearを0.1、Farを100などに設定しておくと良いでしょう。
  6. カメラのレイヤー設定
    • 必要に応じてカメラのCulling Maskを設定し、表示するレイヤーを制限します。例えば、背景だけを別のレイヤーに配置し、カメラのCulling Maskで背景レイヤーを含めるようにします。

さて、ここまでの作業が終わるって実行してみると最低限テトリスの機能が動作します。

Spanerオブジェクトの位置をあわせる

Spanerオブジェクトの座標からテトリミノが出現するのでちょうどいい位置に合わせましょう。以下の赤いマークもセットされてるかチェックしてください。

実行結果

しかし、今のままだと外側の壁もなくわかりずらいので、追加していきましょう。

外側の壁の追加

ヒエラルキーで空のオブジェクトを作って、SpriteRenderコンポーネントを追加してSquareと名前を付けましょう。とりあえず、今のところは、スプライトにはテトリミノと同じものを使っておいてよいでしょう。

最後にPrefabファイルにドラッグ&ドロップしてPrefab化しておきましょう。この時にヒエラルキーのは消しておいてください。

次に、Grid.csスクリプトに以下の追記して外側の壁を生成するようにします。

public GameObject cubeBlock;

    private void Start()
    {
        // 横一列
        for (int i = 0; i < width; ++i)
        {
            Instantiate(cubeBlock, new Vector3(i, -1.0f, 0), Quaternion.identity);
        }
        // 縦一列(左)
        for (int i = 0; i < height; ++i)
        {
            Instantiate(cubeBlock, new Vector3(-1.0f, i-1.0f, 0), Quaternion.identity);
        }
        // 縦一列(右)
        for (int i = 0; i < height; ++i)
        {
            Instantiate(cubeBlock, new Vector3(width, i - 1.0f, 0), Quaternion.identity);
        }
    }

このcubeBlockに先ほど作ったSquareを忘れずにアタッチしておきましょう。実行してみると壁が出現しました。これで最低限テトリスとしての機能をもたせることはできました!

次は壁もテトリミノも真っ白なままなので色をつけていきましょう。

2Dでライティングとマテリアルの設定をする

これからブロックに色をつけていくのですが、まずはURPの設定をします。

URPの設定が終わったらマテリアルを作成して右上のシェーダーにUniversal Render Pipeline/2D/Sprite-Lit-Defaultを指定してやると正常な色に描画されます。ただし、色を変える時は、Standartでないとできないみたいなので、色を設定した後にするといいでしょう。

作成したマテリアルを外壁用ブロックとテトリミノに適用する

Prefabフォルダを開いてすべてのテトリミノのブロックに好きな色のマテリアルを設定しましょう。

実行結果

ちゃんとブロックに色がつきました。これで少しは見た目がよくなりましたね!

音声の追加

ゲームの基本部分はできてきましたが、音がなければゲームっぽくないですよね。ということで、次はBFMと効果音を追加していきます。

ステップ 1: 音声ファイルのインポート

  1. 音声ファイルの準備:BGMとSE用の音声ファイル(例えば .mp3 や .wav フォーマット)を用意します。
  2. Unityプロジェクトにインポート
    • Assetsフォルダ内に音声ファイルをドラッグ&ドロップするか、AssetsメニューからImport New Asset…を選択して音声ファイルをインポートします。

ステップ 2: BGMを再生するための設定

1.オーディオソースの追加

  1. シーン内で音を再生するために、Hierarchy ウィンドウでCreate Emptyを選択して新しい空のゲームオブジェクトを作成し、「AudioManager」などと名付けます。
  2. InspectorウィンドウのAdd ComponentボタンをクリックしてAudio Sourceコンポーネントを追加します。

2.BGMの設定

  1. Audio SourceコンポーネントのAudioClipフィールドに、ステップ 1 でインポートしたBGMファイルをドラッグ&ドロップします。
  2. LoopオプションをチェックしてBGMがループ再生されるように設定します。
  3. ゲーム開始時に自動的に再生するために、ゲーム開始時に再生(Play On Awake)オプションもチェックします。

ステップ 3: ブロック回転時のSEを設定

効果音用のオーディオソースを設定

  1. ブロックを管理しているゲームオブジェクト(例えば「Tetrimino」)を選択します。
  2. 既にAudio Sourceが存在しない場合は、新たに追加します。このAudio Source にはBGMとは別の効果音を設定します。
  3. AudioClipフィールドに、ブロック回転時のSEファイルを設定します。
  4. ゲーム開始時に再生(Play On Awake)オプションはオフにして、特定のアクションでのみ音が再生されるようにします。

ステップ 4: スクリプトで効果音の再生を制御

ブロック回転のスクリプトを修正

  1. ブロックの管理スクリプト(例えば Grid.cs)を開きます。
  2. 回転関数内でオーディオソースを再生するコードを追加します。
void RotateTetromino()
{
    transform.Rotate(0, 0, 90);
    GetComponent<AudioSource>().Play();  // 回転音を再生
}

これで、UnityプロジェクトにBGMと効果音を追加し、適切なタイミングで音が再生されるように設定できました。ゲームを実行して音の再生が期待通りに機能しているか確認し、必要に応じて音量やその他の設定を調整してください。

ステップ5:ブロックが消えた時の処理の追加

まずGridオブジェクトにAudio Sourceを追加します。

public AudioSource audioSource;
    private void Start()
    {
        audioSource = GetComponent<AudioSource>();
        // 横一列
        for (int i = 0; i < width; ++i)
        {
            Instantiate(cubeBlock, new Vector3(i, -1.0f, 0), Quaternion.identity);
        }
        // 縦一列(左)
        for (int i = 0; i < height; ++i)
        {
            Instantiate(cubeBlock, new Vector3(-1.0f, i-1.0f, 0), Quaternion.identity);
        }
        // 縦一列(右)
        for (int i = 0; i < height; ++i)
        {
            Instantiate(cubeBlock, new Vector3(width, i - 1.0f, 0), Quaternion.identity);
        }
    }
    public void DeleteFullRows()
    {
        int numberOfRowsCleared = 0;
        for (int y = 0; y < height; ++y)
        {
            if (IsRowFull(y))
            {
                audioSource.Play(); // ブロックを消す時の効果音を再生する
                DeleteRow(y);
                DecreaseRowsAbove(y + 1);
                --y;
                numberOfRowsCleared++;
            }
        }
    }

以上でBGMと回転SEとブロック消去時のSEが追加されました。最後にゲームオーバーになった時の判定を追加しましょう。

ゲームオーバーとリスタートの処理

Unityでテトリスのようなゲームを作成する際に、ブロックが積み上がって画面(または定義されたプレイエリア)からはみ出る状況を判定する方法について説明します。この判定はゲームオーバーの条件を設定するのに使います。

ステップ 1: グリッドの設定

まず、ゲームのグリッド(プレイエリア)のサイズを定義します。これは通常、Gridクラス内で行われます。 widthとheightを使用してグリッドのサイズを設定します。

ステップ 2: ブロックの位置判定

ブロック(テトリミノ)の各パーツがグリッドの上限を超えたかどうかを判定するためのロジックを実装します。この判定は、テトリミノがグリッドに固定されるたびに行う必要があります。

以下に、ブロックが上限を超えたかどうかを判定する基本的な方法を示します:

public class Grid : MonoBehaviour
{
    public static int width = 10;
    public static int height = 20; // グリッドの高さ
    public static Transform[,] grid = new Transform[width, height];

    public static bool IsGameOver()
    {
        for (int x = 0; x < width; x++)
        {
            // グリッドの最上行にブロックが存在するかチェック
            if (grid[x, height - 1] != null)
            {
                return true; // ゲームオーバー条件を満たす
            }
        }
        return false;
    }
}

ステップ 3: ゲームオーバーの処理

ブロックが固定されるたびに、上記のIsGameOverメソッドを呼び出して、ゲームオーバー条件をチェックします。条件を満たした場合は、ゲームオーバーの処理を実行します。

public class Tetromino : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            // ブロックを下に動かす処理
            transform.position += new Vector3(0, -1, 0);

            if (!IsValidGridPos())
            {
                transform.position -= new Vector3(0, -1, 0); // 元に戻す
                Grid.UpdateGrid(this);
                
                if (Grid.IsGameOver()) // ゲームオーバー条件のチェック
                {
                    Debug.Log("Game Over!");
                    // ゲームオーバー処理、例えばシーンをリロードするなど
                }
            }
        }
    }
}

ステップ 4: ゲームオーバーUIの表示

実際のゲームでは、Debug.Logの代わりにゲームオーバー画面やメニューを表示する処理が必要になります。GameOverManagerクラス(前の説明で作成したもの)を使用して、ユーザーにゲームオーバーを知らせ、次のアクション(リスタートまたは終了)を選択させます。

このようにして、テトリスゲームでブロックが積み上がってゲームオーバーとなる状況を判定し、適切にユーザーにフィードバックを提供することができます。

まずは、ヒエラルキー右クリック→3Dオブジェクト→TextMeshProを選んでGameOverText(TMP)とします。位置をちょうどいい場所に置いたら、左上のチェックを外して、テキストを非アクティブにして見えなくします。

ゲームオーバーから復帰するリスタートボタンはヒエラルキーで右クリック→UI→ボタン TextMeshProをクリックします。同じように左上のチェツクを外して、非アクティブにしておきましょう。

次にゲームオーバーになったらゲームオーバーのテキストとボタンを表示させるようにスクリプトに加えます。ResetGridはリスタートボタンを押した時に初期化して再スタートするために必要な処理です。

public class Grid : MonoBehaviour
{
    public bool isGameOver = false;
    public GameObject gameOverText;
    public GameObject restartButton;
    public void SetGameOver(bool active)
    {
        if (active)
        {
            isGameOver = true;
            gameOverText.SetActive(true);
            restartButton.SetActive(true);
        }
        else
        {
            ResetGrid();
            FindObjectOfType<Spawner>().SpawnNext();
            isGameOver = false;
            gameOverText.SetActive(false);
            restartButton.SetActive(false);
        }
    }
    public void ResetGrid()
    {
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                if (grid[x, y] != null)
                {
                    Destroy(grid[x, y].gameObject);
                    Destroy(grid[x, y].parent.gameObject);  // GameObjectを破棄
                    grid[x, y] = null;  // グリッドの参照をクリア
                }
            }
        }
    }
}

SetGameOver(true)で呼び出すとゲームオーバー時にゲームオーバー用のテキストとボタンを表示させます。

リスタートする時にはSetGameOver(false)で呼び出すとゲームオーバー用のテキストとボタンを非表示にしてグリッドやゲームオブジェクトを初期化します。

それでは、ゲームオーバー時にこの関数を呼び出しましょう。

public class Tetromino : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            transform.position += new Vector3(0, -1, 0);
            if (!IsValidGridPos()) // ブロックが床に接触していたら
            {
                transform.position += new Vector3(0, 1, 0);
                Grid.Instance.UpdateGrid(transform);
                enabled = false;
                if (Grid.Instance.IsGameOver()) // ゲームオーバー条件のチェック
                {
                    Debug.Log("Game Over!");
                    Grid.Instance.SetGameOver(true);
                }
                else // ゲームオーバーでなければ次のブロックを出現させる
                {
                    Grid.Instance.DeleteFullRows();
                    FindObjectOfType<Spawner>().SpawnNext();
                }
            }
            else
            {
                Grid.Instance.UpdateGrid(transform);
            }
        }
    }
}

ここまでの流れでゲームオーバーのテキストとボタンの表示までできました!

ステップ : 5 リスタートボタンを押すと再開する

ここまでで、すでにスクリプトは準備できているので、リスタートボタンを押したらSetGameOver(false)で呼び出すように設定しましょう。

これでリスタートボタンを押した時に再スタートする処理が実行されるようになりました。最後にスコアの機能を追加してゲームとして完成させましょう!

スコア管理の追加

スコアマネージャの作成

スコアマネージャー用のオブジェクトの作成

ヒエラルキーで右クリック→空のオブジェクトの作成でScoreManagerと名付ける。

スコア用のテキストの作成

Assetsフォルダ内で右クリックし、ヒエラルキー右クリック→3Dオブジェクト→TextMeshProを選んでScoreManagerText(TMP)を作成する。

新しいスクリプトの作成:

Assetsフォルダ内で右クリックし、Create > C# Scriptを選択。名前をScoreManagerに変更します。以下のスクリプトをヒエラルキーのScoreManagerオブジェクトにドラッグ&ドロップしてアタッチします。

スクリプトの編集:

using UnityEngine;
using UnityEngine.UI;

public class ScoreManager : MonoBehaviour
{
    public static ScoreManager Instance { get; private set; }
    private int score = 0;
    public Text scoreText;  // UnityのInspectorから設定

    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
    }

    public void AddScore(int linesCleared)
    {
        int scoreToAdd = 0;
        switch (linesCleared)
        {
            case 1:
                scoreToAdd = 100;
                break;
            case 2:
                scoreToAdd = 300;
                break;
            case 3:
                scoreToAdd = 500;
                break;
            case 4:
                scoreToAdd = 800;
                break;
            default:
                break;
        }

        score += scoreToAdd;
        scoreText.text = "Score: " + score;
    }
}

スコアの加算をトリガーする

Grid.csのDeleteFullRowsメソッド内で、ScoreManagerのAddScoreメソッドを呼び出します。

public void DeleteFullRows()
{
    int numberOfRowsCleared = 0;
    for (int y = 0; y < height; ++y)
    {
        if (IsRowFull(y))
        {
       audioSource.Play();
            DeleteRow(y);
            DecreaseRowsAbove(y + 1);
            y--;
            numberOfRowsCleared++;
        }
    }

    if (numberOfRowsCleared > 0)
    {
        ScoreManager.Instance.AddScore(numberOfRowsCleared);
    }
}

numberOfRowsClearedには消したブロックの行数が入っていて、1行消した時には100点、2行消した時には200点などスコアが加算されることになります。

テトリス完成

今回のテトリス講座では、ブロックの移動からスコア加算にゲームオーバーからリスタートまでの一通りの方法を解説しました。このプログラムを元にして、あなただけのオリジナルのテトリスを作っていただけたら、とても嬉しいです(^^)