Skip to content

Latest commit

 

History

History
1020 lines (770 loc) · 55.6 KB

README_JA.md

File metadata and controls

1020 lines (770 loc) · 55.6 KB

Unity Screen Navigator

license

English Documents Available(英語ドキュメント)

UnityのuGUIで画面遷移、画面遷移アニメーション、遷移履歴のスタック、画面のライフサイクルマネジメントを行うためのライブラリです。

Demo

目次

詳細

概要

特徴

  • ページやモーダル、タブとその画面遷移を簡単かつ柔軟に構築
  • 画面のロードから破棄までライフサイクルとメモリを管理
  • 複雑な画面遷移アニメーションをアニメーターと分業して実装可能なワークフロー
  • 余計な機能(ex. GUIライブラリやステートマシンなど)を含まない、よく分離された単機能ライブラリ
  • その他、履歴のスタッキングや遷移中のクリック防止などの機能を標準実装

デモ

デモシーンは以下の手順で再生できます。

  1. リポジトリをクローンする
  2. 以下のシーンを開いて再生する

なお、本デモで使用している画像の一部は以下のフリーコンテンツを利用させていただいております。
著作権情報などの詳細は以下のウェブサイトを参照してください。

セットアップ

要件

  • Unity 2021.3 以上
  • uGUI (UIElementsには非対応)

インストール

  1. Window > Package ManagerからPackage Managerを開く
  2. 「+」ボタン > Add package from git URL
  3. 以下を入力してインストール

あるいはPackages/manifest.jsonを開き、dependenciesブロックに以下を追記します。

{
    "dependencies": {
        "com.harumak.unityscreennavigator": "https://github.com/Haruma-K/UnityScreenNavigator.git?path=/Assets/UnityScreenNavigator"
    }
}

バージョンを指定したい場合には以下のように記述します。

基本的な画面遷移

画面と遷移の概念

Unity Screen Navigatorは、画面を「ページ」「モーダル」「シート」の3つに分類します。

「ページ」は順番に遷移をしていく画面です。
例えば、ページAからページBへ遷移すると、ページAは履歴にスタッキングされます。
ページBから戻るとページAが状態を保ったまま再表示されます。

Demo

「モーダル」は画面上に積み重なっていく画面です。
モーダルの表示中は最前面にあるモーダル以外のインタラクションはブロックされます。

Demo

「シート」はタブのようなGUIに使われます。
履歴を持たず、一つのアクティブな画面だけが表示されます。

Demo

さらに、これらの画面は入れ子にすることが可能です。
また、各画面の領域は自由に指定できます(必ずしも全画面に表示する必要はありません)。

Demo

ページを作成して遷移させる

ページ遷移を作成するには、まずCanvas配下の任意のGameObjectにPage Containerコンポーネントをアタッチします。
ページはこのGameObjectのRectTransformにフィットするように表示されるのでサイズを調整します。

次にページのViewとなるGameObjectのルートにPageコンポーネントをアタッチします。
このGameObjectは任意の名前をつけてPrefab化して、Resourcesフォルダ配下に配置しておきます。

このResourcesフォルダ以下のパスをPageContainer.Push()に与えることでページが表示されます。
以下はAssets/Resources/ExamplePage.prefabに配置したページを読み込む例です。

PageContainer pageContainer;

// 「ExamplePage」に遷移する
var handle = pageContainer.Push("ExamplePage", true);

// 遷移の終了を待機する
yield return handle;
//await handle.Task; // awaitでも待機できます
//handle.OnTerminate += () => { }; // コールバックも使えます

また、アクティブなページを破棄して前のページを表示するには、PageContainer.Pop()を使います。

PageContainer pageContainer;

// アクティブなページから戻る
var handle = pageContainer.Pop(true);

// 遷移の終了を待機する
yield return handle;

戻る際にあるページをスキップしたい場合には、オプション引数を使うことで 履歴へのスタッキングを無効に できます。

モーダルを作成して遷移させる

モーダル遷移を作成するには、Canvas配下の任意のGameObjectにModal Containerコンポーネントをアタッチします。
一般的なモーダルは、その背景が画面全体を覆うことでクリックをブロッキングするデザインになります。
したがってこのGameObjectのRectTransformのサイズは基本的には画面サイズと一致するように設定します。

次にモーダルのViewとなるGameObjectのルートにModalコンポーネントをアタッチします。
このルートGameObjectはModal Containerのサイズにフィットするように調整されます。
したがって余白を持ったモーダルを作成する場合には、小さめのサイズを持った子GameObjectを作り、その中にコンテンツを作成します。

