Developer

【初心者Unity】懐かしのゲームを作ってみよう!⑮
2022.02.25
Lv1

【初心者Unity】懐かしのゲームを作ってみよう!⑮


はじめに

今回は前回に引き続き、↓のゲームを作っていきます。

前回、ゲームクリアの時の動きが完成しました。

いよいよ本記事でこのゲーム最後の機能を実装します。
その機能は。。。「Continue」です!
今現状のゲームの動きをまとめると、「Ballが動く」→「Blockが消える」→「ゲームオーバーorゲームクリア」で終了です。
「ゲームオーバー」や「ゲームクリア」になった後、画面は止まったままで、再度ゲームを開始するためには一度ゲームを終了して再度立ち上げなければなりません。
これでは少々不便ですよね?
ゲームを再起動することなく、ゲームはプレイした状態のまま、ボタン一つで新しいゲームに移りたいですよね?
ということで今回はゲームオーバーorゲームクリアした後に再度ゲームをプレイできるボタンを取り付けましょう!


「Continue」のボタンを画面に出す

それではまずは「Continue」のボタンを画面に出しましょう。
画面上に描画するものなので、出し方は「GameOver」や「GameClear」の時と同じ。
ただ、今回はただの「文字」ではなく「ボタン」なので、Canvasの子オブジェクトとしてButtonを追加します。
CanvasにButtonを追加するので、Hierarchy上の「Canvas」を右クリックし、「UI」→「Button」で追加します。

↓の様に小さいながらもButtonが画面上に出てきたのではないでしょうか?今後分かりやすいように名前を「ButtonContinue」にしておきましょう。

次に、ButtonContinueの方のプロパティを↓の様に調整します。

Width Height
640 90

↓の様にボタンが大きくなったのではないでしょうか?

次に、文字を「Continue」にし、文字のサイズを調整しましょう。
これを調整する場所が少々分かりにくいのですが、「ButtonContinue」の文字の左の三角形をクリックして出てくるTextを選択することで調整することができます。

最初は「ButtonContinue」は画面に出てこないようにする

今、「ButtonContinue」を画面に出すことができましたが、ゲームプレイ中この文字がずっと画面上に出ているのはおかしいですよね?
ということで一旦ボタンを画面上から消しましょう。
これはプログラム関係なく、画面上の操作で簡単に実装できます。
消したいオブジェクト、今回は「ButtonContinue」を選択し、Inspecter上で名前の左のチェックを外す。これだけ!
いかがでしょうか?Hierarchy上では「ButtonContinue」が残りつつ、画面からボタンが消えたのではないでしょうか?

ボタン出現のタイミングはいつなのか?

ボタンを消したところで、次に考えなければならないのが、いつこのボタンを出すか、です。
このボタンを使うことでゲームを再開したいわけですから、ゲームが止まるタイミングで出現させれば良いですよね?
ゲームが止まるタイミングとは。。。ズバリ!ゲームオーバーとゲームクリアのタイミングです!
そこで、その2つのタイミングでボタンが出現するようにプログラムを実装しましょう!

ボタンを出現させる実装

実はこの実装、これまでの「GameOver」「GameClear」の時と全く同じです。
ゲームオーバー時とゲームクリア時それぞれ別に実装しなければなのでまずはゲームオーバー時のボタンの出現から考えていきましょう。
まず、ゲームオーバーのタイミングですが、これは「GameOver」の出現タイミングと同じです。
そこでゲームオーバー時の「ボタン出現の処理」は「GameOver出現の処理」と同様、BallScript内の「下端に着いた時の処理」に実装すれば良いでしょう。

