Help us understand the problem. What is going on with this article?

Unity UIでページやダイアログごとにシーンを分離する実装パターン

More than 3 years have passed since last update.

こちらはUnity Advent Calendar11日目の記事となります。
前日はtoru_inoueさんによるタイムラインGUI TimeFlowShiki をオープンソースにしてみたでした。

今回のお題

本来はSOOMLA LevelUpのAssetに関する記事を書こうと思っていたのですが、火曜日にAsset Storeを見たらSOOMLA LevelUpが消えているという衝撃的な事実が発覚したため、急遽内容を変更させていただきましたorz

最近Unity UIでメニュー画面を作る際、ページやダイアログなど、要素ごとにシーンを分離してシームレスにロードするパターンがお気に入りです。
Unity5.3から追加されたマルチシーンエディット機能の恩恵も受けられますので、その点でもオススメ出来るかもしれません。

メリット

各シーンにページを1つだけ配置するので、遷移が多くなりがちなメニュー画面も細分化して管理しやすくなります。
また、分業しやすくなりますので、複数人数での開発も向いてるかもです。

デメリット

シーンを読み込むため、PrefabをInstantiateするのに比べるとオーバーヘッドが多くなります。
ただ、古めのAndroid端末でもほとんど違和感は感じませんので、大量にコールしない限りは問題ありません。

あとはBuild Settingで大量のシーンを登録する必要が出てきますが、これはご愛嬌ということで。

実装してみよう

では、実装してみましょう。

iTweenをインポート

ページ遷移のアニメーションに使いますので、Asset StoreからiTweenをインポートします。
iTween大好き。イェー。

スクリプトを準備

下記3つのスクリプトを準備します。

ページ管理スクリプト

ページを管理するスクリプトで、メニューの基幹シーンに配置します。
とりあえずデモということで、ボタン制御処理も含めています。

Pager.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class Pager : MonoBehaviour
{
    // インスペクターでGoToHogePageButtonを紐付ける
    public Button goToHogePageButton;

    // インスペクターでCloseButtonを紐付ける
    public Button closeButton;

    // ページのスタック
    private List<PageAbstract> pages = new List<PageAbstract>();

    private void Start()
    {
        // 各ボタンにリスナーを追加
        goToHogePageButton.onClick.AddListener(OnGoToHogePageButtonClick);
        closeButton.onClick.AddListener(OnCloseButtonClick);

        // Closeボタンは隠しておく
        closeButton.gameObject.SetActive(false);
    }

    // ページをスタックします
    public void AddSubMenu(PageAbstract page)
    {
        if (pages.Count == 0)
        {
            // スタックが0→1になる時にcloseボタン表示
            closeButton.gameObject.SetActive(true);
        }
        pages.Add(page);
    }

    // ページを閉じます
    public void RemoveSubMenu(PageAbstract page)
    {
        if (pages.Contains(page))
        {
            pages.Remove(page);
        }
        Destroy(page.gameObject);
        if (pages.Count == 0)
        {
            // スタックが0になる時にcloseボタン隠す
            closeButton.gameObject.SetActive(false);
        }
    }

    // GoToHogePageボタンを押したときの処理です
    private void OnGoToHogePageButtonClick()
    {
        // HogePageを開く
        Application.LoadLevelAdditiveAsync("HogePage");
    }

    // Closeボタンを押したときの処理です
    private void OnCloseButtonClick()
    {
        // 一番上に積まれているページを閉じる
        var surfaceSubMenu = pages.LastOrDefault();
        if (null != surfaceSubMenu)
        {
            surfaceSubMenu.Hide();
        }
    }
}

ページ基幹クラス

ページの量産を楽にするため、ページ基幹クラスを準備します。
ページ遷移アニメーションなどの共通処理はここに書いておくイメージです。

PageAbstract.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

abstract public class PageAbstract : MonoBehaviour
{
    // フェードイン/アウトアニメーションの秒数
    private const float FADE_TIME = 0.3f;

    // アニメーション表示させたいコンテンツ。Canvas直下のPanelでOK
    public GameObject contents;
    // ページのマネージャー
    protected Pager pager;
    // フェードアニメーション時のAlpha値調整のために、CanvasにCanvasGroupコンポーネントをアタッチしてここにセットしておく
    protected CanvasGroup canvasGroup;

    protected virtual void Start()
    {
        canvasGroup = gameObject.AddComponent<CanvasGroup>();

        // Pagerを探す
        var pagerGameObject = GameObject.Find("Pager");
        if (null != pagerGameObject)
        {
            pager = pagerGameObject.GetComponent<Pager>();
            pager.AddSubMenu(this);
        }
        else
        {
            Debug.Log("Pager not found.");
        }
        Show();
    }

