「Flappy Daruma」を作ってみよう!⑤画面の追加

今回の内容を整理しよう!

前回の内容を復習しよう

前回はプレイヤーが操作するダルマのオブジェクトの実装をしました。
入力に応じて連続でジャンプする方法や、障害物にぶつかったときに呼び出されるイベント関数の中でゲームオーバーに切り替えていることを紹介しました。

今回進める作業を確認しよう!

今回はタイトル画面とゲームオーバー画面を作り、ゲーム全体の流れを作ります。
また、配置したUIを動かす方法や、SNSにプレイ結果をシェアする方法、スコアに応じてメダルを表示する際の難易度の調整の仕方などもご紹介します。

前提知識を学ぼう!

ボタンやテキストはキャンバスに配置する

タイトル画面を作るために、今まで作ってきたバトルシーンとは別に、[File]→[New Scene]メニューで新たにタイトルシーンを作りました。

タイトルシーンでは、下図のようにキャンバスにイメージやボタンのUI(ユーザーインターフェース)が配置されています。
キャンバスには、画面全体に表示したり、今回のようにカメラの範囲全体にあわせて表示するなどの3つのRender Modeが用意されています。
今回選択したScreen Space -CameraというRender Modeでは、基準となるRender Cameraをシーンの中から割り当てる必要があります。

キャンバスに配置されたイメージやボタンのUIは標準で用意されていて、ヒエラルキーウィンドウなどで右クリックすると表示されるポップアップの[Create]→[UI]メニューの中から[Image]や[Button]メニューを選択することでシーンに追加できます。
ボタンなどを追加したときに、もしもキャンバスがない場合は自動的に作成されます。

また、バトルシーンのスコアの表示ではテキストのUIを使用しています。

これらのUIの設定、例えば表示する文字列や色などは、インスペクターウィンドウや、実行中のスクリプトから変更することができます。

ボタンの場合は、インスペクターウィンドウのOn Click()で、シーン上のオブジェクトの持つコンポーネントの関数を指定することで、クリック時の動作を設定できます。

スクリプトからもこの設定をすることができますが、上図のようにインスペクターウィンドウから設定するほうが楽でしょう。

テキストの値もインスペクターウィンドウで簡単に設定できますが、時間やスコアのように実行中に変化するテキストの場合は、次のようにスクリプトから変更することもできます。

    /// 現在のスコアです。
    public int score_ = 0;

    /// プレイ中にスコアを表示するテキストを設定します。
    public UnityEngine.UI.Text uiTextScore_;

    /// スコアを増やします。
    public void addScore()
    {
        // スコアアップの効果音を再生します。
        Util.PlayAudioClip(acScoreUp_, Vector3.zero, 1f);
        // スコアを増やし、表示に反映します。
        score_ += 1;
        this.uiTextScore_.text = string.Format("Score {0}", score_);
    }

uiTextScore_変数には、シーン上のスコアを表示するテキストのオブジェクトを指定します。
変数にテキストを指定するには、uiTextScore_を持つBattleSceneオブジェクトを選択し、インスペクターウィンドウにBattleSceneコンポーネントを表示します。
その後、シーン上のテキストのオブジェクトをuiTextScore_のフィールドにドラッグ&ドロップしたり、選択リストから選択して割り当てましょう。

これによりBattleScene.csのスクリプトの中からuiTextScore_変数経由でバトルシーンの画面のスコア表示を変更することができるようになります。

UIやuGUIに関しては、「0から2Dアクションバトルゲームを作ろう!⑥UIを表示してみよう」に詳しく書かれていますので参照してください。

SceneManagerでシーンを切り替える

さきほどのタイトル画面で[START]ボタンがクリックされたときに呼び出すように設定されたTitleScene.csのOnButtonStart関数では、以下のスクリプトによってタイトルシーンからバトルシーンへ切り替える処理をしています。

    /// 次に遷移するバトルシーンの名前です。
    public string nameBattleScene_ = "BattleScene";

    /// START ボタンが押されたときに呼び出される関数です。
    public void OnButtonStart()
    {
        // ボタンの効果音を再生します。
        Util.PlayAudioClip(acButton_, Vector3.zero, 1f);

        // バトル画面を表示するため、バトル用のシーンを読み込みます。
        // シーンは Unity の File > Build Settings の Scenes In Build に登録しておきます。
        UnityEngine.SceneManagement.SceneManager.LoadScene(nameBattleScene_);
    }