public class BallScript : MonoBehaviour
{
    Vector2 pos;
    public int directionX;
    public int directionY;
    GameObject gameOver;
    // Start is called before the first frame update
    void Start()
    {
        pos = transform.position;
        directionX = 1;
        directionY = 1;
        gameOver = GameObject.Find("Canvas").transform.Find("GameOver").gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        //斜め45度に進ませる
        Move();

        //右端に着いた時の処理
        if (IsRightEdge())
        {
            directionX = -1;
        }

        //左端に着いた時の処理
        if (IsLeftEdge())
        {
            directionX = 1;
        }

        //上端に着いた時の処理
        if (IsUpperEdge())
        {
            directionY = -1;
        }

        //下端に着いた時の処理
        if (IsLowerEdge())
        {
            directionX = 0;
            directionY = 0;
            gameOver.SetActive(true);
            //ボタン出現の処理
        }
    }

    void Move()
    {
        pos.x += 0.03f * directionX;
        pos.y += 0.03f * directionY;
        transform.position = pos;
    }

    bool IsRightEdge()
    {
        return pos.x > 2.5;
    }
    
    bool IsLeftEdge()
    {
        return pos.x < -2.5;
    }
    
    bool IsUpperEdge()
    {
        return pos.y > 5;
    }
    
    bool IsLowerEdge()
    {
        return pos.y < -5;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.name == "Paddle")
        {
            directionY = 1;
        }
        if (collision.gameObject.tag == "Block")
        {
            directionX = -directionX;
            directionY = -directionY;
        }
    }
}

実際の処理も「GameOver出現の処理」と同様です。

GameObject型の変数buttonの宣言

Startメソッド内で変数buttonにオブジェクトButtonContinueを参照させる

buttonをSetActive(true)にする。

これらを実装すると↓の様になります。

public class BallScript : MonoBehaviour
{
    Vector2 pos;
    public int directionX;
    public int directionY;
    GameObject gameOver;
    GameObject button;
    // Start is called before the first frame update
    void Start()
    {
        pos = transform.position;
        directionX = 1;
        directionY = 1;
        gameOver = GameObject.Find("Canvas").transform.Find("GameOver").gameObject;
        button = GameObject.Find("Canvas").transform.Find("ButtonContinue").gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        //斜め45度に進ませる
        Move();

        //右端に着いた時の処理
        if (IsRightEdge())
        {
            directionX = -1;
        }

        //左端に着いた時の処理
        if (IsLeftEdge())
        {
            directionX = 1;
        }

        //上端に着いた時の処理
        if (IsUpperEdge())
        {
            directionY = -1;
        }

        //下端に着いた時の処理
        if (IsLowerEdge())
        {
            directionX = 0;
            directionY = 0;
            gameOver.SetActive(true);
            button.SetActive(true);
        }
    }

    void Move()
    {
        pos.x += 0.03f * directionX;
        pos.y += 0.03f * directionY;
        transform.position = pos;
    }

    bool IsRightEdge()
    {
        return pos.x > 2.5;
    }
    
    bool IsLeftEdge()
    {
        return pos.x < -2.5;
    }
    
    bool IsUpperEdge()
    {
        return pos.y > 5;
    }
    
    bool IsLowerEdge()
    {
        return pos.y < -5;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.name == "Paddle")
        {
            directionY = 1;
        }
        if (collision.gameObject.tag == "Block")
        {
            directionX = -directionX;
            directionY = -directionY;
        }
    }
}

いかがでしょうか?
↓の様にゲームオーバー時にボタンが出現したのではないでしょうか?

次に、ゲームクリア時のボタンの出現を考えていきましょう。
まず、ゲームクリアのタイミングですが、これは「GameClear」の出現タイミングと同じです。
そこでゲームクリア時の「ボタン出現の処理」は「GameClear出現の処理」と同様、BlocksScript内の「cntChildrenとBlocksの子オブジェクトの数が同数になった時」の下に実装すれば良いでしょう。

public class BlocksScript : MonoBehaviour
{
    public int cntChildren;
    GameObject gameClear;
    GameObject ball;
    // Start is called before the first frame update
    void Start()
    {
        cntChildren = 0;
        gameClear = GameObject.Find("Canvas").transform.Find("GameClear").gameObject;
        ball = GameObject.Find("Ball");
    }

