第10回

タイトルを付ける

プロジェクトの準備

  1. このページは 2Dシューティングゲーム を初心者用に説明したものです。
    ダウンロードした「第09回プロジェクト」から Assets/Scene/Stage.unity のダブルクリックで起動します。
    Script にエラーがあるらしく、このままでは動かないのでエラーを修正して下さい。(前回の修正も参考にして下さい)
    .velocity が使われている行を次のように修正して下さい。
    【修正前】
    rigidbody2D.velocity =  ・・・(任意の文字列)
    
    【修正後】
    GetComponent<Rigidbody2D>().velocity = ・・・(任意の文字列)
    
  2. Background.cs の次の行を修正して下さい。
    【修正前】
        renderer.sharedMaterial.SetTextureOffset ("_MainTex", offset);
    
    【修正後】
        GetComponent<Renderer>().sharedMaterial.SetTextureOffset ("_MainTex", offset);
    
  3. Player.cs の次の行を修正して下さい。
    【修正前】
        // ショット音を鳴らす
        audio.Play();
    
    【修正後】
        // ショット音を鳴らす
        GetComponent<AudioSource>().Play();
    
  4. 修正が終わるとエラーが消えて実行可能になります。
    実行を確認してメニューの File→Save Scene as から [Main] の名前で保存して下さい。

Titleを表示

  1. Title の表示は Game Title に掲載しています。
    メニューの GameObject/Create Empty から Title を作成します。
    Title の位置は Inspector から (X:0 Y:0 Z:0) として下さい。
  2. もう一つ空のゲームオブジェクト Shooting Game を作成し、Title の子要素とします。
    Shooting Game の位置は (X:0.5 Y:0.65 Z:0) とします。
    メニューの Component→Rendering→GUI Text を選択します。
    Text に「Shooting Game」とタイプします。
    Anchor に「upper center」を選びます。
    Asset/Fonts フォルダの SAM_5C_27TRG_ を Font へ格納して、Font Size を「60」にします。
  3. Shooting Game ゲームオブジェクトを Duplicate して、名前を Press X とします。
    Press X の位置を Y:0.45 にして、Textに「Press X」をタイプします。
    画面にタイトル文字が表示されます。

マネージャークラス

  1. ゲームをコントロールするマネージャークラスを作成します。
    ヒエラルキービューの Player の位置を (X:0 Y:-2.5 Z:0) にして、プレハブを更新してから Player ゲームオブジェクトを削除します。
  2. メニューの GameObject/Create Empty から Manager を作成します。
  3. Asset/Scripts/ に Manager.cs を作成して、ここから Title や Player を呼び出します。
    X キーが押されたらゲームスタートです。
    using System.Collections;
    using System.Collections.Generic;
    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;
        }
    }
    
  4. Manager.cs を Manager にアタッチして下さい。
    Manager の Inspector を表示して、Player に Asset/Prefabs から Player をドラッグして設定します。
  5. Player が撃墜されたとき Player.cs からマネージャー(GameOverメソッド)を呼び出します。
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    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);
            }
        }
    } 
    
  6. Emitter.cs ではゲームが始まるまで待機します。
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    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;
                }
    
            }
        }
    } 
    

スクリプトの実行順

  1. スクリプトの実行順によっては Manager.cs で参照するはずの title を取得する前に Emitter.cs などで title にアクセスしようとしてしまいます。
    この時に NullReferenceException が発生してしまうかもしれません。
    この解決方法としてスクリプトの実行順を管理する「Script Execution Order」を利用します。
  2. メニューの Edit→Project Settings→Script Execution Order を選択することで MonoManager がインスペクターに表示されます。
    元々スクリプトの実行順は保証されているわけではなく、ユーザーは実行順を把握することは出来ません。
    Inspector の「+」ボタンを押して Manager を選択します。
    実行タイミングを表すフィールドを「Default Time」の上に移動させます。
    これで全スクリプトで一番最初に実行されるのは Manager.cs となりました。
    Apply ボタンをクリックして更新して下さい。

  3. メニューの File→Build Settimgs から PC, Mac & Linux Standalone を選びます。
    Windows, x86 を選んで Build ボタンから Test の名前でゲームを書き出します。
    作成された Test.exe をダブルクリックで実行します。
    640*480 を選んで Windows モードをチェックします。(チェックを忘れると強制終了しなけれならない)
プロジェクトの詳細は 2Dシューティングゲーム から 「第10回 タイトルを付ける」を参照して下さい。
第10回 の完成したプロジェクトは、後部の「今回のプロジェクトファイルをダウンロード」からダウンロードすることが出来ます。

[Next Chapter ↓] Shooting-11
[Previous Chapter ↑] Shooting-09

前田稔の超初心者のプログラム入門
超初心者のプログラム入門(Unity)