ミニゲーム作成3　ゲームマネージャー基礎部分作成(GapDush_3)

こんにちは．

↓前の記事

https://qiita.com/tdtiger/items/0d3481d1bf80ea31db6b

講義やらなんやらのせいで長らく手を付けられておらず更新できてませんでした．
今回取り上げるものの作成自体はできていたのですが，記事を書く時間がなかなか取れず．．．
すみません．

今回は，ゲームマネージャーの基礎となる部分を作っていこうと思います．
では早速．

※今回から，極力プログラムの中にコメントを書くようにしたので，本文での説明を少し削って(いこうという意識をして)います．

## ゲームマネージャー
まずは，ゲームマネージャーの機能を記述するためのスクリプトと，それをアタッチするためのオブジェクトを作成します．
Playerスクリプトを作成したときと同じように，Scriptsフォルダ内で右クリック → Create → C# Scripts でスクリプトファイルを新規作成し，名前を変更します．(ここでは，"GameManager"としました．)
その後，ヒエラルキービュー内で右クリック，あるいは”+”ボタンをクリックして新しいオブジェクトを作成します．
ここで，どのようなオブジェクトを作成するかなのですが，ゲームマネージャーはゲームの進行を管理するためだけのものであり，ユーザからその実体が見えている必要は全くありません．そのため，ここでは Create Empty を選択し，空のゲームオブジェクトを作成します．これにより，実際の画面には何も表示されず，スクリプトの貼り付け場所としてのみ機能するオブジェクトが作成できます．
実際，ヒエラルキービューには表示されているのに，ゲーム画面には何も表示されていないことが確認できると思います．
名前を変更し，スクリプトをアタッチしておきましょう．(ここでは"GameManager"としました．)

それでは，スクリプトの記述に移っていきます．
``` C#:GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class GameManager : MonoBehaviour
{
    // 制限時間
    private float timeLimit = 60.0f;
    public float TimeLimit {
        get {
            return timeLimit;
        }
        set {
            timeLimit = Mathf.Max(0, value);
        }
    }

    // 残り時間
    private float timeRemaining;

    [SerializeField]
    private GameObject timerText;
    [SerializeField]
    private GameObject gameOverUI;
    [SerializeField]
    private GameObject resultText;

    // Start is called before the first frame update
    void Start()
    {
        // 残り時間を初期化し，結果表示画面を非アクティブ化
        timeRemaining = timeLimit;
        gameOverUI.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
        // 時間を進め，タイマーを更新
        timeRemaining -= Time.deltaTime;
        timerText.GetComponent<TextMeshProUGUI>().text = "Time : " + Mathf.CeilToInt(timeRemaining).ToString();

        // 残り時間0でクリア
        if (timeRemaining <= 0){
            GameClear();
        }
    }

    private void GameClear(){
        // 結果表示画面をアクティブにし，"GAME CLEAR!"と表示
        gameOverUI.SetActive(true);
        resultText.GetComponent<TextMeshProUGUI>().text = "GAME CLEAR!";
        Time.timeScale = 0;
    }

    public void GameOver(){
        // 結果表示画面をアクティブにし，"GAME OVER..."と表示
        gameOverUI.SetActive(true);
        resultText.GetComponent<TextMeshProUGUI>().text = "GAME OVER...";
        Time.timeScale = 0;
    }
}
```