    // Update is called once per frame
    void Update()
    {
        if (cntChildren == transform.childCount)
        {
            gameClear.SetActive(true);
            ball.GetComponent<BallScript>().directionX = 0;
            ball.GetComponent<BallScript>().directionY = 0;
            //ボタン出現の処理
        }
    }
}

実際の処理も「GameClear出現の処理」と同様です。

GameObject型の変数buttonの宣言

Startメソッド内で変数buttonにオブジェクトButtonContinueを参照させる

buttonをSetActive(true)にする。

これらを実装すると↓の様になります。

public class BlocksScript : MonoBehaviour
{
    public int cntChildren;
    GameObject gameClear;
    GameObject ball;
    GameObject button;
    // Start is called before the first frame update
    void Start()
    {
        cntChildren = 0;
        gameClear = GameObject.Find("Canvas").transform.Find("GameClear").gameObject;
        ball = GameObject.Find("Ball");
        button = GameObject.Find("Canvas").transform.Find("ButtonContinue").gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        if (cntChildren == transform.childCount)
        {
            gameClear.SetActive(true);
            ball.GetComponent<BallScript>().directionX = 0;
            ball.GetComponent<BallScript>().directionY = 0;
            button.SetActive(true);
        }
    }
}

いかがでしょうか?
↓の様にゲームクリア時にボタンが出現したのではないでしょうか?

ボタンのクリックの実装

さあ、ボタンも出現したし完成!と思いきや、このボタン機能してないですよね。。。?
それもそのはず、ボタンをクリックしたときの処理は自分でプログラムを書くことで実装しなければなりません。。。
さあ、最後のひと踏ん張りです。
いよいよ最後の実装、ボタンのクリック時の動きの実装に入りましょう。
ボタンクリック時の動きなので、実装するスクリプトはButtonContinue自体にアタッチしましょう。
そこで↓の様にまずはButtonContinueScriptをButtonContinueにアタッチしてください。

次に、ButtonContinueScriptの中で、ボタンをクリックしたときに呼び出されるメソッドを定義します。
現在ButtonContinueScriptに用意されているメソッドを見ると、デフォルトのStart()とUpdate()しかありませんね。
そこで、クリック時に呼び出されるためのメソッドOnClick()を↓の様にアクセスレベルpublicで定義しましょう。
アクセスレベルをpublicにする必要があるのは、後でこのOnClick()をクリックの動作と紐づけるためにInspecter上で操作しなければならないためです。

public class ButtonContinueScript : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void OnClick()
    {

    }
}

次に、↑にも書いた様にOnClick()とクリックの動作を紐づけます。
Hierarchy上のButtonContinueを選択し、Inspecter上の「On Click ()」の項目の下の「+」をクリック。

Inspecter上のListの「None」にHierarchy上のButtonContinueをドラッグアンドドロップ。

Inspecter上のListの「No Function」>「ButtonContinueScript」>「OnClick()」を選択して完成です。

さて、いよいよButtonContinueScriptを実装していきます。
まず、今まで同様、プログラムを実装する前にボタンを押すと何が起こってほしいのか言語化しましょう。
そう、全てのオブジェクトの動き、場所、その他諸々のステータスをリセットしたいんですよね?
ということでこの願望を基に言語化すると↓の様になります。

・Ballをリセットする
・Paddleをリセットする
・Blocksをリセットする
・GameOverを無効化する(画面から消す)
・GameClearを無効化する(画面から消す)
・ButtonContinueを無効化する(画面から消す)

案外単純ですね?(笑)
GameOver、GameClear、ButtonContinueの動きに関しては具体的に「無効化する」と書いてあるので実装は容易いでしょう。
ただ、Ball、Paddle、Blocksについては「リセットする」。。。漠然としていますね(笑)
ただ、これはこのままでオッケーです。
リセットの具体的な処理はそれぞれのオブジェクトのプロパティ(位置や変数等)を操作する必要があるので、それらのオブジェクト(BallとPaddleとBlocks)側で例えば「Reset()メソッド」にでもまとめて実装しましょう。
そしてButtonContinueScriptの方では、Reset()メソッドを呼び出す形で実装します。
こうすることでButtonContinueScriptでは細かい処理を考えることなく、書きやすく見やすいプログラムを実装することができます。
そこでまずはBall、Paddle、Blocksのリセットを実装します。
ButtonContinueScriptの方ではそれぞれのオブジェクトのReset()を呼び出すだけなので、実装の流れは↓の様になります。