Demo

このGameObjectは任意の名前をつけてPrefab化して、Resourcesフォルダ配下に配置しておきます。

このResourcesフォルダ以下のパスをModalContainer.Push()に与えることでモーダルが表示されます。
以下はAssets/Resources/ExampleModal.prefabに配置したモーダルを読み込む例です。

ModalContainer modalContainer;

// 「ExampleModal」に遷移する
var handle = modalContainer.Push("ExampleModal", true);

// 遷移の終了を待機する
yield return handle;
//await handle.Task; // awaitでも待機できます
//handle.OnTerminate += () => { }; // コールバックも使えます

また、アクティブなモーダルを破棄して前のモーダルを表示するには、ModalContainer.Pop()を使います。

ModalContainer modalContainer;

// アクティブなモーダルから戻る
var handle = modalContainer.Pop(true);

// 遷移の終了を待機する
yield return handle;

なお、 モーダルの背景は自由に変更する ことができます。

シートを作成して遷移させる

シート遷移を作成するには、Canvas配下の任意のGameObjectにSheet Containerコンポーネントをアタッチします。
シートはこのGameObjectのRectTransformにフィットするように表示されるのでサイズを調整します。

次にシートのViewとなるGameObjectのルートにSheetコンポーネントをアタッチします。
このGameObjectは任意の名前をつけてPrefab化して、Resourcesフォルダ配下に配置しておきます。

このResourcesフォルダ以下のパスをSheetContainer.Register()に与えることでシートが生成されます。
生成後にSheetContainer.Show()を呼ぶことでアクティブなシートを切り替えられます。
この時、すでにアクティブなシートが存在した場合にはそのシートは非アクティブになります。

以下はAssets/Resources/ExampleSheet.prefabに配置したシートを表示する例です。

SheetContainer sheetContainer;

// 「ExampleSheet」をインスタンス化する
var registerHandle = sheetContainer.Register("ExampleSheet");
yield return registerHandle;

// 「ExampleSheet」を表示する
var showHandle = sheetContainer.Show("ExampleSheet", false);
yield return showHandle;

なお、Register()メソッドにより同じリソースキーのシートを複数インスタンス化する場合には、
リソースキーによるインスタンスの同一性を担保することができません。
このようなケースでは以下のように、リソースキーの代わりにシートIDを使います。

SheetContainer sheetContainer;

// 「ExampleSheet」をインスタンス化してシートIDを得る
var sheetId = 0;
var registerHandle = sheetContainer.Register("ExampleSheet", x =>
{
    sheetId = x.sheetId;
});
yield return registerHandle;

// シートIDを使ってシートを表示する
var showHandle = sheetContainer.Show(sheetId, false);
yield return showHandle;

また、アクティブなシートを切り替えるのではなく非表示にするには、SheetContainer.Hide()を使います。

SheetContainer sheetContainer;

// アクティブなシートを非表示にする
var handle = sheetContainer.Hide(true);

// 遷移の終了を待機する
yield return handle;

遷移処理を待機する方法

画面遷移のための各メソッドは、戻り値としてAsyncProcessHandleを返します。
このオブジェクトを使用すると、遷移処理が終了するまで待機することができます。

また待機方法としては、コルーチン、非同期メソッド、コールバックに対応しています。
コルーチン内で待機するには以下のようにyield returnを使用します。

yield return pageContainer.Push("ExamplePage", true);

非同期メソッド内で待機するには以下のようにAsyncProcessHandle.Taskに対してawaitを使います。

await pageContainer.Push("ExamplePage", true).Task;

コールバックを使う場合にはAsyncProcessHandle.OnTerminateを使います。

pageContainer.Push("ExamplePage", true).OnTerminate += () => { };

コンテナを取得する方法

各コンテナ(PageContainer/ModalContainer/SheetContainer)には、インスタンスを取得するための静的メソッドが用意されています。

以下のようにContainer.Of()を使うと、与えたTransformやRectTransformのから最も近い親にアタッチされているコンテナを取得できます。

var pageContainer = PageContainer.Of(transform);
var modalContainer = ModalContainer.Of(transform);
var sheetContainer = SheetContainer.Of(transform);

また、コンテナのインスペクタからNameプロパティを設定すると、その名前を用いてコンテナを取得することもできます。
この場合、Container.Find()メソッドを使用します。