まず，4，5行目の記述です．
Playerスクリプトを作成した際は特に`using`文を追加する必要はなかったのですが，今回はUIの操作をするために`using UnityEngine.UI`，テキスト操作のために`using TMPro`という記述を追加しています．
続いて変数の宣言部分です．
`timeLimit`はその名前が表す通りゲームの残り時間を表す変数であり，60秒に設定しています．
そのすぐ下で宣言されている`TimeLimit`は，`timeLimit`のプロパティです．
プロパティについて私は，「安全に他のスクリプトと変数を共有するためのもの」と認識しています．スクリプト間での変数を共有を行うための最も単純な方法は，`public`変数として宣言することでしょう．
しかし，それは裏を返せば「どこからでも変数の値を直接変更できてしまう」ということであり，不具合の原因となりかねません．そこで，プロパティを用いることで`get`，`set`を介しての操作ができるようになります．
さらに重要な点として，`get`，`set`内，つまり値が参照されたり代入されるときの振る舞いを記述できるという点があります．
このスクリプトの例では，`set`において`timeLimit = Mathf.Max(0, value);`と記述されています．これにより，`timeLimit`に値が代入される際に，その値と0を`Mathf.Max()`に渡すことで，`timeLimit`の値が負となることを防ぐことができます．(`Mathf.Max()`関数は，渡された引数の内最大のものを返す関数．)

https://docs.unity3d.com/ja/current/ScriptReference/Mathf.Max.html

また，値への制約をプロパティ側で記述しておくことで，プログラムの本体側の記述を明快にすることも期待できます．
以下に，変数の用途をまとめた表を記します．
| 変数名 | 用途 |
|:-|:-|
| timeLimit |  制限時間(初期値設定用) |
| timeRemaining | 制限時間(更新用)  |
| timerText | 制限時間表示用テキスト  |
| gameOverUI | ゲームクリア，ゲームオーバー時の結果表示用画面  |
| resultText  | gameOverUI上に表示するテキスト  |

表の下から3つの変数については，`SerializeField`されていますが，これらの設定については後ほど述べます．

続いて，各関数の内容について説明します．
まず，`Start()`関数です．残り時間を初期化し，`gameOverUI`を非表示状態にします．

`Update()`関数は，残り時間を`Time.deltaTime`分だけ減らし，残り時間の表示を更新します．ここで，`Time.deltaTime`は，前フレームからの経過時間を表す変数です．
その後，`if`文を用いて，残り時間が0なっているのかをチェックし，もしも0になっていればゲームクリアと判定し，専用の`GameClear()`関数を呼び出します．

`GameClear()`関数は，`GameOverUI`を表示し，そこに"GAME CLEAR!"と表示します．ここでの文字は仮に決めているものなので，制作の過程で変化するかもしれません．`GameOver()`関数もほとんど同じ構造で，表示する文字が違うだけになっています．

## オブジェクトの作成
まず，ヒエラルキービュー内で右クリック → UI → Canvas でCanvasオブジェクトを作成します．さらにもう一度ヒエラルキービュー内で右クリック → UI → PanelでPanelオブジェクトを作成して，先程作成したCanvasオブジェクトの子オブジェクトとします．(名前は"GameOverUI"としました．)
さらに，UI → Text-TextMeshProでTextオブジェクトを2つ作成し，1つを"Timer"，もう1つを"Message"という名前にします．そして，TimerをCanvasの子オブジェクトに，MessageをGameOverUIの子オブジェクトにします．以下の画像のような構造になっていればOKです．
![struct.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3922668/0f16686a-854c-30d4-c03e-19da0eeeca89.png)
そして，先程作成したオブジェクトたちをGameManagerのインスペクタービューから，適切な位置(変数)に設定してやると一先ずは完成です．
![manage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3922668/ccfe3bc2-92a3-db1f-75ad-dea40943a3ae.png)
名前揃えておいた方がわかりやすかった気がする...

## まとめ
今回はゲームマネージャーの基礎の部分を作成しました．
ここまでの状態で，プレイヤーが左右に動き，60秒経過するとゲームクリアの画面が表示されるようにはなっているはずです．

ちょっと，記事を書くモチベーションが下がってきているので，頑張り時．

このシリーズの一本目の記事で，「できるだけ詳しく説明したい」みたいなこと言った気がしますが，調べたらなんぼでも詳しい記事は出てくるわけだし，記事をもっと力抜いて進捗報告の場みたいにして，完成させることを最優先にした方がいいのだろうか．
制作に飽きたわけではなく，記事を書くのに時間をとられている&そもそも時間があまりないという感じなので．．．

↓次の記事
作成中