[Unity] ブロック崩しをあのアプリ風にする #9 ゲームオーバーとリトライ

ゲームオーバーとリトライを作ってゲームロジックは一区切りとします。


ゲームオーバー画面


とりあえずシンプルにUIのTextで表示したいと思います。
TouchScriptのCursorsをヒエラルキーに置いているとTextを作成した時にCursorsの中に入ってしまうので先にCreate > UI > CanvasでCanvasだけ作って、そのあと作成したCanvasを選択した状態でCreate > UI > Textとします。
Textの名前をGameOverTextとしておきます。

設定は以下のようにします。


GameOverTextを右クリック > Duplicateで複製、名前をDengerTextとします。
Textの文字も『Denger !』と変えておきます。


ちょっとシンプル過ぎる気もするけどひとまず画面は出来ました。


ゲームオーバー判定


ブロックが一番下までついたらゲームオーバーです。
初めは下の方に透明なゾーンを作り、そこに当たり判定をつけようかとも思ったんですが、ブロックは決められたタイミングと移動量で動いているのでわざわざコライダーで拾う必要はありません。
ここはブロックの位置で判断する形にしたいと思います。

BlockManagerに追記します。


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

public class BlockManager : MonoBehaviour {

    public GameObject[] waves;
    private GameObject wave;
// ゲームオーバーになるz座標
    private float gameOverLine;
// 危険ラインのz座標
    private float dengerLine;
// ゲームオーバーテキスト
    public Text gameOverText;
// 危険ゾーンテキスト
    public Text dengerText;

    void Start () {
        BlockSet(0);
        GameObject obj = GameObject.Find ("Bwall");
        gameOverLine = obj.transform.position.z + obj.transform.lossyScale.z/2 + 0.4f;
        dengerLine = gameOverLine + 0.8f;
    }
//〜中略〜

    IEnumerator Move(Vector3 pos){
        while(wave.transform.position.z >= pos.z){
            wave.transform.position -= new Vector3 (0, 0, 0.1f);
            yield return null;
        }
        wave.transform.position = pos;
        CrossOverJodg (); 
    }

// 線を超えているか判定
    void CrossOverJodg(){
        bool isDenger = false;
//waveの子要素ブロックを一つずつ取り出す
        foreach (Transform child in wave.transform) {
//gameOverLineより下なら
            if (child.position.z <= gameOverLine) {
                dengerText.enabled = false;
                gameOverText.enabled = true;
                break;
            }
//dengerLineより下なら
            else if (child.position.z <= dengerLine) {
                isDenger = true;
            }
            dengerText.enabled = isDenger;
        }
    }
}

Start

gameOverLineはBwallを基準にしてブロックの半分の高さに設定しています。
dengerLineはそこから1ブロック 分上に設定してます。

Move

ブロックの移動が終わったタイミングでCrossOverJodg ()を呼び出します。

CrossOverJodg

ブロックが線を超えているかの判定をします。
isDengerはデンジャーゾーンに入っているかどうかのフラグです。
先にゲームオーバーかどうかの判定をして、もしそうならそれ以上見る必要はないので
gameOverTextを表示してループを抜けています。
次にデンジャーゾーンに入っているかどうかの判定をします。そのあと入っているかどうかに関わらずdengerTextの状態を更新します。trueだからと言ってループを抜けてしまうと、そのあとのブロックがゲームオーバー領域だった場合でもその判定が行われなくなってしまいます。
なので全てのブロックに対してチェックをしなければならないので回り道のような作りになりました。

Text2つをインスペクタから登録しておきます。


登録したらどちらのテキストもチェックを外して非アクティブにしておきます



ちゃんと表示されてるのでもう少し手を加えていきます。
今の時点ではゲームオーバーになっても入力できてしまうのでそれを無効化します。

GameManagerスクリプトに追加します。
//BlockManagerから呼ばれる
    public void GameOver(){
        GameObject.Find("InputController").GetComponent<InputController>().enabled = false;
    }

InputControllerを無効化しているだけです。BlockManagerのCrossOverJodgから呼び出します。

//〜中略〜
 void CrossOverJodg(){
  bool isDenger = false;
  foreach (Transform child in wave.transform) {
   if (child.position.z <= gameOverLine) {
    dengerText.enabled = false;
    gameOverText.enabled = true;
    GameObject.Find ("GameManager").GetComponent<GameManager>().GameOver();
    break;
   } else if (child.position.z <= dengerLine) {
    isDenger = true;
   }
   dengerText.enabled = isDenger;
  }
 }

リトライできるようにする

続いてリトライ画面も作っていきます。


リトライボタン作成

Create > UI > Button でCanvas内にボタンを作ります。Anchor Presetsをartを押しながら選択、PosYを-100とします。表示されるTextを『Retry ?』にしてButtonもRetryButton、テキストもRetryTextにします。



GameManagerにRetryメソッドを追加し、現在のステージ情報を保存しておきます。

    private int currentStage;
//〜中略〜

    public void Retry(){
        Init (currentStage);
    }

RetryButtonのインスペクタからGameManagerのRetryメソッドを紐付けます。


Retryボタンのチェックを外して非アクティブ化しておきます。

リトライ時のスクリプト


GameManagerを編集します。

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

public class GameManager : MonoBehaviour {

 Vector3 staticOrigin;
 public GameObject ballBornPoint;
    public GameObject reBtn;
    public InputController inputCon;
    public BlockManager blockMgr;
    public BallManager ballMgr;