var pageContainer = PageContainer.Find("SomePageContainer");
var modalContainer = ModalContainer.Find("SomeModalContainer");
var sheetContainer = SheetContainer.Find("SomeSheetContainer");

画面遷移アニメーション

共通の遷移アニメーションを設定する

デフォルトでは、画面の種類ごとに標準的な遷移アニメーションが設定されています。

独自の遷移アニメーションを作成する場合には、TransitionAnimationObjectを継承したクラスを作成します。
このクラスはアニメーションの挙動を定義するためのプロパティやメソッドを持ちます。

// アニメーションの時間(秒)
public abstract float Duration { get; }

// 初期化する
public abstract void Setup();

// ある時間における状態を決める
public abstract void SetTime(float time);

実際の実装方法は SimpleTransitionAnimationObject を参考にしてください。

次にこのScriptableObjectをインスタンス化し、UnityScreenNavigatorSettingsに設定します。
UnityScreenNavigatorSettingsAssets > Create > Screen Navigator Settingsから作成できます。

画面ごとに遷移アニメーションを設定する

各画面ごとに個別のアニメーションを設定することもできます。

Page、Modal、SheetコンポーネントにはそれぞれAnimation Containerというプロパティが用意されています。
ここでこの画面の遷移に使用するアニメーションを設定できます。

Asset TypeScriptable Objectに設定した上で、前節で説明したTransitionAnimationObjectAnimation Objectにアサインすると、この画面の遷移アニメーションを変更できます。

また、ScriptableObjectではなくMonoBehaviourを使うこともできます。
この場合、まずTransitionAnimationBehaviourを継承したクラスを作成します。
実際の実装方法は SimpleTransitionAnimationBehaviour を参考にしてください。

クラスを作ったら、このコンポーネントをアタッチした上で、Asset TypeMono Behaviourにして参照をアサインします。

相手画面に応じて遷移アニメーションを変更する

例えば画面Aが入ってきて画面Bが出ていくとき、画面Bを画面Aの「相手画面」と呼びます。

下図のプロパティに相手画面の名前を入力すると、相手画面と名前が一致したときのみその遷移アニメーションが適用されます。

デフォルトでは、Prefab名が画面名として使われます。
明示的に命名したい場合、Use Prefab Name As Identiferのチェックを外した上でIdentifierプロパティに名前を入力します。

さらに、Partner Page Identifier Regexには正規表現も使用できます。
なお複数のアニメーションを指定した場合、上から順に評価されます。

画面遷移アニメーションと描画順

相手画面が存在するページやシートの遷移アニメーションでは描画順が重要になることがあります。
例えば画面が相手画面に覆い被さるようなアニメーションです。

描画順を制御したい場合には、Rendering Orderプロパティを使用します。

画面遷移時にはこの値が小さいものから順に描画されます。

なおモーダルは常に新しいものが手前に表示されるため、Rendering Orderプロパティを持ちません。

シンプルな遷移アニメーションを簡単に作る

シンプルな遷移アニメーションの実装としてSimpleTransitionAnimationObjectを使うことができます。

これを使うには、Assets > Create > Screen Navigator > Simple Transition Animationを選択します。
すると以下のようなScriptableObjectが生成されるので、Inspectorからアニメーションを設定します。

これのMonoBehaviour実装版であるSimpleTransitionAnimationBehaviourも用意しています。
こちらは直接GameObjectにアタッチして使用します。

各プロパティの説明は以下の通りです。

名前 説明
Delay アニメーション開始前の遅延時間(秒)
Duration アニメーション時間(秒)
Ease Type イージング関数の種類
Before Alignment 遷移前の、コンテナからの相対位置
Before Scale 遷移前のスケール値
Before Alpha 遷移前の透明度
After Alignment 遷移後の、コンテナからの相対位置
After Scale 遷移後のスケール値
After Alpha 遷移後の透明度

相手画面とのインタラクティブなアニメーションの実装

相手画面の状態を参照したアニメーションを作成することもできます。
以下の例では、前のモーダルの画像を拡大しつつシームレスに次のダイアログに遷移しています。

これを実装するには、まずTransitionAnimationObjectTransitionAnimationBehaviourを継承したクラスを作成します。
そしてPartnerRectTransformプロパティを参照することで相手画面を取得します。
相手画面が存在しない場合にはPartnerRectTransformはnullになります。

具体的な実装方法は、デモに含まれる CharacterImageModalTransitionAnimation を参考にしてください。

