Skip to content

第11回 残機とゲームオーバー ​

この回でつくるもの:自機が3機になり、やられても点滅しながら復活します。全部なくなると「GAME OVER」が出て、RETRY ボタンでやり直せます。

この回で学ぶこと ​

  • 残機(のこりの自機の数)を GameManager で管理する
  • コルーチンで自機を点滅させ、無敵時間を作る
  • SetActive でオブジェクトを表示したり消したりする
  • UI の Panel と Button を作り、On Click () で関数を呼ぶ
  • SceneManager.LoadScene でシーンを読みこみ直す

前回までのふりかえり ​

第10回で、画面の左上にスコアを表示しました。敵を倒すたびに GameManager の AddScore が呼ばれて点数がふえます。 でも今は、自機が1回ぶつかると消えてしまい、そこでおしまいです。今回は「残機」と「ゲームオーバー」を作ります。

1. 残機の文字を出す ​

1-1. LivesText を作る ​

スコアの文字をコピーして使います。

  1. Hierarchy で Canvas の子の ScoreText を選ぶ
  2. Ctrl+D(Macは Cmd+D)で複製する
  3. 複製したものの名前を LivesText に変える
  4. Inspector の Rect Transform で、Pos Y を -110 にする(スコアの下になる)
  5. Text Input を LIFE 3 に書きかえる
  6. Ctrl+S(Macは Cmd+S)でシーンを保存する

2. GameManager に残機とゲームオーバーを追加する ​

2-1. GameManager を書きかえる ​

GameManager を開いて、次のように書きかえます。

csharp
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;        // ★追加

// スコア・残機・ゲームの状態を管理する
public class GameManager : MonoBehaviour
{
    public static GameManager instance;   // どこからでも使えるようにする

    public int score = 0;
    public TextMeshProUGUI scoreText;

    public int lives = 3;                 // 残機(第11回) // ★追加
    public TextMeshProUGUI livesText;     // ★追加
    public GameObject gameOverPanel;      // ★追加
    public bool isPlaying = true;         // ゲーム中かどうか // ★追加

    void Awake()
    {
        instance = this;
    }

    void Start()
    {
        UpdateUI();
    }

    public void AddScore(int amount)
    {
        score += amount;
        UpdateUI();
    }

    public void LoseLife()                // ★追加
    {                                     // ★追加
        lives--;                          // ★追加
        UpdateUI();                       // ★追加
                                          // ★追加
        if (lives <= 0)                   // ★追加
        {                                 // ★追加
            GameOver();                   // ★追加
        }                                 // ★追加
    }                                     // ★追加

    void GameOver()                       // ★追加
    {                                     // ★追加
        isPlaying = false;                // ★追加
        gameOverPanel.SetActive(true);    // ★追加
    }                                     // ★追加

    void UpdateUI()
    {
        scoreText.text = "SCORE " + score.ToString("D6");
        livesText.text = "LIFE " + lives; // ★追加
    }

    // ボタンから呼ぶ
    public void Retry()                                               // ★追加
    {                                                                 // ★追加
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);   // ★追加
    }                                                                 // ★追加
}

保存します。

コードの解説 ​

行意味
using UnityEngine.SceneManagement;シーンを切りかえる SceneManager を使うための1行
public int lives = 3;残機。最初は3
public GameObject gameOverPanel;ゲームオーバーのときに出す画面(この後で作る)
public bool isPlaying = true;ゲーム中なら true、ゲームオーバーになったら false。ほかのスクリプトが「まだ遊んでいる?」を確かめるのに使う
lives--;lives = lives - 1; と同じ。1へらす
if (lives <= 0)残機が0以下になったらゲームオーバー
gameOverPanel.SetActive(true);かくしておいたゲームオーバー画面を表示する(次の tip)
"LIFE " + lives文字と数字を + でつなぐと、数字も文字になってつながる
SceneManager.GetActiveScene().name今のシーンの名前(Game)
SceneManager.LoadScene(...)その名前のシーンを最初から読みこみ直す。スコアも残機も敵も、全部最初の状態に戻る

SetActive とは

SetActive(true) はオブジェクトを表示して動かす、SetActive(false) はかくして止める命令です。 Inspector の名前の左にあるチェックボックスを、スクリプトからオン・オフするのと同じです。 Destroy とちがって、消したわけではないので、あとでまた SetActive(true) で出せます。

