【Unity】ブロック崩し(3) スコア、タイトル、ゲームオーバー etc.【クソゲー制作】

ブロック崩しを作る(3)

ブロック崩しゲームの制作、第3回です。今回はスコアの表示、タイトル画面、ゲームオーバー画面、ゲームクリア画面などなどを作っていきます。

環境
  • Mac mini (M1, 2020)
  • Unity 2022.3.36f1
目次

スコアを表示する

ゲームにスコアはつきものです。世のゲーマーたちはみなハイスコアを目指して日々無駄な時間を費やしているのです。

STEP

スコアを表示するためのオブジェクトを作ります。

ヒエラルキータブで右クリック「UI > Text – TextMeshPro」を選択します。オブジェクトの名前を「ScoreText」にします。

ブロック崩し47
STEP

UIオブジェクトを作成するとCanvasが作成され、その下にUIオブジェクトが配置されます。で、Sceneタブで見たときにカメラの表示領域とキャンバスの表示領域が一致していないので、なんかモヤモヤします。

ブロック崩し48

このままでもゲーム画面では正常に表示されるのですが、開発中にUIの位置を決めづらいので、カメラの表示領域をキャンバスに合わせます。

Canvasのインスペクターで「Canvas > Render Mode」「Screen Space – Camera」にします。

ブロック崩し49

「Render Camera」にヒエラルキーの「Main Camera」をドラッグ&ドロップします。

ブロック崩し50

これで、カメラ領域とキャンバス領域が重なりました。いえい。

ブロック崩し51
STEP

ScoreTextオブジェクトはTextMesh Proなので、TMPの設定をします。メニューバーの「Window > TextMeshPro > Font Asset Creator」を選択して、フォントアセットを作成しました。

今回は欧文フォントを使うのですが、日本語フォントのときと同じように設定したらうまくいきませんでした。文字に白い膜がかかったようになってしまいます。

ブロック崩し52

いろいろ試した結果、以下の設定でうまくいきました。

ブロック崩し53

ポイントは「Atlas Resolution」の値です。日本語フォントの場合は4096*4096だったのですが、欧文フォントではもっと小さくしたほうがいいっぽいです。扱う文字数に応じて適した値があるのかな。UnityのドキュメントではCharacter SetがASCIIならば512*512が適切とあります。

STEP

空のオブジェクトを作成します。ヒエラルキータブで右クリック「Create Empty」を選択します。

ブロック崩し54

名前を「GameManager」にします。

STEP

C#スクリプトGameManager.csを作成して、オブジェクトGameManagerにアタッチします。GameManager.csの内容は次のとおりです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class GameManager : MonoBehaviour
{
    public int score = 0;
    public int combo = 0;
    public TextMeshProUGUI scoreText; // スコアを表示するテキスト

    // スコア加算
    public void AddScore()
    {
        score += 10;
        combo++;

        // コンボボーナス
        if (combo >= 2)
            score += combo - 1;

        string formattedScore = score.ToString("D8"); // スコアを8桁の文字列に変換
        scoreText.text = formattedScore;
    }

    // コンボのリセット
    public void ResetCombo()
    {
        combo = 0;
    }
}

Unityで、GameManagerオブジェクトのScore TextにScoreTextオブジェクトを割り当てます。

STEP

スクリプトBlockController.csを修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlockController : MonoBehaviour
{
    public GameManager gameManager; // GameManager スクリプトへの参照

    void Start()
    {
    }

    void Update()
    {
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Ball") // 衝突したオブジェクトのタグが Ball なら
        {
            Destroy(gameObject); // 自身を削除
            gameManager.AddScore(); // スコア加算
        }
    }
}

Unityで、BlockオブジェクトのGame ManagerにGameManagerオブジェクトを割り当てます。

STEP

スクリプトPlayerController.csを修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    ...省略...
    public GameManager gameManager; // GameManagerへの参照

    void Update()
    {
        ...省略...
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Ball") // ボールと衝突したら
        {
            gameManager.ResetCombo(); // コンボをリセット
        }
    }
}

Unityで、PlayerオブジェクトのGame ManagerにGameManagerオブジェクトを割り当てます。

以上で、ブロックを1つ消したら+10点、コンボボーナスは+1点、ボールがプレイヤーに接触したらコンボはリセットの仕組みが完成しました。

ブロックを動かす

ブロックが少しずつ下に下がっていくという機能を実装します。

最初、複数のステージを作ってクリアしていく形にしようと思っていたのですが、ステージの切り替えが上手くいかなくて挫折しました。

BlockController.csを修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlockController : MonoBehaviour
{
    ...省略...
    private float downSpeed = 0.03f; // ブロックが下がる速度

    void Update()
    {
        transform.Translate(Vector2.down * downSpeed * Time.deltaTime); // ブロックをゆっくり下げる
    }

    ...省略...
}

