【Unity】ゲームマネージャークラスを作る：「はじめてのUnity」のブロック崩しを改造しながら学ぶ

# はじめに
JunShimuraさんの「[[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で](http://qiita.com/JunShimura/items/cbb0db8087a5cc75735e) ::(1)ステージ配置～(6)ブロックを手作業で並べる)」をよりゲームらしく改造する。

作成する記事は下記の通りです。

 * [スコアの表示](http://qiita.com/yaju/items/615b31e9f17e310642d6)
 * ゲームマネージャークラスを作る(この記事)
 * [ブロックをスクリプトで配置](http://qiita.com/yaju/items/5b54016c6574bc84c41d)
 * [音を鳴らす](http://qiita.com/yaju/items/32a6960a2e61267f8731)

# ゲームマネージャークラスとは
ゲームの進行に関する情報を管理します。
例えば、「タイトル -> ゲームスタート -> (ミスしたら)ゲームオーバー -> タイトル」 といった進行となります。

クラス名は人によってマネージャー(Manager)ではなくコントローラー(Controller)かも知れません。

ゲームマネージャークラスを作成する前に幾つか準備が必要となります。
順々にこなしてください。

# タイトルの表示
スコアの表示はScene上はカメラの影響で傾いて表示されますが、実際のGame上は垂直に表示されます。
![Unity_TitleScene.png](https://qiita-image-store.s3.amazonaws.com/0/14053/7bb444ae-f868-d75f-55c8-9a859fd7f51b.png)

![Unity_TitleGame.png](https://qiita-image-store.s3.amazonaws.com/0/14053/8eade7bf-b95a-5089-73ee-ab4e2055443f.png)

## テキストの追加
スコア表示の時とは違い、今回は間にパネル(Panel)を挟んでみます。パネルを挟むとゲーム画面全体に"もや"がかかったような表示になります。※靄(もや)とは霧(きり)よりも薄いものを指す。
参照:[【Unity開発】uGUIのPanelの使い方(非表示の方法等)【ひよこエッセンス】](http://hiyotama.hatenablog.com/entry/2015/07/01/090000)

Hierarchyの下にある[Create]>[UI]>[Canvas]を選択して下さい。次に[Create]>[UI]>[Panel]を選択して下さい。同様に[Create]>[UI]>[Text]を選択して下さい。
今のままだと`Panel`と`Text`が同一階層になっているので、`Text`を`Panel`の子階層にするため、`Text`をドラッグ＆ドロップで`Panel`に重ねてください。
![Unity_TitleAdd.gif](https://qiita-image-store.s3.amazonaws.com/0/14053/778b73a1-fac2-fdfe-c280-a10b894d1dd1.gif)

## Canvasの設定変更(Title GUI)
Canvasの[Inspector]タブにて名前を「Title GUI」にします。Canvasコンポーネントにある`Render Mode`を`Screen Space - Camera`に変更します。次に`Render Camera`のところにある○をクリックし、開いたリスト画面から`Main Camera(Camera)`を選択します。
このままだと、タイトルが壁の下に表示されてしまうので、カメラとGUIとの距離である`Plane Distance`を`18`に変更します。

![Unity_TitleGUI2.png](https://qiita-image-store.s3.amazonaws.com/0/14053/8a1af6ef-0d2a-0eb2-8b23-86ac388e81a0.png)

※`Render Mode`は`Screen Space - Camera`ではなくデフォルトの`Screen Space - Overlay`のままでもいいです。`Screen Space - Overlay`はゲームで使われる他のオブジェクトの描画が終わったあとにGUIが描画されます。つまりGUIが絶対に最前面になります。本来はこれが望ましいです。その場合、Scene上ではGUIが別の位置に配置されますが、Game上では重なって表示されます。CanvasをクリックすればScene上のGUI位置に移動するので、慣れればこちらを使う方がいいかも知れません。

## タイトルの作成(Title)
今回、タイトル部分は「Game Start」と「Game Clear」と「Game Over」の3つをスクリプトで文字と色を書き換えて使いまわします。

Title GUI配下のTextの[Inspector]タブにて名前を「Title」にします。
[Rect Transform]と[Scale]を下図のように変更します。今回は目立つように文字の大きさを3倍にしておきます。
次に`Text`を`Game Start`と入力します。`Font Style`を`Bold And Italic`、`Font Size`を`48`、`Alignment`を水平方向は中央寄せ／垂直方向は中央揃えにして、`Color`を緑(0,255,0)にします。

![Unity_Title2.png](https://qiita-image-store.s3.amazonaws.com/0/14053/66dda7e7-4634-d6a7-baaf-ae095227740f.png)

## タイトルに影やアウトラインを装飾する
そのままの文字だとそっけないので、影(Shadow)とアウトライン(Outline)のエフェクトを追加して装飾します。
参照:[UnityのGUIの基本的な作り方 uGUIのPanel、Button、Text、Imageの使い方](http://www.atmarkit.co.jp/ait/articles/1503/11/news045_5.html)

`Title`の[Inspector]タブの一番下にある[Add Compornent]ボタンをクリックして、UI>Effects>Shadowを追加します。同様にUI>Effects>Outlineを追加します。
影の大きさを変更したいので、`Shadow(Script)`の`Effect Distance`の`X`を`4`にします。

![Unity_TitleEffect.gif](https://qiita-image-store.s3.amazonaws.com/0/14053/25f45ecf-a48b-ae5f-28e7-f7644d5c18e0.gif)
表示結果(通常->影->アウトライン)
![Unity_TitleEffects.png](https://qiita-image-store.s3.amazonaws.com/0/14053/1f90272c-3e21-a064-5b19-6ec478a08952.png)

## シーン表示が変になったら
シーンの向きや拡大・縮小などの変更はアンドゥ(CTRL+Z)やリドゥ(CTRL+Y)が効かないので、元に戻す方法が分からなくなる時があります。
その場合、Sceneビュータブの右クリックメニューの[Add Tab]-> [Scene]で、新たなSceneビューを作成します。問題の起こっているSceneビューは右クリックメニューの [Close Tab] で閉じてしまいましょう。
参照:[【Unity】シーン表示上のシーンの向きに戸惑わない方法](http://qiita.com/yaju/items/f4829daeedd685888ef7)

# シーンの保存
現在、シーン名は初期値の「Untitled」になったままなので、ここらへんで名前を付けて保存しましょう。
メニューの[File]>[Save Scenes]を選択すると、保存ダイアログ画面が表示されるので「main」で保存してください。

# ラケットの位置をずらす
現在のラケットの位置が高めなので、少し下げましょう。
`Position`の`Z`を`-6.0`から`-8.5`に変更します。
![Unity_RacketZ.png](https://qiita-image-store.s3.amazonaws.com/0/14053/9ff572c2-2390-1bfe-2ef3-8c33d34d3467.png)

# ボールの位置をずらす
現在のボールの位置が高めなので、ラケットの乗せた感じにするように下げましょう。
`Position`の`Z`を`0.0`から`-7.5`に変更します。
![Unity_BallZ.png](https://qiita-image-store.s3.amazonaws.com/0/14053/e0d628cd-1725-8461-50ae-4dc8ff5844e8.png)

# ブロックのタグを追加
ブロックを色で分けるように今後はしていきたいのですが、その前にどの色でもブロックと判定されるように、タグを追加します。

Blockの[Inspector]タブのTagのところで`Untagged`をクリックして出るプルダウンから、`Add Tag...`を選びます。タグ&レイヤー画面(Tag & Layers)のTagの一覧の左にある`+`をクリックし`Block`を追加します。
※名前を間違えたら`-`をクリックします。この際に`(Removed)`となりますが、プロジェクトをリロードした時に一覧から削除されるので気にせず、`+`をクリックして再追加してください。
![Unity_BlockTagAdd.gif](https://qiita-image-store.s3.amazonaws.com/0/14053/c6382586-892f-2057-b26b-27de6095a764.gif)

タグ&レイヤー画面を閉じる方法が見当たらないので`Hierarchy`の`Block`を再選択します。[Inspector]タブのTagのプルダウンを選ぶと、`Block`が現れるので選択します。
![Unity_BlockTag.gif](https://qiita-image-store.s3.amazonaws.com/0/14053/79a48c7d-cc77-d7fb-33c5-24eccab3968e.gif)
※ブロックが複数ある場合、全てのTagを`Block`に変更してください。(複数選択で一気に変更可能)

# スクリプトの作成
先に空のゲームオブジェクトを作成します。
Hierarchyの下にある[Create]>[Create Empty]を選択して下さい。
![Unity_EmptyAdd.png](https://qiita-image-store.s3.amazonaws.com/0/14053/a970b646-8f3e-af8a-90f0-6237a355c724.png)
名前を`Manager`とします。`Position`はXYZを`0`にしておきます。
![Unity_Manager.png](https://qiita-image-store.s3.amazonaws.com/0/14053/cde176ca-8163-45f4-6f66-7182e49ad233.png)

`Manager`の[Inspector]タブの一番下にある[Add Compornent]ボタンをクリックして、Scriptアセットを作成、追加します。ファイル形式はcsを選びます。
![Unity_MangerScriptAdd.gif](https://qiita-image-store.s3.amazonaws.com/0/14053/028c8015-2863-a492-7d06-221cbbdabc11.gif)

ファイル名を`Manager`とします。そうすると、Assetsの中に「Manager.cs」が出来ています。
※先頭は英大文字です。間違えた場合、ファイル名と中身のクラス名の両方を修正してください。
![Unity_ManagerScript.png](https://qiita-image-store.s3.amazonaws.com/0/14053/272a1cd2-f096-a46a-ca28-bbd6e538eeda.png)

## Manger.csを編集する
Asset内の`Manger`スクリプト(C#のアイコン）をダブルクリックすると、エディタが開きます。
優先的に開かれるエディタは、多くの場合はMonoDevelopというソフトが起動されます。

少し長いのでコピー＆ペーストで入力してしまいましょう。
※MonoDevelopでペーストが効かないに時はMonoDevelopを閉じ直してください。

``` csharp:Manager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Manager : MonoBehaviour {


	// ゲームステート
	public enum GameState{
		Opening,
		Playing,
		Clear,
		Over
	}

	// 現在のゲーム進行状態
	public GameState currentState = GameState.Opening;
	// パネル
	private GameObject panel;
	// タイトル
	private GameObject title;
	// ラケット
	private GameObject racket;
	// ボール
	private GameObject ball;

	// テキスト
	private Text text;
	// ステージ
	private int stage = 1;

	// Use this for initialization
	void Start () {
		// Panelゲームオブジェクトを検索し取得する
		panel = GameObject.Find("Panel");		
		// Titleゲームオブジェクトを検索し取得する
		title = GameObject.Find("Title");		
		// ラケットゲームオブジェクトを検索し取得する
		racket = GameObject.Find("Racket");		
		// ボールゲームオブジェクトを検索し取得する
		ball = GameObject.Find("Ball");
		// テキスト
		text = title.GetComponent<Text> ();

		// オープニング
		GameOpening ();
	}
	
	// Update is called once per frame
	void Update () {
		// ゲーム中ではなく、Spaceキーが押されたらtrueを返す。
		if(currentState == GameState.Opening && Input.GetKeyDown (KeyCode.Space)) {
			dispatch (GameState.Playing);					
		}

		if (currentState == GameState.Playing) {
			// ゲームクリアーの判定
			if (GameObject.FindGameObjectsWithTag ("Block").Length == 0) {
				dispatch (GameState.Clear);					
			}
		}
	}

	// 状態による振り分け処理
	public void dispatch (GameState state){
		GameState oldState = currentState;

		currentState = state;
		switch (state) {
			case GameState.Opening:
				GameOpening ();
				break;
			case GameState.Playing:
				GameStart ();
				break;
			case GameState.Clear :
				GameClear ();
				break;
			case GameState.Over:
				if (oldState == GameState.Playing) {
					GameOver ();
				}
				break;
		}

	}

	// オープニング処理
	void GameOpening ()
	{
		currentState = GameState.Opening;

		// ボールの動作停止
		Time.timeScale = 0;

		// タイトル名のセット
		SetTitle ("Game Start", Color.green);

		// ラケットの初期位置をセット
		racket.transform.position = new Vector3 (0.0f, 0.0f, -8.5f);
		// ボールの初期位置をセット
		ball.transform.position = new Vector3 (0.0f, 0.0f, -7.5f);

	}

	// ゲームスタート処理
	void GameStart ()
	{
		// パネル非活性化
		panel.SetActive (false);

		// ボールの動作開始
		Time.timeScale = 1.0f;

		// ボールの初期化
		FindObjectOfType<Ball> ().Init ();
	}

	// ゲームクリアー処理
	void GameClear ()
	{
		stage++;

		// タイトル名のセット
		SetTitle ("Game Clear", Color.yellow);

		// 3秒後にオープニング処理を呼び出す
		Invoke("GameOpening", 3f);
	}

	// ゲームオーバー処理
	void GameOver ()
	{
		// タイトル名のセット
		SetTitle ("Game Over", Color.red);
		// ステージ初期値
		stage = 1;

		// ハイスコアの保存
		FindObjectOfType<Score> ().Save();

		// 3秒後にオープニング処理を呼び出す
		Invoke("GameOpening", 3f);
	}

	// オープニング処理
	void SetTitle (string message, Color color)
	{
		// タイトル名のセット
		text.text = message;
		text.color = color;
		// パネル活性化
		panel.SetActive (true);
	}
}
```

少し説明します。

* `Start`処理で使用するオブジェクトを予め格納しておくことで、都度オブジェクト検索するのを防いでいます。
* ブロックのタグ検索をして何も見つからない場合にゲームクリアーと判定しています。
* `Time.timeScale = 0`として、ボールの動きを停止しています。
* `dispatch`処理はpublic扱いにして他スクリプトから呼べるようにしています。
* タイトル表示有無はPanelを活性/非活性で実現させています。

## Ball.csを編集する
一番下にある壁にボールが当たったら、ミスとしたとしてゲームオーバーにします。
ボールの衝突判定処理(`OnCollisionEnter`)を追加します。

また、ゲームスタート時にボールの加速値を初期化したいため、`Start`にある処理を`Manager`スクリプトから呼べるようpublicを付けた`Init`処理を作成し、処理内部を移動させます。

``` csharp:Ball.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ball : MonoBehaviour {

	private float speed = 20.0f;    //これを追加

	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
		
	}

	void OnCollisionEnter(Collision collision) {
		//衝突判定
		if (collision.gameObject.name == "BottomWall") {
			// ゲームオーバー処理を呼ぶ
			FindObjectOfType<Manager>().dispatch(Manager.GameState.Over);
		}
	}

	public void Init(){
		// 加速値を初期化
		Rigidbody rd = this.GetComponent<Rigidbody> ();
		rd.velocity = Vector3.zero;
		rd.AddForce(
			(transform.forward + transform.right) * speed, 
			ForceMode.VelocityChange);
	}
}
```

# 実装を確認する
エディタ画面中央の実行ボタンを押してみましょう。
オープニング画面でスペースキーで押すとゲームスタートとなります。
ボールが一番下の壁に当たるとゲームオーバーになり、3秒後にオープニング画面に自動で戻ります。また、ブロックが全て無くなるとゲームクリアーになり、これも3秒後にオープニング画面に自動で戻ります。
![Unity_GameStart.png](https://qiita-image-store.s3.amazonaws.com/0/14053/0541ef9a-b15c-534c-c7a0-685f8339caa3.png)

すぐ気が付くと思いますが、実はブロックの初期化処理が出来ていません。
ブロックをスクリプトで配置する上でPrefab(プレハブ)を使いたいのですが、説明が長くなるので次回にします。

# 学んだこと
* Panelを使うことで背景に"もや"がかかるように表示できること
* 空のゲームオブジェクトを作成して管理するスクリプトを使うこと
* タグを使うことで複数種類があっても判定がタグ1つで済むこと
* マネージャクラスにて進行状況により処理を振り分けること

# 参照
* [UnityとBlenderハンズオン第7章](https://www.slideshare.net/yaju88/unityblender7)
* [UnityとBlenderハンズオン第8章](https://www.slideshare.net/yaju88/unityblender8)
* [UnityとBlenderハンズオン第9章](https://www.slideshare.net/yaju88/unityblender9)
* [UnityとBlenderハンズオン第10章](https://www.slideshare.net/yaju88/unityblender10-55356833)