タイトルを付ける

確認済のバージョン: 5.5

-

難易度: 初級

ゲームを開始する前にタイトル画面を表示します。

10.1 タイトルの表示

空のゲームオブジェクトを作成し、名前をTitleとします。Titleゲームオブジェクトの位置はX 0 Y 0 Z 0とします。

次に、もう一つ空のゲームオブジェクトを作成し、名前をShooting Gameとして、Titleの子要素とします。名前を変更したら、メニューのComponent -> Rendering -> GUI Textを選択し、GUIテキストを作成します。

Titleの子要素としたShooting Gameのパラメータを設定していきます。 Shooting Game位置はX 0.5 Y 0.65 Z 0とします。 Textを「Shooting Game」Anchorを「upper center」FontsフォルダのSAM_5C_27TRG_Fontへ格納、Font Sizeを「60」とします。


図10.1のように表示されているはずです。


図10.1:

次にShooting GameゲームオブジェクトをDuplicateして、名前をPress Xとします。 Shooting Gameゲームオブジェクトと変更する部分は位置のYを0.45Textを「Press X」になります。


図10.2のように表示されれば問題ありません。


図10.2:

10.2 タイトル -> ゲームスタート ( -> 死んだら ) -> タイトル のマネージャークラスを作る

タイトル表示中はプレイヤーやWaveの作成を行わないようにします。

Playerゲームオブジェクトの削除

まずはPlayerの位置を X 0 Y -2.5 Z 0にしてプレハブを更新して、Playerゲームオブジェクトを削除してください。

Managerの作成

空のゲームオブジェクトを作成し、名前をManagerとします。 次にManager.csScriptsフォルダの中に作成します。

Manager.cs

Code snippet

using UnityEngine;

public class Manager : MonoBehaviour
{
    // Playerプレハブ
    public GameObject player;

    // タイトル
    private GameObject title;

    void Start ()
    {
        // Titleゲームオブジェクトを検索し取得する
        title = GameObject.Find ("Title");
    }

    void Update ()
    {
        // ゲーム中ではなく、Xキーが押されたらtrueを返す。
        if (IsPlaying () == false && Input.GetKeyDown (KeyCode.X)) {
            GameStart ();
        }
    }

    void GameStart ()
    {
        // ゲームスタート時に、タイトルを非表示にしてプレイヤーを作成する
        title.SetActive (false);
        Instantiate (player, player.transform.position, player.transform.rotation);
    }

    public void GameOver ()
    {
        // ゲームオーバー時に、タイトルを表示する
        title.SetActive (true);
    }

    public bool IsPlaying ()
    {
        // ゲーム中かどうかはタイトルの表示/非表示で判断する
        return title.activeSelf == false;
    }
}

もし NullReferenceException が発生してしまったら?

スクリプトの実行順によっては Manager.cs で参照するはずの title を取得する前に Emitter.cs などで title にアクセスしようとしてしまいます。この時に NullReferenceException が発生してしまうかもしれません。この解決方法としてスクリプトの実行順を管理する「Script Execution Order」を利用します。

メニューの Edit -> Project Settings -> Script Execution Order を選択することで MonoManager がインスペクターに表示されます。元々スクリプトの実行順は保証されているわけではなく、ユーザーは実行順を把握することは出来ません。その状態が「Default Time」と表記されている部分です。

MonoManagerでは「Default Time」から特定のスクリプトを抜き出して実行タイミングを指定することが出来ます。

「+」ボタンを押して Manager を選択してみましょう。

すると、Managerの実行タイミングを表すフィールドが追加されるので、そのフィールドを摘んで「Default Time」の上に移動させます。

Applyを押して適用します。これで全スクリプトで一番最初に実行されるのは Manager.cs となりました。

マネージャーにPlayerプレハブの設定

10.3 他のスクリプトからマネージャーを呼び出す

Player.csを修正します。

Player.cs