transform.Translate()でオブジェクトの位置を動かします。

Vactor2.downは下方向を表すベクトル、downSpeedはブロックが1秒間にどれだけ下がるか、これらを掛け算して移動する方向と速度を計算しています。

Time.deltaTimeは1フレーム間の経過時間を表す変数で、これを掛けることによって常に一定とは限らないフレームレートの変動に対応します。PCの性能やゲームの負荷によってフレームレートが変わってしまう場合でも、一定の速度でブロックを動かすということね(わかったつもり)。

タイトル画面を作る

タイトル画面を作ります。タイトル用のシーンを作って、シーンの切り替えでゲーム画面に遷移するようにします。

STEP

Assets/Scenesフォルダの下で右クリック「Create > Scene」を選択します。新しいシーンが作成されるので名前を「TitleScene」に変更します。

ブロック崩し55
STEP

TitleSceneをダブルクリックします。

シーンをダブルクリックすることによって、Unity上で編集するシーンを切り替えることができます。

TitleSceneに切り替わったら、必要なオブジェクトを配置します。とりあえず、タイトルとスタートボタンを作りました。

ブロック崩し56
STEP

ヒエラルキータブで右クリック「Create Empty」を選択して、空のオブジェクトを作成します。名前を「TitleManager」にします。

Assets/Scripts下にC#スクリプト TitleManager.cs を作成して TitleManager オブジェクトにアタッチします。 TitleManager.cs の内容は次のとおりです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class TitleManager : MonoBehaviour
{
    public void StartGame()
    {
        SceneManager.LoadScene("MainScene"); // MainSceneを表示
    }
}

using UnityEngine.SceneManagement;で名前空間の追加。

SceneManager.LoadScene()で指定したシーンをロードします。

STEP

スタートボタンを押したときのアクションを設定します。

スタートボタンのインスペクターの「Button > On Click ()」「+」をクリックして新しいイベントを作成します。「None(Object)」を「TitleManager」に「No Function」を「TitleManager.StartGame」に設定します。

ブロック崩し57

これでスタートボタンをクリックしたときに TitleManager.cs の StartGame() 関数が実行されるようになりました。

STEP

1つのゲームに複数のシーンがある場合、ゲームを起動したときに最初に表示されるのはどのシーンか問題があります。この問題を解決しましょう。

メニューバーの「File > Build Settings…」を選択して Build Settings ウインドウを開きます。「Add Open Scenes」ボタンをクリックするとシーンの一覧画面にすべてのシーンが表示されます。

ブロック崩し58

TitleSceneをドラッグ&ドロップしてリストのいちばん上に持っていきます。このリストのいちばん上にあるのがゲームを起動したときに表示されるシーンです。

ゲームオーバー画面を作る

STEP

ゲームオーバー画面のオブジェクトを作ります。

MainSceneに戻って、ヒエラルキーのCanvas上で右クリック「UI > Panel」を選択します。名前を「GameOver」にします。

各種オブジェクトを配置します。「GameOverText」「RetryButton」「BackToTitleButton」の3つのオブジェクトを作成しました。

ブロック崩し59
STEP

ボールが下の壁(WallBottom)に衝突したらゲームオーバーになるようにします。

BallController.csを修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallController : MonoBehaviour
{
    ...省略...
    public GameManager gameManager; // GameManagerへの参照

    ...省略...

    void OnCollisionEnter2D(Collision2D collision)
    {
        ...省略...

        if (collision.gameObject.name == "WallBottom") // ボールがWallBottomに当たった場合
        {
            gameManager.GameOver(); // ゲームオーバー
        }
    }
}
STEP

GameManager.csを修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;

public class GameManager : MonoBehaviour
{
    public GameObject ball; // Ballオブジェクト
    public GameObject player; // Playerオブジェクト
    public GameObject blocks; // Blocksオブジェクト
    public GameObject gameOver; // GameOverオブジェクト
    ...省略...

    // ゲームオーバー
    public void GameOver()
    {
        Time.timeScale = 0; // ゲームを一時停止
        gameOver.SetActive(true); // ゲームオーバー画面を表示
        ball.SetActive(false); // ボールを非表示
        player.SetActive(false); // プレイヤーを非表示
        blocks.SetActive(false); // ブロックを非表示
    }

    // リトライ
    public void RetryGame()
    {
        SceneManager.LoadScene("MainScene");
        Time.timeScale = 1; // ゲーム時間を動かす
    }

    // タイトルに戻る
    public void BackToTitle()
    {
        SceneManager.LoadScene("TitleScene");
    }

    ...省略...
}

GameOver()関数では、Time.timeScale = 0でゲーム内の時間を止めます。ゲームオーバー画面を表示し、それ以外のオブジェクトを非表示にします。

RetryGame()関数では、ゲーム画面を再読込して、一時停止していたゲーム時間を再開します。

BackToTitle()関数は、タイトル画面をロードします。このとき、タイトル画面からゲームをスタートするとゲーム時間が止まったままになってしまうので、TitleManager.csを修正してゲーム時間を動かしてあげます。

public class TitleManager : MonoBehaviour
{
    public void StartGame()
    {
        SceneManager.LoadScene("MainScene"); // MainSceneを表示
        Time.timeScale = 1; // ゲーム時間を動かす
    }
}
STEP

ブロックが下の壁に衝突したときもゲームオーバーにしたいですね。

その場合、BlockオブジェクトにRigidbody 2Dコンポーネントを追加する必要があります。でもそうすると、ゲームバランスが崩れてしまうんですね。実際にやってみたらブロックが柔らかくなってしまって、ボールが当たったときに跳ね返らなくなってしまいました。

つーわけで、当たり判定を使わずに、ブロックがゲーム画面の外に出たらゲームオーバーとなるようにBlockController.csスクリプトを修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlockController : MonoBehaviour
{
    ...省略...
    private Camera mainCamera; // メインカメラ

    void Start()
    {
        mainCamera = Camera.main; // Main Cameraを取得
    }

    void Update()
    {
        ...省略...
        float cameraBottom = mainCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).y; // 画面の下端のy座標を取得
        if (transform.position.y < cameraBottom) // ブロックが画面の外に出たら
        {
            gameManager.GameOver(); // ゲームオーバー
        }
    }

    ...省略...
}
STEP

リトライボタンとタイトルへ戻るボタンをクリックしたときのアクションを設定します。

RetryButtonオブジェクトのインスペクターの「Button > On Click ()」で新しいイベントを作成して、「None(Object)」を「GameManager」に「No Function」を「GameManager.RetryGame」に設定します。

ブロック崩し60

同じようにして、BackToTitleButton「GameManager.BackToTitle」に設定します。

ブロック崩し61
STEP

GameOverオブジェクトはゲーム開始時には表示させないので、インスペークターのチェックを外して非表示にしておきます。

ブロック崩し62

ゲームクリア画面を作る

STEP

ゲームクリア画面のオブジェクトを作ります。

ヒエラルキーのCanvas上で右クリック「UI > Panel」を選択します。名前を「GameClear」にします。

「GameClear」の下に「GameClearText」「BackToTitleButton」の2つのオブジェクトを作成しました。

ブロック崩し63
STEP

GameManager.csを修正します。

public class GameManager : MonoBehaviour
{
    ...省略...
    public GameObject blocks; // Blocksオブジェクト
    public int blockCount; // ブロックの数

    void Start()
    {
        blockCount = blocks.transform.childCount; // ブロックの数を初期化
    }

    ...省略...

    // ゲームクリア
    public void GameClear()
    {
        Time.timeScale = 0; // ゲームを一時停止
        gameClear.SetActive(true); // ゲームクリア画面を表示
        ball.SetActive(false); // ボールを非表示
        player.SetActive(false); // プレイヤーを非表示
        blocks.SetActive(false); // ブロックを非表示
    }

    ...省略...
}

ゲーム開始時にブロックの数を数えています。Blocksオブジェクトの下にすべてのブロックが入っているので、transform.childCountで子要素を数えています。

GameClear()関数では、Time.timeScale = 0でゲーム内の時間を止め、ゲームクリア画面を表示し、それ以外のオブジェクトを非表示にします。

STEP

BlockController.csを修正します。

public class BlockController : MonoBehaviour
{
    public GameManager gameManager; // GameManager スクリプトへの参照
    ...省略...

    void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Ball") // 衝突したオブジェクトのタグが Ball なら
        {
            ...省略...
            gameManager.blockCount--; // 残りブロック数を1つ減らす

            if (gameManager.blockCount <= 0) // 全てのブロックが消えたら
            {
                gameManager.GameClear(); // ゲームクリア
            }
        }
    }
}

ブロックを消したら残りブロック数を1つ減らし、残りブロック数が0になったらゲームクリア画面を表示します。

STEP

BackToTitleButtonのアクションを設定します。

ゲームオーバ画面のときとまったく同じですね。「On Click ()」に「GameManager.BackToTitle」を割り当てます。

ブロック崩し61
STEP

GameClearオブジェクトはゲーム開始時には表示させたくないので、インスペークターのチェックを外して非表示にしておきます。

ブロック崩し64

さいごに

記事にはうまくできたことしか書いてないけど、実際には何度も行き詰まったり試行錯誤したりしてます。楽しいー。

でわでわ

ブロック崩しを作る(3)

この記事が気に入ったら
いいね または フォローしてね!

シェアしてね

コメント

コメントする

目次