LoadScene関数ではシーン名などを指定して、シーンを切り替えたり追加することができます。
このときに指定するシーン名はあらかじめBuild SettingsダイアログのScenes In Buildのリストにシーンのアセットをプロジェクトウィンドウからドラッグ&ドロップするなどして追加しておきましょう。
Build Settingsダイアログは[File]→[Build Settings…]メニューで開くことが出来ます。

SetActiveでオブジェクトの有効・無効を切り替える

シーンを切り替えるのではなく、シーンの上にダイアログのようなものを追加で表示する簡単な方法として、隠しておいたUIのオブジェクトを表示する方法があります。
例えば、ゲームオーバーになった直後に、プレイ中のシーンの上に結果を表示するUIのオブジェクトを表示したりするときに利用できます。

具体的には、オブジェクトのアクティブ・非アクティブを切り替えるSetActive関数により、オブジェクトがアクティブであるかどうかを切り替えます。

    /// ゲームオーバー時に表示するパネルを設定します。初期は無効になっています。
    public GameObject gameoverPanel_;

    /// ゲームオーバーの段階に遷移します。(一部抜粋)
    public void ChangePhaseGameOver()
    {
        // 結果を表示するパネルを有効にして、設定してきたメダルの画像やスコアのテキストを表示します。
        gameoverPanel_.SetActive(true);
    }

gameoverPanel_はGameObject型の変数で、ゲームオーバー時に表示するUIです。
シーン上のこのパネルのUIのオブジェクトを、インスペクターウィンドウからこのgameoverPanel_のフィールドに設定して使用しています。

ゲームオーバー時のパネルはプレイ開始時は非アクティブな状態なので、表示もアニメーションもしません。
ゲームオーバーになり、上記のスクリプトのSetActive関数によりパネルがアクティブになると、上に飛び出るなどのアニメーションとともに表示されます。

タイトル画面を作る

ボタンとスクリプトの機能を関連付ける

さきほどのuGUI の使い方を参考にキャンバスにボタン、テキスト、イメージなどのUIを配置していきましょう。

ボタンのOnClickイベントにはシーンを読み込んだり、アプリについての説明パネルの表示非表示を切り替える関数を設定しておきます。

それぞれの呼び出す関数はTitleScene.cs(一部抜粋)で次のように定義されています。

/// タイトルシーンの管理をします。
public class TitleScene : MonoBehaviour {
    /// ボタン音を設定します。
    public AudioClip acButton_;

    /// ABOUT ボタンが押されたときに表示する、説明・クレジットが記載されたパネルを設定します。
    public GameObject panelAbout_;

    /// 次に遷移するバトルシーンの名前です。
    public string nameBattleScene_ = "BattleScene";

    /// START ボタンが押されたときに呼び出される関数です。
    public void OnButtonStart()
    {
        // ボタンの効果音を再生します。
        Util.PlayAudioClip(acButton_, Vector3.zero, 1f);

        // バトル画面を表示するため、バトル用のシーンを読み込みます。
        // シーンは Unity の File > Build Settings の Scenes In Build に登録しておきます。
        UnityEngine.SceneManagement.SceneManager.LoadScene(nameBattleScene_);
    }

    /// ABOUT ボタンが押されたときに呼び出される関数です。
    public void OnButtonAbout()
    {
        // ボタンの効果音を再生します。
        Util.PlayAudioClip(acButton_, Vector3.zero, 1f);
        // About パネルを表示します。
        panelAbout_.SetActive(true);
    }

    /// ABOUT パネルの CLOSE ボタンが押されたときに呼び出される関数です。
    public void OnButtonAboutClose()
    {
        // ボタンの効果音を再生します。
        Util.PlayAudioClip(acButton_, Vector3.zero, 1f);
        // About パネルを無効にして非表示にします。
        panelAbout_.SetActive(false);
    }