GameObject型の変数「ball」「paddle」「blocks」の宣言

Startメソッド内で変数「ball」「paddle」「blocks」にオブジェクトBall、Paddle、Blocksを参照させる

ball、paddle、blocksにアタッチされたスクリプト内のメソッドReset()を呼び出す。

これらを実装すると↓の様になります。

public class ButtonContinueScript : MonoBehaviour
{
    GameObject ball;
    GameObject paddle;
    GameObject blocks;
    // Start is called before the first frame update
    void Start()
    {
        ball = GameObject.Find("Ball");
        paddle = GameObject.Find("Paddle");
        blocks = GameObject.Find("Blocks");
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void OnClick()
    {
        ball.GetComponent<BallScript>().Reset();
        paddle.GetComponent<PaddleScript>().Reset();
        blocks.GetComponent<BlocksScript>().Reset();
    }
}

このままでは21-23行目がコンパイルエラーしているかと思います。
理由は簡単でBallScriptにもPaddleScriptにもBlockScriptにもReset()をまだ実装していないからですね(笑)
そこで次にそれぞれReset()を実装していきましょう。
まずはPaddleからいきましょう。
リセットするということは、Paddleのプロパティの中でゲームプレイ中に変化したものを元に戻すということです。
そこで、ゲームプレイ中にPaddleの変化したものは何か考えてみると、それはPaddleの位置だけであることが分かります。
Paddleの位置はPaddleScriptの中の変数posで制御しているので、この「変数posを初期位置に戻す」という処理をReset()メソッドに実装しましょう。
そこでまず、ゲーム開始時にPaddleの初期位置を記録しておく必要があります。
これは、Vector2型の変数startPosとして宣言し、Startメソッド内でposと一緒にPaddleの位置を取ることで記録できるので↓の様になります。

public class PaddleScript : MonoBehaviour
{
    Vector2 pos;
    Vector2 startPos;
    // Start is called before the first frame update
    void Start()
    {
        pos = transform.position;
        startPos = transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.RightArrow) && !IsRightEdge())
        {
            pos.x += 0.05f;
        }
        if (Input.GetKey(KeyCode.LeftArrow) && !IsLeftEdge())
        {
            pos.x -= 0.05f;
        }
        transform.position = pos;
    }

    bool IsRightEdge()
    {
        return pos.x > 2.5;
    }

    bool IsLeftEdge()
    {
        return pos.x < -2.5;
    }
}

初期位置を取ったところでいよいよReset()の処理です。これは、変数posの値をstartPosの値にすればよいので、↓の様になります。
外部クラスであるButtonContinueScriptで参照できるようにアクセス修飾子「public」をつけることに注意してください。

public class PaddleScript : MonoBehaviour
{
    Vector2 pos;
    Vector2 startPos;
    // Start is called before the first frame update
    void Start()
    {
        pos = transform.position;
        startPos = transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.RightArrow) && !IsRightEdge())
        {
            pos.x += 0.05f;
        }
        if (Input.GetKey(KeyCode.LeftArrow) && !IsLeftEdge())
        {
            pos.x -= 0.05f;
        }
        transform.position = pos;
    }

    bool IsRightEdge()
    {
        return pos.x > 2.5;
    }

    bool IsLeftEdge()
    {
        return pos.x < -2.5;
    }

    public void Reset()
    {
        pos = startPos;
    }
}