Timelineで画面遷移アニメーションをつける

Timelineを使って画面遷移アニメーションを作成することもできます。
複雑な遷移アニメーションにはTimelineを使用することをおすすめします。

これを実装するためにはまず適当なGameObjectにTimeline Transition Animation Behaviourをアタッチします。
プロパティにPlayable DirectorTimeline Assetをアサインしておきます。

Playable DirectorPlay On Awakeプロパティのチェックは外しておいてください。

最後にこのTimeline Transition Animation BehaviourAnimation Containerにアサインします。

なお、Timelineを使ってuGUIのアニメーションを作成するには UnityUIPlayables がおすすめです。

ライフサイクルイベント

画面遷移中には、画面のライフサイクルに紐づいたイベントが実行されます。
これをフックすることで画面の初期化時や遷移前後の処理を作成することができます。

ページのライフサイクルイベント

Pageクラスを継承したクラスで以下のようにメソッドをオーバーライドすることで、
そのページのライフサイクルに紐づく処理を記述することができます。

using System.Collections;
using UnityScreenNavigator.Runtime.Core.Page;

public class SomePage : Page
{
    // このページがロードされた直後に呼ばれる
    public override IEnumerator Initialize() { yield break; }
    // このページがリリースされる直前に呼ばれる
    public override IEnumerator Cleanup() { yield break; }
    // Push遷移によりこのページが表示される直前に呼ばれる
    public override IEnumerator WillPushEnter() { yield break; }
    // Push遷移によりこのページが表示された直後に呼ばれる
    public override void DidPushEnter() { }
    // Push遷移によりこのページが非表示になる直前に呼ばれる
    public override IEnumerator WillPushExit() { yield break; }
    // Push遷移によりこのページが非表示になった直後に呼ばれる
    public override void DidPushExit() { }
    // Pop遷移によりこのページが表示される直前に呼ばれる
    public override IEnumerator WillPopEnter() { yield break; }
    // Pop遷移によりこのページが表示された直後に呼ばれる
    public override void DidPopEnter() { }
    // Pop遷移によりこのページが非表示になる直前に呼ばれる
    public override IEnumerator WillPopExit() { yield break; }
    // Pop遷移によりこのページが非表示になった直後に呼ばれる
    public override void DidPopExit() { }
}

また、以下のように Page.AddLifecycleEvent() により外部からライフサイクルイベントを登録することもできます。

// IPageLifecycleEventは上記のライフサイクルイベントが定義されているインターフェース
// 第二引数で実行優先度を指定できる
//  0未満: Pageのライフサイクルイベントよりも前に実行
//  1以上: Pageのライフサイクルイベントよりも後に実行
IPageLifecycleEvent lifecycleEventImpl;
Page page;
page.AddLifecycleEvent(lifecycleEventImpl, -1);

// 以下のように一部のライフサイクルイベントだけを登録することもできる
IEnumerator OnWillPushEnter()
{
    // 何かしらの処理
    yield break;
}
page.AddLifecycleEvent(onWillPushEnter: OnWillPushEnter);

さらに、IPageContainerCallbackReceiverを実装したクラスをPageContainer.AddCallbackReceiver()に渡すことで、コンテナから遷移イベントをフックできます。

public interface IPageContainerCallbackReceiver
{
    // ページがPushされる直前に呼ばれる
    void BeforePush(Page enterPage, Page exitPage);
    // ページがPushされた直後に呼ばれる
    void AfterPush(Page enterPage, Page exitPage);
    // ページがPopされる直前に呼ばれる
    void BeforePop(Page enterPage, Page exitPage);
    // ページがPopされた直後に呼ばれる
    void AfterPop(Page enterPage, Page exitPage);
}

なおIPageContainerCallbackReceiverMonoBehaviourに実装してコンテナのGameObjectにアタッチしておけば、
PageContainer.AddCallbackReceiver()を呼ばなくても初期化時にPageContainerに登録されます。

モーダルのライフサイクルイベント

Modalクラスを継承したクラスで以下のようにメソッドをオーバーライドすることで、
そのモーダルのライフサイクルに紐づく処理を記述することができます。

using System.Collections;
using UnityScreenNavigator.Runtime.Core.Modal;