    /// ベストスコアの記録を消去します。
    /// デバッグ用です。
    public void OnButtonDebugPlayerPrefsClear()
    {
        // ボタンの効果音を再生します。
        Util.PlayAudioClip(acButton_, Vector3.zero, 1f);
        // このゲーム独自のセーブデータを全て破棄します。
        PlayerPrefs.DeleteAll();
    }
}

これによりタイトル画面のボタンに、バトルシーンに切り替えたりするゲームの機能を持たせることができました。
バトルシーンのゲームオーバー時も同様に、[TITLE]ボタンでタイトルシーンに切り替えることで、Flappy Darumaのゲーム画面の遷移が完成します。

ボタンをふわふわ動かす

続いて配置したボタンなどのUIに動きをつけて雰囲気をよりゲームらしくしてみましょう。

Flappy Darumaではボタン、テキスト、ダルマのスプライト画像などいくつかのオブジェクトに、アニメーションが設定され下の動画のように動いています。
今回は、その一つである[START]ボタンのアニメーションの設定方法を紹介しましょう。

今回[START]ボタンにつけるアニメーションは、以下の動画のように、上下に移動を繰り返すものです。

まずは、アニメーションを実現するコンポーネントとして、StartButtonのオブジェクトに、AnimationClipアセットのついたAnimatorコンポーネントを追加しましょう。
Animatorはさまざまなアニメーションを切り替えて制御するもので、AnimationClipはひとつのアニメーションを記録したデータです。

追加するためには、初めに、ヒエラルキーウィンドウやシーンビューでアニメーションをつけたいオブジェクト、今回はStartButtonのオブジェクトを選択します。
そして、[Window]→[Animation]→[Animation]メニューからアニメーションウィンドウを表示し、下図のとおり[Create]ボタンをクリックし保存ファイルを指定するだけです。

今回は、StartButtonMoveというファイル名のAnimationClipを作成しました。
そうすると、下図のようにアニメーションウィンドウにAnimationClipのファイル名が表示された画面が表示されます。

アニメーションウィンドウの[Add Property]ボタンからは、アニメーションさせたい項目をオブジェクト内から選択することができます。
例えば、StartButtonオブジェクトの持つButtonコンポーネントや、下位に持っているTextオブジェクトのTextコンポーネントなどです。
今回は位置を移動させるため、RectTransformコンポーネントの位置情報をアニメーションの対象にします。

今述べたように[Add Property]ボタンから位置に関するいくつかの情報を選んで、時間(フレーム)ごとに値をインスペクターウィンドウで変更してアニメーションを作ることもできますが、今回はシーン上で直接動かしてアニメーションを作る方法を紹介しましょう。

アニメーションウィンドウには、赤い丸印の録画ボタンがあり、シーンビューやインスペクターウィンドウで対象のオブジェクトを変化させると、それらを全て記録してくれます。
もちろん、インスペクターウィンドウで値を微調整して変化させたものを記録することもできます。

今回はStartButtonオブジェクトの位置を、0:30の時間(フレーム)で記録し、0:00と1:00の時間(フレーム)では初期位置を設定しています。
1:00の時間(フレーム)には0:00のダイヤの印を選択して青くした後にCtrl+Cでコピーし、1:00を選択したあとにCtrl+Vでコピーした0:00の状態を貼り付けています。

アニメーションをひととおり記録させたら、録画ボタンをもう一度押して編集を終え、右向きの三角形の形をした再生ボタンで動きを確認してみましょう。

これで、ボタンが上下に動くアニメーションができました。
Unityエディタ上で実行するとボタンが上下に動くアニメーションを確認できます。

カメラを調整してアスペクト比を維持する

テキストやボタンなどのUIはアンカーを調整することで、さまざまな画面解像度・アスペクト比(縦横の比率)に対応しています。
しかし、キャンバス上のUIではない、ダルマ・背景・地面などのスプライト画像は、比率を維持できず画面からはみでたり、見えないはずの場所まで表示されてしまいます。
これについては、実行中にゲームビューの左上の画面サイズ(500×700、800×480 Portrait、800×480 Landscapeなど)を変更すると、下図のように確認できます。

ボタンなどのUIのように画面サイズに応じて伸び縮みさせる対策もありますが、それではダルマなどの画像の比率が変わりゆがんだ形で表示されてしまいます。
そこで今回は、ダルマなどの画像の比率を変えずにさまざまな画面サイズに対応するため、カメラの撮影する範囲の縦横の比率(アスペクト比)を一定に保つ機能をカメラに追加しました。

