Edited at

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

More than 1 year has passed since last update.


はじめに

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

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


ゲームマネージャークラスとは

ゲームの進行に関する情報を管理します。

例えば、「タイトル -> ゲームスタート -> (ミスしたら)ゲームオーバー -> タイトル」 といった進行となります。

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

ゲームマネージャークラスを作成する前に幾つか準備が必要となります。

順々にこなしてください。


タイトルの表示

スコアの表示はScene上はカメラの影響で傾いて表示されますが、実際のGame上は垂直に表示されます。

Unity_TitleScene.png

Unity_TitleGame.png


テキストの追加

スコア表示の時とは違い、今回は間にパネル(Panel)を挟んでみます。パネルを挟むとゲーム画面全体に"もや"がかかったような表示になります。※靄(もや)とは霧(きり)よりも薄いものを指す。

参照:【Unity開発】uGUIのPanelの使い方(非表示の方法等)【ひよこエッセンス】

Hierarchyの下にある[Create]>[UI]>[Canvas]を選択して下さい。次に[Create]>[UI]>[Panel]を選択して下さい。同様に[Create]>[UI]>[Text]を選択して下さい。

今のままだとPanelTextが同一階層になっているので、TextPanelの子階層にするため、Textをドラッグ&ドロップでPanelに重ねてください。

Unity_TitleAdd.gif


Canvasの設定変更(Title GUI)

Canvasの[Inspector]タブにて名前を「Title GUI」にします。CanvasコンポーネントにあるRender ModeScreen Space - Cameraに変更します。次にRender Cameraのところにある○をクリックし、開いたリスト画面からMain Camera(Camera)を選択します。

このままだと、タイトルが壁の下に表示されてしまうので、カメラとGUIとの距離であるPlane Distance18に変更します。

Unity_TitleGUI2.png

Render ModeScreen 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倍にしておきます。

次にTextGame Startと入力します。Font StyleBold And ItalicFont Size48Alignmentを水平方向は中央寄せ/垂直方向は中央揃えにして、Colorを緑(0,255,0)にします。

Unity_Title2.png


タイトルに影やアウトラインを装飾する

そのままの文字だとそっけないので、影(Shadow)とアウトライン(Outline)のエフェクトを追加して装飾します。

参照:UnityのGUIの基本的な作り方 uGUIのPanel、Button、Text、Imageの使い方

Titleの[Inspector]タブの一番下にある[Add Compornent]ボタンをクリックして、UI>Effects>Shadowを追加します。同様にUI>Effects>Outlineを追加します。

影の大きさを変更したいので、Shadow(Script)Effect DistanceX4にします。

Unity_TitleEffect.gif

表示結果(通常->影->アウトライン)

Unity_TitleEffects.png


シーン表示が変になったら

シーンの向きや拡大・縮小などの変更はアンドゥ(CTRL+Z)やリドゥ(CTRL+Y)が効かないので、元に戻す方法が分からなくなる時があります。

その場合、Sceneビュータブの右クリックメニューの[Add Tab]-> [Scene]で、新たなSceneビューを作成します。問題の起こっているSceneビューは右クリックメニューの [Close Tab] で閉じてしまいましょう。

参照:【Unity】シーン表示上のシーンの向きに戸惑わない方法


シーンの保存

現在、シーン名は初期値の「Untitled」になったままなので、ここらへんで名前を付けて保存しましょう。

メニューの[File]>[Save Scenes]を選択すると、保存ダイアログ画面が表示されるので「main」で保存してください。


ラケットの位置をずらす

現在のラケットの位置が高めなので、少し下げましょう。

PositionZ-6.0から-8.5に変更します。

Unity_RacketZ.png


ボールの位置をずらす

現在のボールの位置が高めなので、ラケットの乗せた感じにするように下げましょう。

PositionZ0.0から-7.5に変更します。

Unity_BallZ.png


ブロックのタグを追加

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

Blockの[Inspector]タブのTagのところでUntaggedをクリックして出るプルダウンから、Add Tag...を選びます。タグ&レイヤー画面(Tag & Layers)のTagの一覧の左にある+をクリックしBlockを追加します。

※名前を間違えたら-をクリックします。この際に(Removed)となりますが、プロジェクトをリロードした時に一覧から削除されるので気にせず、+をクリックして再追加してください。

Unity_BlockTagAdd.gif

タグ&レイヤー画面を閉じる方法が見当たらないのでHierarchyBlockを再選択します。[Inspector]タブのTagのプルダウンを選ぶと、Blockが現れるので選択します。

Unity_BlockTag.gif

※ブロックが複数ある場合、全てのTagをBlockに変更してください。(複数選択で一気に変更可能)


スクリプトの作成

先に空のゲームオブジェクトを作成します。

Hierarchyの下にある[Create]>[Create Empty]を選択して下さい。

Unity_EmptyAdd.png

名前をManagerとします。PositionはXYZを0にしておきます。

Unity_Manager.png

Managerの[Inspector]タブの一番下にある[Add Compornent]ボタンをクリックして、Scriptアセットを作成、追加します。ファイル形式はcsを選びます。

Unity_MangerScriptAdd.gif

ファイル名をManagerとします。そうすると、Assetsの中に「Manager.cs」が出来ています。

※先頭は英大文字です。間違えた場合、ファイル名と中身のクラス名の両方を修正してください。

Unity_ManagerScript.png


Manger.csを編集する

Asset内のMangerスクリプト(C#のアイコン)をダブルクリックすると、エディタが開きます。

優先的に開かれるエディタは、多くの場合はMonoDevelopというソフトが起動されます。

少し長いのでコピー&ペーストで入力してしまいましょう。

※MonoDevelopでペーストが効かないに時はMonoDevelopを閉じ直してください。


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処理を作成し、処理内部を移動させます。


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

すぐ気が付くと思いますが、実はブロックの初期化処理が出来ていません。

ブロックをスクリプトで配置する上でPrefab(プレハブ)を使いたいのですが、説明が長くなるので次回にします。


学んだこと


  • Panelを使うことで背景に"もや"がかかるように表示できること

  • 空のゲームオブジェクトを作成して管理するスクリプトを使うこと

  • タグを使うことで複数種類があっても判定がタグ1つで済むこと

  • マネージャクラスにて進行状況により処理を振り分けること


参照