するとどうでしょう、ButtonContinueScriptの22行目のコンパイルエラーが解消されたのではないでしょうか。
この調子で次にBallScriptにもReset()を実装しましょう。
PaddleScript同様、ゲームプレイ中に変化するBallのプロパティに着目しますが、BallはPaddleより少し要素が増えます。
Paddleでは位置だけでしたが、Ballでは位置と、「その進む方向」というプロパティが存在します。
そこで、進む方向を表す変数「directionX」と「directionY」も初期値に戻してあげる必要があります。
まず、位置についてですが、これは↓の様にPaddleと同じ方法で初期位置に戻すことができます。

public class BallScript : MonoBehaviour
{
    Vector2 pos;
    public int directionX;
    public int directionY;
    GameObject gameOver;
    GameObject button;
    Vector2 startPos;
    // Start is called before the first frame update
    void Start()
    {
        pos = transform.position;
        directionX = 1;
        directionY = 1;
        gameOver = GameObject.Find("Canvas").transform.Find("GameOver").gameObject;
        button = GameObject.Find("Canvas").transform.Find("ButtonContinue").gameObject;
        startPos = transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        //斜め45度に進ませる
        Move();

        //右端に着いた時の処理
        if (IsRightEdge())
        {
            directionX = -1;
        }

        //左端に着いた時の処理
        if (IsLeftEdge())
        {
            directionX = 1;
        }

        //上端に着いた時の処理
        if (IsUpperEdge())
        {
            directionY = -1;
        }

        //下端に着いた時の処理
        if (IsLowerEdge())
        {
            directionX = 0;
            directionY = 0;
            gameOver.SetActive(true);
            button.SetActive(true);
        }
    }

    void Move()
    {
        pos.x += 0.03f * directionX;
        pos.y += 0.03f * directionY;
        transform.position = pos;
    }

    bool IsRightEdge()
    {
        return pos.x > 2.5;
    }
    
    bool IsLeftEdge()
    {
        return pos.x < -2.5;
    }
    
    bool IsUpperEdge()
    {
        return pos.y > 5;
    }
    
    bool IsLowerEdge()
    {
        return pos.y < -5;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.name == "Paddle")
        {
            directionY = 1;
        }
        if (collision.gameObject.tag == "Block")
        {
            directionX = -directionX;
            directionY = -directionY;
        }
    }

    public void Reset()
    {
        pos = startPos;
    }
}

次に、directionXとdirectionYですが、これはStartメソッド内を見てみると、ゲーム開始時それぞれ1に設定されていることが分かるので、↓の様にReset()の中でもそれぞれ1に設定すれば完成です。

public class BallScript : MonoBehaviour
{
    Vector2 pos;
    public int directionX;
    public int directionY;
    GameObject gameOver;
    GameObject button;
    Vector2 startPos;
    // Start is called before the first frame update
    void Start()
    {
        pos = transform.position;
        directionX = 1;
        directionY = 1;
        gameOver = GameObject.Find("Canvas").transform.Find("GameOver").gameObject;
        button = GameObject.Find("Canvas").transform.Find("ButtonContinue").gameObject;
        startPos = transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        //斜め45度に進ませる
        Move();

        //右端に着いた時の処理
        if (IsRightEdge())
        {
            directionX = -1;
        }

        //左端に着いた時の処理
        if (IsLeftEdge())
        {
            directionX = 1;
        }

        //上端に着いた時の処理
        if (IsUpperEdge())
        {
            directionY = -1;
        }

        //下端に着いた時の処理
        if (IsLowerEdge())
        {
            directionX = 0;
            directionY = 0;
            gameOver.SetActive(true);
            button.SetActive(true);
        }
    }

    void Move()
    {
        pos.x += 0.03f * directionX;
        pos.y += 0.03f * directionY;
        transform.position = pos;
    }

    bool IsRightEdge()
    {
        return pos.x > 2.5;
    }
    
    bool IsLeftEdge()
    {
        return pos.x < -2.5;
    }
    
    bool IsUpperEdge()
    {
        return pos.y > 5;
    }
    