3. PlayerLife を作り直す ​

3-1. どう変えるか ​

今の PlayerLife は、敵にぶつかると Destroy(gameObject) で自機を消しています。消してしまうと復活できません。 そこで次のように作り直します。

  1. ぶつかったら爆発して、GameManager に「残機をへらして」とお願いする(Hit)
  2. まだ残機があれば、スタート位置に戻して、2秒間点滅しながら無敵にする(Respawn)
  3. 残機がなければ、自機を SetActive(false) でかくす

3-2. PlayerLife を書きかえる ​

PlayerLife を開いて、次のように書きかえます。Die は Hit に名前が変わり、中身もかなり変わります。

csharp
using System.Collections;                 // ★追加
using UnityEngine;

// 自機がやられる・復活する
public class PlayerLife : MonoBehaviour
{
    public GameObject explosionPrefab;    // 爆発(第9回)
    public AudioClip explosionSound;      // 爆発音(第9回)
    public GameObject model;              // 点滅させる見た目(第11回) // ★追加
    public float invincibleTime = 2f;     // 無敵時間(第11回) // ★追加

    bool isInvincible = false;            // ★追加

    void OnTriggerEnter(Collider other)
    {
        if (isInvincible) return;         // ★追加

        if (other.CompareTag("Enemy"))
        {
            Hit();                        // ★変更
        }
    }

    void Hit()                            // ★変更
    {
        if (explosionPrefab != null)
        {
            Instantiate(explosionPrefab, transform.position, Quaternion.identity);
        }
        if (explosionSound != null)
        {
            AudioSource.PlayClipAtPoint(explosionSound, Camera.main.transform.position);
        }

        GameManager.instance.LoseLife();  // ★追加

        if (GameManager.instance.lives > 0)   // ★追加
        {                                     // ★追加
            StartCoroutine(Respawn());        // ★追加
        }                                     // ★追加
        else                                  // ★追加
        {                                     // ★追加
            gameObject.SetActive(false);      // ★変更
        }                                     // ★追加
    }

    // 少しのあいだ点滅しながら無敵になる
    IEnumerator Respawn()                                 // ★追加
    {                                                     // ★追加
        isInvincible = true;                              // ★追加
        transform.position = new Vector3(-6f, 0f, 0f);    // ★追加
                                                          // ★追加
        float time = 0f;                                  // ★追加
        while (time < invincibleTime)                     // ★追加
        {                                                 // ★追加
            model.SetActive(!model.activeSelf);           // ★追加
            yield return new WaitForSeconds(0.1f);        // ★追加
            time += 0.1f;                                 // ★追加
        }                                                 // ★追加
                                                          // ★追加
        model.SetActive(true);                            // ★追加
        isInvincible = false;                             // ★追加
    }                                                     // ★追加
}

保存します。

コードの解説 ​

行意味
using System.Collections;コルーチン(IEnumerator)を使うための1行。第7回の EnemySpawner でも書いた
public GameObject model;点滅させる見た目(自機のモデル)
bool isInvincible = false;無敵中かどうか
if (isInvincible) return;無敵中なら、ここで関数を終わりにする(ぶつかっても何も起きない)
GameManager.instance.LoseLife();GameManager に残機をへらしてもらう
if (GameManager.instance.lives > 0)へらしたあと、まだ残機があれば復活する
StartCoroutine(Respawn());Respawn というコルーチンを始める
gameObject.SetActive(false);残機がないときは自機をかくす。Destroy ではなくかくすだけにしている
transform.position = new Vector3(-6f, 0f, 0f);スタート位置(画面の左のほう)に戻す
model.SetActive(!model.activeSelf);点滅のしかけ(下の説明)

3-3. コルーチンで点滅させるしくみ ​

第7回で、コルーチンは「とちゅうで待つことができる関数」だと学びました。yield return new WaitForSeconds(0.1f); のところで 0.1 秒待ってから、続きを実行します。

点滅は、この3行のくり返しです。

csharp
model.SetActive(!model.activeSelf);       // 見えていたら消す、消えていたら見せる
yield return new WaitForSeconds(0.1f);    // 0.1秒待つ
time += 0.1f;                             // 待った時間を足していく
  • model.activeSelf は「今、表示されているか」(true か false)
  • ! は「反対にする」記号。!true は false、!false は true
  • つまり SetActive(!model.activeSelf) は「今と反対にする」という意味です