Code snippet

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour
{
    // Spaceshipコンポーネント
    Spaceship spaceship;
    
    IEnumerator Start ()
    {
        // Spaceshipコンポーネントを取得
        spaceship = GetComponent<Spaceship> ();
        
        while (true) {
            
            // 弾をプレイヤーと同じ位置/角度で作成
            spaceship.Shot (transform);
            
            // ショット音を鳴らす
            GetComponent<AudioSource>().Play();
            
            // shotDelay秒待つ
            yield return new WaitForSeconds (spaceship.shotDelay);
        }
    }
    
    void Update ()
    {
        // 右・左
        float x = Input.GetAxisRaw ("Horizontal");
        
        // 上・下
        float y = Input.GetAxisRaw ("Vertical");
        
        // 移動する向きを求める
        Vector2 direction = new Vector2 (x, y).normalized;
        
        // 移動の制限
        Move (direction);
        
    }
    
    // 機体の移動
    void Move (Vector2 direction)
    {
        // 画面左下のワールド座標をビューポートから取得
        Vector2 min = Camera.main.ViewportToWorldPoint(new Vector2(0, 0));
        
        // 画面右上のワールド座標をビューポートから取得
        Vector2 max = Camera.main.ViewportToWorldPoint(new Vector2(1, 1));
        
        // プレイヤーの座標を取得
        Vector2 pos = transform.position;
        
        // 移動量を加える
        pos += direction  * spaceship.speed * Time.deltaTime;
        
        // プレイヤーの位置が画面内に収まるように制限をかける
        pos.x = Mathf.Clamp (pos.x, min.x, max.x);
        pos.y = Mathf.Clamp (pos.y, min.y, max.y);
        
        // 制限をかけた値をプレイヤーの位置とする
        transform.position = pos;
    }
    
    // ぶつかった瞬間に呼び出される
    void OnTriggerEnter2D (Collider2D c)
    {
        // レイヤー名を取得
        string layerName = LayerMask.LayerToName(c.gameObject.layer);
        
        // レイヤー名がBullet (Enemy)の時は弾を削除
        if( layerName == "Bullet (Enemy)")
        {
            // 弾の削除
            Destroy(c.gameObject);
        }
        
        // レイヤー名がBullet (Enemy)またはEnemyの場合は爆発
        if( layerName == "Bullet (Enemy)" || layerName == "Enemy")
        {
            // Managerコンポーネントをシーン内から探して取得し、GameOverメソッドを呼び出す
            FindObjectOfType<Manager>().GameOver();
            
            // 爆発する
            spaceship.Explosion();
            
            // プレイヤーを削除
            Destroy (gameObject);
        }
    }
}

Emitter.csを修正します。

Emitter.cs

Code snippet

using UnityEngine;
using System.Collections;

public class Emitter : MonoBehaviour
{
    // Waveプレハブを格納する
    public GameObject[] waves;

    // 現在のWave
    private int currentWave;

    // Managerコンポーネント
    private Manager manager;

    IEnumerator Start ()
    {

        // Waveが存在しなければコルーチンを終了する
        if (waves.Length == 0) {
            yield break;
        }

        // Managerコンポーネントをシーン内から探して取得する
        manager = FindObjectOfType<Manager>();

        while (true) {

            // タイトル表示中は待機
            while(manager.IsPlaying() == false) {
                yield return new WaitForEndOfFrame ();
            }

            // Waveを作成する
            GameObject g = (GameObject)Instantiate (waves [currentWave], transform.position, Quaternion.identity);

            // WaveをEmitterの子要素にする
            g.transform.parent = transform;

            // Waveの子要素のEnemyが全て削除されるまで待機する
            while (g.transform.childCount != 0) {
                yield return new WaitForEndOfFrame ();
            }

            // Waveの削除
            Destroy (g);

            // 格納されているWaveを全て実行したらcurrentWaveを0にする(最初から -> ループ)
            if (waves.Length <= ++currentWave) {
                currentWave = 0;
            }

        }
    }
}

さて、ゲームを再生してみてください。タイトル表示中はプレイヤーも敵も出てませんか?Xキーを押すとゲームが開始されますか?