具体的には、ゲームの画面サイズ(500×700pixel)の比率5:7にあわない画面解像度では、縦か横に余白を作り、比率を5:7に保ちます。
Flappy Darumaでは、アスペクト比を維持するCameraStableAspect.csコンポーネントを作成しカメラに追加し、CameraコンポーネントのorthographicSizeとrectを調整しました。

CameraのorthographicSizeは、カメラが撮影する縦幅のサイズの半分の値をユニット単位で指定します。
この値を固定することで、カメラの撮影する縦幅は維持されます。

rectは、カメラの撮影した映像を、画面のどの範囲に表示するかを画面の幅を1として、0.0~1.0の小数の四角形で指定します。
このときにアスペクト比を保持するために、rectの表示座標と幅を調整します。
下の動画のようにCameraコンポーネントのViewport Rectの矩形情報を0~1の間で変更することで、画面のどの位置からどのくらいの幅で表示するかを比率で指定できます。

CameraStableAspect.csでは、指定されたピクセル単位の解像度width、heightと指定されたpixelPerUnitによって、表示に必要なorthographicSizeを計算し設定します。
そして、さまざまな画面解像度に対応するため、width、heightから算出したアスペクト比を維持しつつ、画面の縦または横の表示幅が100%になるrectを計算し設定します。

    /// 操作するカメラのキャッシュ変数です。
    private Camera cam;
    // 画面のサイズ(ピクセル単位)
    public float width = 540f;
    public float height = 960f;
    // 画像のPixel Per Unit
    public float pixelPerUnit = 100f;

    void Awake()
    {
        // カメラコンポーネントを取得します
        cam = GetComponent();
    }

    /// フレームを更新する直前のイベント関数です。
    /// Input も毎回更新されるので、入力の受け取りはこちらで行います。
    private void Update()
    {
        Adjust();
    }

    /// 参照:Unity2Dで画面のアスペクト比を固定にしたい【Unity】 - Qiita : http://qiita.com/kwst/items/371542a6d3892b577b41
    void Adjust()
    {
        float aspect = (float)Screen.height / (float)Screen.width;
        float bgAcpect = height / width;

        if (this.width < this.height) // 縦長
        {
            // カメラのorthographicSizeを設定
            cam.orthographicSize = (height / 2f / pixelPerUnit);
        }
        else // 横長
        {
            // カメラのorthographicSizeを設定
            cam.orthographicSize = (width / 2f / pixelPerUnit);
        }

        if (bgAcpect > aspect)
        {
            // 倍率
            float bgScale = height / Screen.height;
            // viewport rectの幅
            float camWidth = width / (Screen.width * bgScale);
            // viewportRectを設定
            cam.rect = new Rect((1f - camWidth) / 2f, 0f, camWidth, 1f);
        }
        else
        {
            // 倍率
            float bgScale = width / Screen.width;
            // viewport rectの幅
            float camHeight = height / (Screen.height * bgScale);
            // viewportRectを設定
            cam.rect = new Rect(0f, (1f - camHeight) / 2f, 1f, camHeight);
        }
    }

 

これにより、余分な幅に応じて両側に黒い領域を設けてアスペクト比を維持した映像を実行時に表示します。

これは、テレビが地デジになる前の映像を映す際にアスペクト比を維持するために、左右に余白を設けていることと同じ対処法といえます。

上記のCameraStableAspect.csや、さまざまな効果音を再生するスクリプトなどは、インターネットで紹介されているUnityに関する記事を参考にしました。
Unityでは多くのアドバイスをしてくれるサイトがあるので、困ったときは参考にしてみましょう。

ゲームオーバー画面を作る

ゲームオーバーに状態を切り替える

ダルマが障害物にぶつかると下の動画のようにゲームオーバーの画面が表示されます。
これはバトルシーンから別のシーンに切り替えず、非表示にしておいたゲームオーバー用のUI群をSetActive関数でアクティブにして行っています。

