game
Unity3D
Unity
初心者
チュートリアル

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

はじめに

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つで済むこと
  • マネージャクラスにて進行状況により処理を振り分けること

参照