    bool IsLowerEdge()
    {
        return pos.y < -5;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.name == "Paddle")
        {
            directionY = 1;
        }
        if (collision.gameObject.tag == "Block")
        {
            directionX = -directionX;
            directionY = -directionY;
        }
    }

    public void Reset()
    {
        pos = startPos;
        directionX = 1;
        directionY = 1;
    }
}

これにてBallのReset()も完成!ButtonContinueScriptの21行目のコンパイルエラーが解消されたのではないでしょうか。
それでは最後に、BlocksScriptにReset()を実装します。
Blocksはこれまでと少々違います。
まず第一にBlockに関しては位置は移動していません。
それではBlocksでゲームプレイ中に変化しているものは何か。。。
それはSetActive()ですね。
プレイ中にSetActive()がtrueからBallが当たる度にfalseに変わっていきます。
つまりBlocksはリセット時に全ての子オブジェクトをSetActive(true)に戻す必要があるということです。
また、これは些細な事ですが(でも重要)、変数cntChildrenを0に戻す必要があります。
ということでBlocksScriptにおいてこれら2つの機能を実装しましょう。
まずは簡単な方、cntChildrenを0にする方を実装します。
これはこの言葉そのまま↓の様になります。

public class BlocksScript : MonoBehaviour
{
    public int cntChildren;
    GameObject gameClear;
    GameObject ball;
    GameObject button;
    // Start is called before the first frame update
    void Start()
    {
        cntChildren = 0;
        gameClear = GameObject.Find("Canvas").transform.Find("GameClear").gameObject;
        ball = GameObject.Find("Ball");
        button = GameObject.Find("Canvas").transform.Find("ButtonContinue").gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        if (cntChildren == transform.childCount)
        {
            gameClear.SetActive(true);
            ball.GetComponent<BallScript>().directionX = 0;
            ball.GetComponent<BallScript>().directionY = 0;
            button.SetActive(true);
        }
    }

    public void Reset()
    {
        cntChildren = 0;
    }
}

次に、Blocksの子オブジェクトを全てSetActive(true)する実装ですが、子オブジェクトを操作するためにまずはゲーム開始時にBlocksScript内でBlocksの子オブジェクトを全て変数として参照する必要があります。
子オブジェクト達を参照するのに変数を1つ1つ用意するのは骨が折れますよね?
そこで、全ての子オブジェクトを1つの変数で参照できる便利な機能、Listを利用しましょう。
ジェネリクスをGameObject型でList「children」を宣言し、Startメソッド内で変数childrenの参照をBlocksの子オブジェクト達にすることで、リセット時に子オブジェクト達を操作できるようにします。
これを実装すると↓の様になります。