初めにダルマのDaruma.csコンポーネントのOnTriggerEnter2DとOnCollisionEnter2Dイベント関数によって、障害物との衝突を検知します。
そのイベント関数内で、バトルシーン全体の処理を行うBattleScene.csコンポーネントのChangePhaseGameOver関数を呼び出すことで、ゲームオーバーに切り替えるところまでは、前回説明しました。

今回はその後のChangePhaseGameOver関数がどのようにゲームオーバーに状態を切り替えているかを説明していきましょう。

    /// バトルシーンの段階を表す列挙子です。
    public enum Phase
    {
        /// プレイ中。
        Playing,
        /// ゲームオーバー
        Gameover
    }

    /// 現在のバトルシーンの段階です。
    protected Phase currentPhase_ = Phase.Playing;
    /// 現在のバトルシーンの段階を得る読み取り専用のプロパティです。
    public Phase CurrentPhase { get { return currentPhase_; } }

    /// ゲームオーバーの段階に遷移します。
    public void ChangePhaseGameOver()
    {
        // 段階を切り替えます。すでに切り替えてある場合は何もしません。
        if (CurrentPhase == Phase.Gameover)
        {
            return;
        }
        currentPhase_ = Phase.Gameover;

        // ベストスコアを取得し、現在のスコアが上回っていれば更新します。
        if (PlayerPrefs.GetInt("BestScore", 0) < score_)
        {
            // ベストスコアの更新
            PlayerPrefs.SetInt("BestScore", score_);
            PlayerPrefs.Save();
        }

        // ゲームオーバーになったら一部の機能を停止します。
        //  - 障害物の生成とスクロールの停止
        //   - 背景のスクロールの停止
        //   - Daruma のジャンプ入力の受付停止
        obstacleManager_.IsPause = true;
        foreach (ScrollAndBack _backGround in backgroundList_)
        {
            _backGround.IsPause = true;
        }
        daruma_.IsPause = true;

        // スコアに応じたメダルの画像を表示するように設定します。(後述のため省略)

        // プレイ中に表示していたスコアのテキストを非表示にします。
        uiTextScore_.gameObject.SetActive(false);

        // 現在のスコア、ベストスコアを結果を表示するパネルのテキストに設定します。
        uiTextScoreResult_.text = score_.ToString();
        int _bestScore = PlayerPrefs.GetInt("BestScore", 0);
        uiTextBestScore_.text = _bestScore.ToString();

        // 結果を表示するパネルを有効にして、設定してきたメダルの画像やスコアのテキストを表示します。
        gameoverPanel_.SetActive(true);

        // ゲームオーバーの効果音を再生します。
        Util.PlayAudioClip(acGameover_, Vector3.zero, 1f);
    }

 

BattleScene.csコンポーネントでは、状態としてプレイ中とゲームオーバーのどちらであるかをPhaseというenumをCurrentPhaseプロパティで管理しています。
このCurrentPhaseプロパティがプレイ中の場合は、入力を受けてダルマを動かしますが、ゲームオーバーの場合はダルマは動かしません。

ゲームオーバーになると、PlayerPrefsで管理しているハイスコアを更新し、背景や障害物の動きを停止し、ゲームオーバー時に表示されるテキストのUIに今回のスコアとハイスコアを設定します。
そして、スコアのテキストや後述するメダルのイメージを設定したあとに、ゲームオーバーのUIがおさめられたパネルのUIのオブジェクトをSetActive関数でアクティブにします。

これにより、ゲームの結果を含んだゲームオーバー画面が表示されます。

ツイートの実装

表示されたゲームオーバーのUIの中に[SHARE]ボタンがあります。
最近のゲームアプリでは、ゲームのスコアなどをSNSに投稿する機能を持つものが多くあります。
これによりプレイヤーのゲームに対するモチベーションがあがったり、その投稿をみた人が新たにプレイしてくれることが期待できます。

今回は、[SHARE]ボタンを押すことで、ツイッターにスコアをツイートする画面を表示するようにしました。
スクリプトは次のようになります。

public class BattleScene : MonoBehaviour {
    /// ツイートのフォーマット文です。
    /// {0} は現在のスコア、 {1} はベストスコアに置き換わります。
    public string tweetFormat_ = "#FlappyDaruma フラッピーだるま Score {0} BestScore {1}";