0.1秒ごとに「見える → 消える → 見える…」を、time が invincibleTime(2秒)になるまでくり返します。 最後に model.SetActive(true); で必ず見える状態に戻してから、無敵を終わりにします。

ヒント

gameObject.SetActive(false) で Player 全体をかくすと、スクリプトも当たり判定も止まります。点滅のときは Player ではなく、見た目の Model だけをオン・オフしているので、自機は動かせるままです。

3-4. Model をつなぐ ​

  1. Hierarchy で Player の左の ▶ を開き、Bank の ▶ も開く
  2. Player を選ぶ
  3. Player Life コンポーネントの Model の欄に、Hierarchy の Player > Bank > Model をドラッグする
  4. Invincible Time が 2 になっていることを確認する
  5. Ctrl+S(Macは Cmd+S)でシーンを保存する

4. ゲームオーバー画面を作る ​

4-1. Panel を作る ​

Panel(パネル)は、画面いっぱいに広がる四角い板です。ゲームオーバーの文字やボタンをのせる台にします。

  1. Hierarchy の Canvas を右クリックし、UI > Panel を選ぶ
  2. できた Panel の名前を GameOverPanel に変える
  3. Inspector の Image コンポーネントの Color をクリックする
  4. 色を黒にして、A(透明度)を 150 くらいにする(うしろのゲームがうっすら見える)

4-2. GAME OVER の文字を作る ​

  1. Hierarchy の GameOverPanel を右クリックし、UI > Text - TextMeshPro を選ぶ
  2. 名前を GameOverText に変える
  3. Rect Transform のアンカーが真ん中(middle-center)になっていることを確認する
  4. Pos X を 0、Pos Y を 100 にする
  5. Width を 1200、Height を 200 にする
  6. Text Input を GAME OVER にする
  7. Font Size を 120 にする
  8. Alignment の「中央よせ」と「上下の真ん中」のボタンを押す

4-3. RETRY ボタンを作る ​

  1. Hierarchy の GameOverPanel を右クリックし、UI > Button - TextMeshPro を選ぶ
  2. できた Button の名前を RetryButton に変える
  3. Pos X を 0、Pos Y を -100 にする
  4. Width を 400、Height を 100 にする
  5. RetryButton の ▶ を開き、子の Text (TMP) を選ぶ
  6. Text Input を RETRY にして、Font Size を 48 にする

4-4. ボタンに Retry をつなぐ(On Click) ​

ボタンがクリックされたときに、GameManager の Retry 関数が呼ばれるようにします。

  1. Hierarchy で RetryButton を選ぶ
  2. Inspector の Button コンポーネントの一番下にある On Click () を見る
  3. On Click () の右下の + を押す
  4. 増えた行の左下の None (Object) の欄に、Hierarchy の GameManager オブジェクトをドラッグする
  5. 右側の No Function と書いてあるメニューをクリックする
  6. GameManager > Retry () を選ぶ

よくあるまちがい

None (Object) の欄には、Project の GameManager スクリプトではなく、Hierarchy の GameManager オブジェクトをドラッグします。 また、メニューに Retry が出ないときは、GameManager の Retry に public が付いているか確認しましょう。public な関数だけがボタンから呼べます。

4-5. GameManager につなぐ ​

  1. Hierarchy で GameManager を選ぶ
  2. Game Manager コンポーネントの Lives Text に LivesText をドラッグする
  3. Game Over Panel に GameOverPanel をドラッグする
  4. Lives が 3 になっていることを確認する

4-6. ゲームオーバー画面をかくしておく ​

  1. Hierarchy で GameOverPanel を選ぶ
  2. Inspector の一番上、名前の左にあるチェックボックスを外す
  3. Game ビューから GAME OVER が消えたことを確認する
  4. Ctrl+S(Macは Cmd+S)でシーンを保存する

4-7. シーンが登録されているか確認する ​

SceneManager.LoadScene で読みこめるのは、Build Profiles の Scene List に登録されたシーンだけです。

  1. メニューの File > Build Profiles を開く
  2. 左の一覧で Scene List を選ぶ
  3. 右側のリストに Scenes/Game があり、チェックが入っていることを確認する
  4. なければ Add Open Scenes を押して追加する
  5. Build Profiles のウィンドウを閉じる

先生へ