public class SomeModal : Modal
{
    // このモーダルがロードされた直後に呼ばれる
    public override IEnumerator Initialize() { yield break; }
    // このモーダルがリリースされる直前に呼ばれる
    public override IEnumerator Cleanup() { yield break; }
    // Push遷移によりこのモーダルが表示される直前に呼ばれる
    public override IEnumerator WillPushEnter() { yield break; }
    // Push遷移によりこのモーダルが表示された直後に呼ばれる
    public override void DidPushEnter() { }
    // Push遷移によりこのモーダルが非表示になる直前に呼ばれる
    public override IEnumerator WillPushExit() { yield break; }
    // Push遷移によりこのモーダルが非表示になった直後に呼ばれる
    public override void DidPushExit() { }
    // Pop遷移によりこのモーダルが表示される直前に呼ばれる
    public override IEnumerator WillPopEnter() { yield break; }
    // Pop遷移によりこのモーダルが表示された直後に呼ばれる
    public override void DidPopEnter() { }
    // Pop遷移によりこのモーダルが非表示になる直前に呼ばれる
    public override IEnumerator WillPopExit() { yield break; }
    // Pop遷移によりこのモーダルが非表示になった直後に呼ばれる
    public override void DidPopExit() { }
}

また、以下のように Modal.AddLifecycleEvent() により外部からライフサイクルイベントを登録することもできます。

// IModalLifecycleEventは上記のライフサイクルイベントが定義されているインターフェース
// 第二引数で実行優先度を指定できる
//  0未満: Modalのライフサイクルイベントよりも前に実行
//  1以上: Modalのライフサイクルイベントよりも後に実行
IModalLifecycleEvent lifecycleEventImpl;
Modal modal;
modal.AddLifecycleEvent(lifecycleEventImpl, -1);

// 以下のように一部のライフサイクルイベントだけを登録することもできる
IEnumerator OnWillPushEnter()
{
    // 何かしらの処理
    yield break;
}
modal.AddLifecycleEvent(onWillPushEnter: OnWillPushEnter);

さらに、IModalContainerCallbackReceiverを実装したクラスをModalContainer.AddCallbackReceiver()に渡すことで、コンテナから遷移イベントをフックできます。

public interface IModalContainerCallbackReceiver
{
    // モーダルがPushされる直前に呼ばれる
    void BeforePush(Modal enterModal, Modal exitModal);
    // モーダルがPushされた直後に呼ばれる
    void AfterPush(Modal enterModal, Modal exitModal);
    // モーダルがPopされる直前に呼ばれる
    void BeforePop(Modal enterModal, Modal exitModal);
    // モーダルがPopされた直後に呼ばれる
    void AfterPop(Modal enterModal, Modal exitModal);
}

なおIModalContainerCallbackReceiverMonoBehaviourに実装してコンテナのGameObjectにアタッチしておけば、
ModalContainer.AddCallbackReceiver()を呼ばなくても初期化時にModalContainerに登録されます。

シートのライフサイクルイベント

Sheetクラスを継承したクラスで以下のようにメソッドをオーバーライドすることで、
そのシートのライフサイクルに紐づく処理を記述することができます。

using System.Collections;
using UnityScreenNavigator.Runtime.Core.Sheet;

public class SomeSheet : Sheet
{
    // このシートがロードされた直後に呼ばれる
    public override IEnumerator Initialize() { yield break; }
    // このシートがリリースされる直前に呼ばれる
    public override IEnumerator Cleanup() { yield break; }
    // このシートが表示される直前に呼ばれる
    public override IEnumerator WillEnter() { yield break; }
    // このシートが表示された直後に呼ばれる
    public override void DidEnter() { }
    // このシートが非表示になる直前に呼ばれる
    public override IEnumerator WillExit() { yield break; }
    // このシートが非表示になった直後に呼ばれる
    public override void DidExit() { }
}

また、以下のように Sheet.AddLifecycleEvent() により外部からライフサイクルイベントを登録することもできます。

// IModalLifecycleEventは上記のライフサイクルイベントが定義されているインターフェース
// 第二引数で実行優先度を指定できる
//  0未満: Modalのライフサイクルイベントよりも前に実行
//  1以上: Modalのライフサイクルイベントよりも後に実行
ISheetLifecycleEvent lifecycleEventImpl;
Sheet sheet;
sheet.AddLifecycleEvent(lifecycleEventImpl, -1);

// 以下のように一部のライフサイクルイベントだけを登録することもできる
IEnumerator OnWillEnter()
{
    // 何かしらの処理
    yield break;
}
modal.AddLifecycleEvent(onWillEnter: OnWillEnter);