    /// SHARE ボタンが押されたときに呼び出される関数です。
    public void OnButtonShare()
    {
        // ボタンの効果音を再生します。
        Util.PlayAudioClip(acButton_, Vector3.zero, 1f);

        // ベストスコアと現在のスコアを含んだツイート用の文字列を作成し、ツイート用の画面を開きます。
        int _bestScore = PlayerPrefs.GetInt("BestScore", 0);
        string _msg = string.Format(tweetFormat_, score_, _bestScore);
        Util.Tweet(_msg);
    }
}

public class Util : MonoBehaviour {
    /// ツイート画面を開きます。
    /// msgPlain ツイートする文字列
    public static void Tweet(string msgPlain)
    {
        string url = "http://twitter.com/intent/tweet?text=" + WWW.EscapeURL(msgPlain);
        OpenURL(url);
        return;
    }
}

ツイートの文面を変えたくなった場合、わざわざスクリプト自体を変更するのは、その後のビルド時間が増えたり手間がかかるので、ツイートのフォーマット文字列を設定するメンバ変数を定義し、下図のようにインスペクターウィンドウで設定できるようにしておくと便利です。

スコアに応じたメダルを表示する

高いスコアを目指すという漠然とした目標よりも、具体的に何点のスコアを狙うか目標ができるとゲームへのモチベーションもあがりやすいでしょう。
Flappy Darumaでは、ゲームオーバーのUIにメダルを表示し、一定のスコアを超えるたびにメダルの種類を変える演出をしています。

さきほどのChangePhaseGameOver関数にこの処理を追加してみます。

public class BattleScene : MonoBehaviour {

    /// スコアに応じたメダルを表示するためのリストの要素です。
    [System.Serializable]
    public class MedalInfo
    {
        /// 表示するメダルの画像です。アセットから設定してください。
        public Sprite spriteMedal_;
        /// メダルを表示するための条件となるスコアの下限です。この値以上の場合に表示します。
        public int scoreBorder_;
    }
    
    /// メダルの情報です。
    /// scoreBorder_ の『大きい』順に設定してください。
    public List medalInfoList_;

    /// メダルを表示する UI.Image を設定します。
    /// メダル画像の差し替えで使います。
    public UnityEngine.UI.Image uiMedal_;

    /// 現在のスコアです。
    public int score_ = 0;

    /// ゲームオーバーの段階に遷移します。
    public void ChangePhaseGameOver()
    {
        // スコアに応じたメダルの画像を表示するように設定します。(関数内の他の処理は省略)
        {
            foreach (MedalInfo _medalInfo in medalInfoList_)
            {
                if (_medalInfo.scoreBorder_ <= score_)
                {
                    uiMedal_.sprite = _medalInfo.spriteMedal_;
                    break;
                }
            }
        }
	}
}

Flappy Darumaでは、メダルを表示するための最低スコアをメダルボーダーと呼びます。
このメダルボーダーとそれに応じたメダルの画像アセットの組み合わせをリストで持たせます。
このリストはインスペクターウィンドウから下図のように設定でき、設定されたメダルボーダー以上で対応するメダルの画像をイメージのUIにセットするようにしています。

これにより、スコアに応じて条件にあったメダルがゲームオーバー時に表示されるようになりました。

メダルボーダーで難易度調節をしよう

メダルボーダーや表示するメダルの画像、メダルの個数などは紹介したBattleSceneコンポーネントのMedalInfoList_リストで設定を簡単に変更できます。

最初にリストのSizeに設定するメダルの個数を設定して任意の個数の要素を作りましょう。
リストの要素のScoreBorder_にはメダル画像を表示するための最低スコアを設定し、SpriteMedal_には表示するメダルの画像アセットを割り当てます。
このときスクリプトの仕様上、ScoreBorder_の数値が大きい順に要素を設定してください。

メダルボーダーは上記のようにインスペクターウィンドウから簡単に変更ができるので、お好みの難易度に調整してゲームを改造してみましょう。

まとめ

今回はタイトル画面とゲームオーバー画面の実装を紹介しました。
ボタンなどのUIに動きをつける方法や、SNSでシェアする機能の実装方法、Flappy Daruma独自のメダルボーダーによる難易度の調整方法などについて触れました。