public class BlocksScript : MonoBehaviour
{
    public int cntChildren;
    GameObject gameClear;
    GameObject ball;
    GameObject button;
    List<GameObject> children = new List<GameObject>();
    // Start is called before the first frame update
    void Start()
    {
        cntChildren = 0;
        gameClear = GameObject.Find("Canvas").transform.Find("GameClear").gameObject;
        ball = GameObject.Find("Ball");
        button = GameObject.Find("Canvas").transform.Find("ButtonContinue").gameObject;
        for (int index = 0; index < transform.childCount; index++)
        {
            children.Add(transform.GetChild(index).gameObject);
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (cntChildren == transform.childCount)
        {
            gameClear.SetActive(true);
            ball.GetComponent<BallScript>().directionX = 0;
            ball.GetComponent<BallScript>().directionY = 0;
            button.SetActive(true);
        }
    }

    public void Reset()
    {
        cntChildren = 0;
    }
}

これにて下準備は完璧!後はReset()の中でList「children」の要素1つ1つをSetActive(true)にするだけです。
これを実装すると↓の様になります。

public class BlocksScript : MonoBehaviour
{
    public int cntChildren;
    GameObject gameClear;
    GameObject ball;
    GameObject button;
    List<GameObject> children = new List<GameObject>();
    // Start is called before the first frame update
    void Start()
    {
        cntChildren = 0;
        gameClear = GameObject.Find("Canvas").transform.Find("GameClear").gameObject;
        ball = GameObject.Find("Ball");
        button = GameObject.Find("Canvas").transform.Find("ButtonContinue").gameObject;
        for (int index = 0; index < transform.childCount; index++)
        {
            children.Add(transform.GetChild(index).gameObject);
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (cntChildren == transform.childCount)
        {
            gameClear.SetActive(true);
            ball.GetComponent<BallScript>().directionX = 0;
            ball.GetComponent<BallScript>().directionY = 0;
            button.SetActive(true);
        }
    }

    public void Reset()
    {
        cntChildren = 0;
        foreach (GameObject child in children)
        {
            child.SetActive(true);
        }
    }
}

これにてBlocksのReset()も完成!ButtonContinueScriptの23行目のコンパイルエラーが解消されたのではないでしょうか。
この状態でゲームをプレイすると。。。
いかがでしょうか。GameOverの文字は消えないものの、Ballの位置、Paddleの位置が元に戻り、消えてしまったBlocksの子オブジェクトが復活するのではないでしょうか?

ここまでくればもうほぼ完成です。
最後に、GameOver、GameClear、ButtonContinueを実装します。
GameOverとGameClearに関しては実装の流れはこれまでと同じです。

GameObject型の変数「gameOver」「gameClear」の宣言

Startメソッド内で変数「gameOver」「gameClear」にオブジェクトGameOver、GameClearを参照させる

gameOver、gameClearをSetActive(false)にする。

ButtonContinueに関してはこのスクリプトがアタッチされているゲームオブジェクト本体なので、ゲームオブジェクトを検索することなく直接SetActive(false)します。
これらを実装すると↓の様になります。

public class ButtonContinueScript : MonoBehaviour
{
    GameObject ball;
    GameObject paddle;
    GameObject blocks;
    GameObject gameOver;
    GameObject gameClear;
    // Start is called before the first frame update
    void Start()
    {
        ball = GameObject.Find("Ball");
        paddle = GameObject.Find("Paddle");
        blocks = GameObject.Find("Blocks");
        gameOver = transform.parent.Find("GameOver").gameObject;
        gameClear = transform.parent.Find("GameClear").gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void OnClick()
    {
        ball.GetComponent<BallScript>().Reset();
        paddle.GetComponent<PaddleScript>().Reset();
        blocks.GetComponent<BlocksScript>().Reset();
        this.gameObject.SetActive(false);
        gameOver.SetActive(false);
        gameClear.SetActive(false);
    }
}

ゲームをプレイすると。。。
いかがでしょうか?↓の様にContinueをクリックするとGameOverの文字とボタンが消えたのではないでしょうか?

おわりに

15回に渡ってお送りしてきた本シリーズですが、今回で無事終了しました!
長らく読んでいただいたみなさんありがとうございました。
Unityはツールなので、やはり説明書を読むよりは実際にゲームを作りながらの方がノウハウが掴みやすいのではないかという思いで始めた本連載でしたが、いかがでしょうか?実際に手を動かすことで楽しく効果的に学ぶことはできたでしょうか?
また、本記事では実装のみならず設計から皆さんと取り組み、どの様なことを考えながら設計をすれば良いのか、その視点も養っていただけたかと思います。
本記事はこれにて終了ですが、皆さんの想像力次第でこのゲームはいくらでも改良の余地があります。
PaddleがBallを跳ね返す度に点数をつけていってもいいし、ステージを増設してもいいし、BallやPaddleを強化するアイテムを作ってもいいし。。。等々。
また、他にもぱっと思いつくようなゲームもあるかと思います。
このゲームをどんどん改良していただき、皆さんなりのゲームにしていただいても良いです。
全く違う他のゲームを作成していただいても良いです。
本記事を通して身に着けた技術を今後も皆さん自身で伸ばしていっていただけたら幸いです。

 


連載目次リンク

「初心者のための」Unityゲーム制作 目次

Unity実践編 - 目次リンク

実践Unityゲームプログラミング 連載目次