 void Start () {
        Init ();
 }
    public void Init(){
        GameObject Bwall = GameObject.Find ("Bwall");
        float _z = Bwall.transform.position.z + Bwall.transform.lossyScale.z/2 + 0.4f;
        ballBornPoint.transform.position = transform.position= new Vector3 (0, 0, _z);;
        reBtn.SetActive(false);
        blockMgr.Init ();
        ballMgr.Init ();
        inputCon.enabled = true;
}

    public void GameOver(){
        inputCon.enabled = false;
        reBtn.SetActive(true);
    }
}


Init()で新たにブロックとボールの初期化処理としてBlockManager.Init()BallManager.Init()を呼び出します。そのあとでInputControllerを有効化しています。
この2つも作ります。

BlockManagerの初期化


//削除
//void Start () {
//        Init ();
// }
//初期化
    public void Init(){
        BlockSet(0);
        GameObject obj = GameObject.Find ("Bwall");
        gameOverLine = obj.transform.position.z + obj.transform.lossyScale.z/2 + 0.4f;
        dengerLine = gameOverLine + 0.8f; 
    }

//Waveの生成
 public void BlockSet(int w){
        Destroy (wave);
        wave = Instantiate (waves[w],new Vector3 (0, 0, 0),Quaternion.identity)as GameObject;
 }

Start()の中身をInit()として切り出し、BlockSet()のはじめに現在のwaveの削除処理を追加しました。
初期化処理はGameManagerから呼び出されるのでStart()は削除します。


BallManagerの初期化

BallManagerもやることはBlockManagerと変わりません。Start()Init()として切り出してボールの位置をリセットします。でもリセット方法が少し変わります。

 void Start () {
    blockManager = GameObject.Find ("BlockManager").GetComponent<BlockManager> ();
 }

//初期化
    public void Init(){
        foreach (var ball in balls) {
            Destroy (ball);
        }
        balls.Clear ();
        BallBorn ();
        isShooting = false;
    }


移動ではなくわざわざ破壊して作り直しているのには理由があります。今後実装予定のボールの個数を増やすようなアイテムを画面に配置する場合、一旦リセットしないとゲームオーバーのたびに個数が増えることになるからです。

foreachでの要素の削除

foreach()で一つずつ取り出してballに格納しDestroy()してます。そのあとClear()メソッドを呼んでいます。
ballsに格納されているのはオブジェクトそのものではなく、その参照です。参照先であるBallオブジェクトをDestroy()で削除してるのでballsリストの要素数は変わりません。なので最後にClear()でリセットする必要があります。
逆に言えば、ballsの要素数が変わらないからforeach()で処理できています。仮にballsに格納されているのがStringやintのようなプリミティブ型であれば、ループ中にリストのサイズが変わってしまいエラーになります。

リトライボタンタッチ時の修正

今のままでは「Retry ?」ボタンを押すとすぐさま玉が発射されます。そこを修正します。
ついでにゲームオーバー処理をまとめます。

    public class GameManager : MonoBehaviour {
    Vector3 staticOrigin;
    public GameObject ballBornPoint;
    public GameObject reBtn;
    public InputController inputCon;
    public BlockManager blockMgr;
    public BallManager ballMgr;
//   ゲームオーバーテキスト
    public Text gameOverText;

    void Start () {
        Init ();
    }
    public void Init(){
        GameObject Bwall = GameObject.Find ("Bwall");
        float _z = Bwall.transform.position.z + Bwall.transform.lossyScale.z/2 + 0.4f;
        ballBornPoint.transform.position = transform.position= new Vector3 (0, 0, _z);
        gameOverText.enabled = false;
        reBtn.SetActive(false);
        blockMgr.Init ();
        ballMgr.Init ();
        Invoke ("InputSet", 0.5f);
    }
    void InputSet(){
        inputCon.enabled = true;
    }

    public void GameOver(){
        inputCon.enabled = false;
        reBtn.SetActive(true);
        gameOverText.enabled = true;
    }
}


Invokeで遅延

InputControllerがアクティブ化するのを遅延することで即発射されるのを回避してます。
Invoke(呼び出すメソッド, 何秒後か)

それと、『GameOver』テキストの表示をBlockManagerから移動したのでBlockManager側の処理は削除しておきます。
public class BlockManager : MonoBehaviour {
 public GameObject[] waves;
 private GameObject wave;
 private float gameOverLine; 
 private float dengerLine;
//    public Text gameOverText;//削除
 public Text dengerText;
// 〜中略〜

 void CrossOverJodg(){
    bool isDenger = false;
    foreach (Transform child in wave.transform) {
        if (child.position.z <= gameOverLine) {
            dengerText.enabled = false;
//        gameOverText.enabled = true;// 削除
            GameObject.Find ("GameManager").GetComponent<GameManager>().GameOver();
            break;
       } else if (child.position.z <= dengerLine) {
            isDenger = true;
       }
    dengerText.enabled = isDenger;
    }
}

再生!


ちゃんと動いてますね!
これで一通りゲームとして通して動くようになりました。あとはタイトル画面を作ってゲームオーバー時の表示にタイトルバックを増やしたり、waveを増やしてステージ選択できるようにしたりとどこから手をつけてもいいようになりました。
この後はその辺を作りつつゲームらしくエフェクトや効果音などの盛り上げ要素を作っていきたいと思います。

ということで第1部完!

コメント