さらに、ISheetContainerCallbackReceiverを実装したクラスをSheetContainer.AddCallbackReceiver()に渡すことで、コンテナから遷移イベントをフックできます。

public interface ISheetContainerCallbackReceiver
{
    // シートがShowされる直前に呼ばれる
    void BeforeShow(Sheet enterSheet, Sheet exitSheet);
    // シートがShowされた直後に呼ばれる
    void AfterShow(Sheet enterSheet, Sheet exitSheet);
    // シートがHideされる直前に呼ばれる
    void BeforeHide(Sheet exitSheet);
    // シートがHideされた直後に呼ばれる
    void AfterHide(Sheet exitSheet);
}

なおISheetContainerCallbackReceiverMonoBehaviourに実装してコンテナのGameObjectにアタッチしておけば、
SheetContainer.AddCallbackReceiver()を呼ばなくても初期化時にSheetContainerに登録されます。

コルーチンの代わりに非同期メソッドを使う

以下のように、コルーチンの代わりに非同期メソッドを使用してライフサイクルイベントを定義することもできます。

using System.Threading.Tasks;
using UnityScreenNavigator.Runtime.Core.Page;

public class SomePage : Page
{
    // 非同期メソッドを使ってライフサイクルイベントを定義する
    public override async Task Initialize()
    {
        await Task.Delay(100);
    }
}

非同期メソッドを使うには、以下の手順でScripting Define Symbolsを追加します。

  • Player Settings > Other Settingsを開く
  • Scripting Define SymbolsにUSN_USE_ASYNC_METHODSを追加

Scripting Define Symbolsは全てのプラットフォームに対して設定する必要がある点に注意してください。

画面リソースのロード

画面リソースの読み込み方法を変更する

上述の通り、デフォルトでは各画面のリソースはResourcesフォルダにPrefabとして配置して読み込みます。

リソースの読み込み方法を変更するには、まずAssetLoaderObjectを継承したScriptable Objectを作成します。
AssetLoaderObjectIAssetLoaderの実装であり、以下のメソッドを持ちます。

// keyに対応するリソースを読み込む
public abstract AssetLoadHandle<T> Load<T>(string key) where T : Object;

// keyに対応するリソースを非同期で読み込む
public abstract AssetLoadHandle<T> LoadAsync<T>(string key) where T : Object;

// handleが示すリソースを解放する
public abstract void Release(AssetLoadHandle handle);

具体的な実装方法は ResourcesAssetLoader を参考にしてください。
このクラスを作成したら、Scriptable Objectをインスタンス化し、UnityScreenNavigatorSettingsAssetLoaderプロパティにアサインします。

UnityScreenNavigatorSettingsAssets > Create > Screen Navigator Settingsから作成できます。

また、各 ContainerAssetLoader プロパティに値を設定することで、Container ごとに使用する IAssetLoader を設定できます。

Addressableアセットシステムを使って読み込む

Addressableアセットシステム用のIAssetLoaderの実装は標準で用意しています。
アドレスを使って各画面を読み込みたい場合には以下の手順で設定します。

  1. Assets > Create > Resource Loader > Addressable Asset Loader を選択
  2. 1.で作成したScriptable ObjectをUnityScreenNavigatorSettingsAssetLoaderプロパティにアサイン

同期的にロードする

画面を同期的にロードするには、各コンテナの遷移メソッドのloadAsync引数にfalseを渡します。
例えばPageContainer.Push()の場合は以下のように記述します。

PageContainer container;

// 同期ロード
var handle = container.Push("FooPage", true, loadAsync: false);

// 遷移アニメーションの終了を待つ
yield return handle;

さらにonLoadコールバックを併用すると、遷移メソッドのコールと同じフレームで初期化処理を行えます。

PageContainer container;

// 同期ロード & ロード後のコールバック
var handle = container.Push("FooPage", true, loadAsync: false, onLoad: x =>
{
    // ページの初期化処理(Pushと同フレームに呼ばれる)
    x.page.Setup();
});

// 遷移アニメーションの終了を待つ
yield return handle;

なお、AddressableAssetLoaderを使う場合、同期ロードを行うにはAddressables 1.17.4以上が必要です。
さらに Addressableの仕様 によりパフォーマンス上の注意点が存在するのでご注意ください。

プリロードする

ページやモーダルのリソースは、画面遷移がリクエストされて初めてロードされます。
容量の大きいページやモーダルをロードする際には、ロードに時間がかかりスムーズな遷移を妨げるかもしれません。

そのような場合にはプリロードして事前にリソースをロードしておく手法が有用です。
以下はPageContainerでプリロードを行う例です。

const string pageName = "FooPage";
PageContainer container;

// FooPageをプリロードする
var preloadHandle = container.Preload(pageName);

// プリロードの完了を待つ
yield return preloadHandle;

// FooPageがプリロードされているのでスムーズに遷移できる
container.Push(pageName, true);

// プリロードしたFooPageを破棄する
container.ReleasePreloaded(pageName);

具体的な使用例としては DemoのHomePage を参考にしてください。
Homeページの初期化時にShopページも同時に読み込み、破棄も同時に行っています。

その他の機能

まとめて戻る

PageContainerModalContainer では、複数の画面をまとめて戻ることができます。
まとめて戻るには、PageContainer.Pop()ModalContainer.Pop()の第二引数に戻る画面数を指定します。

PageContainer pageContainer;
pageContainer.Pop(true, 2);

ModalContainer modalContainer;
modalContainer.Pop(true, 2);

また、戻り先の PageID や ModalID を指定してまとめて戻ることもできます。
PageID や ModalID は、以下のように Push()onLoad コールバックを使うことで取得できます。

PageContainer pageContainer;
pageContainer.Push("fooPage", true, onLoad: x =>
{
    var pageId = x.pageId;
});

ModalContainer modalContainer;
modalContainer.Push("fooModal", true, onLoad: x =>
{
    var modalId = x.modalId;
});

また、Push()pageIdmodalId 引数を指定することで、任意の ID を指定することもできます。

PageContainer pageContainer;
pageContainer.Push("fooPage", true, pageId: "MyPageID");

ModalContainer modalContainer;
modalContainer.Push("fooModal", true, modalId: "MyModalID");

なお、まとめて戻る際にスキップされるページやモーダルについては、遷移前後のライフサイクルイベントは呼ばれず、破棄前のイベントだけ呼ばれます。
また PageContainer においては、スキップされるページの遷移アニメーションは再生されません。
ModalContainer においてはスキップされるモーダルが閉じる際の遷移アニメーションがまとめて同時に再生されます。

ページを履歴にスタッキングしない

ロード画面や演出用のページのように、履歴にスタッキングせずに戻る遷移の際にはスキップしたいページがあります。

このようなケースでは、PageContainer.Push()メソッドのオプション引数stackをfalseに指定すると、そのページは履歴に積まれなくなります。
次のページに遷移する際にはこのページのインスタンスは破棄され、したがって戻る際にはスキップされます。

PageContainer container;

// FooPageを履歴に積まずに遷移する
yield return container.Push("FooPage", true, stack: false);

// BarPageに遷移し、FooPageは破棄される
yield return container.Push("BarPage", true);

// PopするとFooPageには戻らず、さらにその前のページに戻る
yield return container.Pop(true);

具体的な使用例としては DemoのTopPage を参考にしてください。
スタッキングしないロード用のページに遷移しています。

モーダルの背景を変更する

デフォルトでは、モーダルの背景として黒い半透明の画面が設定されています。
これは設定により変更することができます。

変更するにはまずモーダル背景のViewにModal Backdropコンポーネントをアタッチし、これをPrefab化します。

次にこのPrefabをモーダルの背景としてアサインします。
アプリケーション全体のモーダルの背景を変更するには、UnityScreenNavigatorSettingsModal Backdrop Prefabにアサインします。

UnityScreenNavigatorSettingsAssets > Create > Screen Navigator Settingsから作成できます。

また、Modal Containerごとに背景を設定するには、Modal ContainerOverride Backdrop PrefabにPrefabをアサインします。

モーダルの背景がクリックされたらモーダルを閉じる

デフォルトではモーダル背景はクリックできません。
モーダルの背景がクリックされたときに最前面のモーダルを閉じるには、まず上記の手順でモーダルの背景を変更します。
その上で Modal BackdropClose Modal When Clicked プロパティにチェックを入れます。

遷移中のインタラクションを有効にする

遷移開始から終了までは、全てのコンテナにおいて画面のクリックなどのインタラクションが無効になります。
この設定はUnityScreenNavigatorSettingsEnable Interaction In TransitionControl Interactions Of All Containersで変更できます。
デフォルトでは、Enable Interaction In Transitionはfalseで、Control Interactions Of All Containersはtrueになっています。

UnityScreenNavigatorSettingsEnable Interaction In Transitionをtrueに設定すると、遷移中でもインタラクションが有効になります。
またEnable Interaction In TransitionをfalseにしたままControl Interactions Of All Containersをfalseにすると、遷移処理を行なっているコンテナのみインタラクションを無効にします。

UnityScreenNavigatorSettingsAssets > Create > Screen Navigator Settingsから作成できます。

ただし、あるコンテナについて遷移中に他の画面への遷移はできないので、
インタラクションを有効にする場合には遷移のタイミングを自身で適切に制御する必要があります。

Containerのマスクを外す

デフォルトでは、コンテナの配下の画面のうち、コンテナの外に出た部分はマスクされます。
コンテナ外の画面も表示したい場合には、コンテナのGameObjectにアタッチされているRect Mask 2Dコンポーネントのチェックボックスを外してください。

再生中の遷移アニメーションの情報を取得する

再生中の遷移アニメーションの情報は PageModalSheet クラスの以下のプロパティから取得できます。

プロパティ名 説明
IsTransitioning 遷移中かどうか。
TransitionAnimationType 遷移アニメーションの種類。遷移中じゃない場合にはnullを返す。
TransitionAnimationProgress 遷移アニメーションの進捗。
TransitionAnimationProgressChanged 遷移アニメーションの進捗が変わった時のイベント。

画面ロード時に読み込み済みの Prefab インスタンスを使用する

PreloadedAssetLoaderObject を使用すると、画面読み込み時に Resources や Addressables を経由せず、読み込み済みの Prefab インスタンスを直接できます。
Assets > Create > Resource Loader > Preloaded Asset Loader から作成した Scriptable Object に以下のようにキーと Prefab を入力することで使用できます。

また、ランタイム用の実装として PreloadedAssetLoader も用意しています。

モーダルの背景の挙動を変えたい

ModalContainerの Inspector から Backdrop Strategy を変更することで、モーダルの背景の挙動を以下の通り変更できます。

設定値 説明
Generate Per Modal モーダルごとに背景を生成する
Only First Backdrop 最初のモーダルにだけ背景を生成し、2個目以降は背景をつけない
Change Order Before Animation 2個目のモーダルを生成したときに最初に生成した背景の描画順を変更して再利用する(アニメーションの前に描画順を変更)
Change Order After Animation 2個目のモーダルを生成したときに最初に生成した背景の描画順を変更して再利用する(アニメーションの後に描画順を変更)

FAQ

各画面をPrefabではなくシーンで作りたい

AssetLoader を実装することで、シーンに配置された画面を読み込むことができます。
リクエストされたページに対応したシーンファイルを読み込み、その中から適切な GameObject を返すような AssetLoader を実装します。
詳細は 画面リソースの読み込み方法を変更する を参照してください。

ビューとロジックを分離する方法を知りたい

以下のブログ記事に考え方と実装方法を掲載しています。

https://light11.hatenadiary.com/entry/2022/01/11/193925

各画面にデータを受け渡す方法を知りたい

まず、例として、デモシーンでは以下のように、ロードが完了したタイミングで画面にデータを受け渡しています。

characterImageModal.Setup(_characterId, _selectedRank);

ただし、これ以外にもさまざまな受け渡し方法が考えられます。
例えばDIコンテナを使ってデータを各画面にセットしたいケースも考えられます。
したがって本ライブラリとしては、特定の方法を実装して強制することは行わない方針です。

Popしたページやモーダルを再利用したい

Popしたページやモーダルは即座に破棄され、再利用することはできません。

再利用をしたいという要望の本質は、以下の二つに分類できます。

  1. 毎回画面のリソースをロードしたくない
  2. 画面の状態を保持したい

このうち、ロード時間の問題は プリロード で解決できます。
状態の保持については、保守性の観点からも、状態とビューを切り離して再構築できる実装を行うことをお勧めします。

また、一般的にユーザビリティとして状態を保持するべきなのは「タブ」による遷移です。
本ライブラリにおいてもタブを実現するための「シート」による遷移では状態が常に保持されます。
詳しくは シートを作成して遷移させる を参照してください。

なお、仮に再利用できるようにした場合、ライフサイクルをユーザ自身が管理する必要が出てきます。
つまり不要になった際に Cleanup メソッドを呼び、インスタンスを破棄してメモリを綺麗にする必要があります。

ライセンス

本ソフトウェアはMITライセンスで公開しています。
ライセンスの範囲内で自由に使っていただけますが、著作権表示とライセンス表示が必須となります。