テンプレートの SampleScene は最初から Scene List に入っているので、第1回で名前を Game に変えていれば、ふつうはそのまま登録されています。シーンの登録は第12回で改めて扱います。

5. ゲームオーバー中は敵を出さない ​

5-1. EnemySpawner を書きかえる ​

ゲームオーバーになっても敵が出つづけるのは変なので、止めましょう。EnemySpawner を開いて、次のように書きかえます。

csharp
using System.Collections;
using UnityEngine;

// 敵を一定時間ごとに出す
public class EnemySpawner : MonoBehaviour
{
    public GameObject enemyPrefab;      // 出す敵
    public float interval = 1.5f;       // 出す間隔(秒)
    public float minY = -4f;            // 出す高さの範囲
    public float maxY = 4f;

    void Start()
    {
        StartCoroutine(SpawnLoop());
    }

    IEnumerator SpawnLoop()
    {
        while (true)
        {
            yield return new WaitForSeconds(interval);

            // ゲームが終わっていたら出さない(第11回)            // ★追加
            if (!GameManager.instance.isPlaying) continue;          // ★追加

            // ランダムに高さを決める
            float y = Random.Range(minY, maxY);
            Vector3 pos = new Vector3(transform.position.x, y, 0f);
            Instantiate(enemyPrefab, pos, Quaternion.identity);
        }
    }
}

保存します。

コードの解説 ​

行意味
GameManager.instance.isPlayingGameManager の「ゲーム中かどうか」。ここでも static の掲示板が活やくする
!GameManager.instance.isPlaying! で反対にするので「ゲーム中ではないなら」という意味
continue;ループの残りを飛ばして、while の最初に戻る。つまり「今回は敵を出さずに、また待つ」

6. 動作確認 ​

6-1. ためしてみる ​

  1. ▶ Play ボタンを押す
  2. わざと敵にぶつかる
  3. 爆発して「LIFE 2」になり、自機が左に戻って点滅すればOK
  4. 点滅しているあいだは、敵にぶつかっても何も起きないことを確かめる
  5. さらに2回ぶつかって「LIFE 0」にする
  6. 自機が消えて「GAME OVER」と RETRY ボタンが出て、新しい敵が出てこなくなればOK
  7. RETRY ボタンを押すと、スコア 000000・LIFE 3 から始まればOK

ヒント

テストのたびに3回ぶつかるのが大変なら、GameManager の Lives を Inspector で 1 にしてためしてもOKです。終わったら 3 に戻しましょう。

注意

RETRY のあと、画面が急に暗くなることがあります。これは Unity エディターの中だけで起きることがあり、ライトの情報が作られていないのが原因です。Window > Rendering > Lighting を開き、下の Generate Lighting を押すと直ります。

チャレンジ課題 ​

  • 最初の残機を5にしてみよう(ヒント: GameManager の Inspector で Lives を変える)
  • 点滅を速くしたり遅くしたりしてみよう(ヒント: 0.1f を2か所とも変える)
  • 復活する位置を Inspector で変えられるようにしてみよう(ヒント: public Vector3 respawnPosition = new Vector3(-6f, 0f, 0f); を作る)
  • GAME OVER の文字の色を赤にしたり、フチどりを付けたりしてみよう

つまずきポイント ​

こんなときはここを確認
ぶつかったら NullReferenceException が出るPlayerLife の Model、GameManager の Lives Text・Game Over Panel に入れ忘れがないか
最初から GAME OVER が出ているGameOverPanel のチェックボックスを外したか
自機が点滅しないModel に Player 自身を入れていないか。Player > Bank > Model を入れる
1回ぶつかっただけで残機が2つへるPlayer に Collider を2つ付けていないか。無敵の if (isInvincible) return; を書いたか
RETRY を押しても何も起きないOn Click () に GameManager.Retry が入っているか。シーンに EventSystem があるか
RETRY で「シーンが見つからない」エラーFile > Build Profiles の Scene List に Game が入っているか

まとめ ​

  • 残機は GameManager の lives で管理し、LoseLife でへらして 0 になったら GameOver
  • コルーチンと SetActive(!model.activeSelf) で点滅させ、そのあいだは無敵にする
  • ボタンの On Click () に、オブジェクトと public な関数を登録するとクリックで呼べる
  • SceneManager.LoadScene でシーンを読みこみ直すと、ゲームを最初からやり直せる