    // ページの表示アニメーション
    protected virtual void Show()
    {
        iTween.ValueTo(gameObject, iTween.Hash(
                "from", 0f,
                "to", 1f,
                "time", FADE_TIME,
                "onupdate", "OnValueUpdate"
            ));

        iTween.MoveFrom(contents, iTween.Hash(
                "y", -Screen.height, 
                "time", FADE_TIME, 
                "easetype", iTween.EaseType.linear));
    }

    // ページの非表示アニメーション
    public virtual void Hide()
    {
        iTween.ValueTo(gameObject, iTween.Hash(
                "from", 1f,
                "to", 0f,
                "time", FADE_TIME,
                "onupdate", "OnValueUpdate"
            ));

        iTween.MoveTo(contents, iTween.Hash(
                "y", -Screen.height, 
                "time", FADE_TIME, 
                "easetype", iTween.EaseType.linear,
                "oncomplete", "OnHideComplete",
                "oncompletetarget", gameObject));
    }

    private void OnValueUpdate(float value)
    {
        canvasGroup.alpha = value;
    }

    private void OnHideComplete()
    {
        pager.RemoveSubMenu(this);
    }
}

ページのスクリプト

最後に、ページのスクリプトです。
先ほど作った基幹クラスを継承させます。
中身は空っぽでOKです。

HogePage.cs
using UnityEngine;
using System.Collections;

public class HogePage : PageAbstract
{
}

メニューのシーンを準備

続いて、メニューのシーンを準備します。
Canvasを2つ作り、ひとつは「MainCanvas」、もうひとつは「SurfaceCanvas」と名前を付けます。
SurfaceCanvasは常に前面に表示したいので、CanvasコンポーネントのSort Orderを1にします。
スクリーンショット 2015-12-10 22.43.25.png

MainCanvasに、ページ呼び出し(ページ遷移)用の「GoToHogePageButton」を、
SurfaceCanvasに、ページを閉じるための「CloseButton」を配置します。
スクリーンショット 2015-12-10 22.45.59.png

ページ管理オブジェクトを配置

空のゲームオブジェクトを作り、名前を「Pager」にします。
Pager.csスクリプトをアタッチし、インスペクターでGoToHogePageButtonとCloseButtonをひも付けしておきます。
スクリーンショット 2015-12-10 22.46.31.png

ここまでの準備が出来たら、シーンを保存しましょう。
シーンの名前は「Menu」にしておきます。

ページのシーンを準備

ページ用に新しいシーンを作成します。
画面遷移したことがわかりやすいように、Panelを配置して真っ赤にでもしておきましょうか。

次に、CanvasにHogePageスクリプトをアタッチします。
HogePageスクリプトのContentsにはPanelをひも付けます。
スクリーンショット 2015-12-10 22.51.42.png

シーンをロードしたときに邪魔になりますので、Main Camera、Directional Light、EventSystemをシーンから削除します。
スクリーンショット 2015-12-10 22.52.40.png

最後に、シーンを保存しましょう。
名前は「HogePage」にしておきます。

Build Settingsにシーンを登録する

先ほど作ったMenuとHogePageシーンを登録しておきます。
スクリーンショット 2015-12-10 22.53.15.png

実行してみよう

Menuシーンを開き、Editorで実行します。
上手くページ遷移できましたか?
ページをたくさん作って連続でページ遷移させると、ちゃんとスタックしていきます。

マルチシーンエディット機能の恩恵

Unity5.3をお使いであれば、MenuシーンとHogePageシーンを両方開いた状態で実行してみてください。
すると、HogePageがちゃんとページスタックに積まれた状態で開きます。
これってちょっとステキなことですよね。

まとめ

管理が楽でページをガンガン増やせるので、結構重宝してます。
バリバリな方々はもっと素晴らしいパターンを使われているかと思いますので、ぜひ教えてくださいましm(_ _)m

Ijoru
Gameスキー 【開発に携わったゲーム】→ ・おしゅしだよ ブレイクスマッシュ!:https://www.cr-gene.com/products/oshushi-breaksmash ・Shooting&Dragons:https://www.qnote.co.jp/shooting-and-dragons/ などなど
https://www.cr-gene.com/
unity-game-dev-guild
趣味・仕事問わずUnityでゲームを作っている開発者のみで構成されるオンラインコミュニティです。Unityでゲームを開発・運用するにあたって必要なあらゆる知見を共有することを目的とします。
https://unity-game-dev-guild.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした