Upgrade to Pro — share decks privately, control downloads, hide ads and more …

どこのご家庭にもあるシーンマネージャーの話

いも
March 24, 2021

 どこのご家庭にもあるシーンマネージャーの話

いも

March 24, 2021
Tweet

More Decks by いも

Other Decks in Programming

Transcript

  1. ━━━
    どのご家庭にもある
    シーンマネージャーの話
    いも
    ━━━
    2021/03/24 Gotanda.unity #17 1

    View full-size slide

  2. いもです
    twitter @adarapata
    Unity触ったりしています
    Godot Engineも好きです
    2021/03/24 Gotanda.unity #17 2

    View full-size slide

  3. モチベーション
    画⾯遷移の管理の仕組みを毎回作っている気がする
    みんな各⾃で似たようなもの作ってる気がする
    しかしあまり公開されてる気がしない
    話すからみんなも教えて下さい
    2021/03/24 Gotanda.unity #17 3

    View full-size slide

  4. シーンマネージャー is
    Unityでの「画⾯をいい感じに管理してくれる」君
    画⾯の単位を定義して管理する
    1Sceneとか1Prefabとか
    画⾯をスタックできる
    前の画⾯に戻ったりとか
    画⾯遷移にデータを渡せる
    前の画⾯で選択した設定でクエストに挑むとか
    2021/03/24 Gotanda.unity #17 4

    View full-size slide

  5. Unityデフォルトでの画⾯管理
    Sceneファイルの切り替え・追加は可能
    Prefabはデフォルトではない
    画⾯の名称の管理が必要
    遷移先の⽂字列 or Indexをどう持つか。増えたらどうするか
    パラメータを渡す⼿段がデフォルトではない
    スタックする機能はない
    ⼀個前の画⾯の状態を保持させるなど
    この辺を解決するために画⾯管理クラスを作ったりする
    2021/03/24 Gotanda.unity #17 5

    View full-size slide

  6. 解決したいこと
    画⾯をスタックできること
    パラメータを渡せること
    名称の管理が楽なこと
    2021/03/24 Gotanda.unity #17 6

    View full-size slide

  7. ⾃作の⼀例 : PageManager
    画⾯のことを「Page」と呼ぶ
    SceneはUnityの呼称とかぶるのでPageにした
    1Page = 1Prefab
    Page Prefabを動的にロードして追加する
    Pageをスタックしており、前のPageに戻れる
    遷移先Pageを型で呼ぶ
    パラメータを型で渡せる
    依存関係の解決にVContainerを使⽤
    2021/03/24 Gotanda.unity #17 7

    View full-size slide

  8. 呼び⽅
    var parameter = new FooPage.CreateParameter
    {
    Bar = "bar"
    };
    _pageManager.Push(new FooPage.Transition { Parameter = parameter }); // 追加
    _pageManager.Pop(); // ⼀つ前に戻る
    _pageManager.Replace(new FooPage.Transition { Parameter = parameter }); // 現在のページを破棄して追加する
    // スタックしたページをすべて破棄して追加する
    _pageManager.ReplaceAll(new FooPage.Transition { Parameter = parameter });
    await _pageManager.PushAsync(new FooPage.Transition { Parameter = parameter }); // ⾮同期で追加
    2021/03/24 Gotanda.unity #17 8

    View full-size slide

  9. Page定義側
    [PageAsset("BarPage.prefab")]
    public class BarPage : BasePage
    {
    public class Transition : BasePageTransition{}
    }
    2021/03/24 Gotanda.unity #17 9

    View full-size slide

  10. 登場⼈物
    PageManager : ページのスタック・ライフサイクル管理者
    IPageTransition : ページ遷移者
    BasePage : ページ本体
    2021/03/24 Gotanda.unity #17 10

    View full-size slide

  11. クラス図
    2021/03/24 Gotanda.unity #17 11

    View full-size slide

  12. PageManager
    Pageのスタックを管理する
    リストで保持してる
    読み込まれたPageに対して初期化・停⽌というライフサイクルイベ
    ントを呼んであげるのが役割
    具体的にどうやってPageが読み込まれるのかは知らない
    public async UniTask PushAsync(IPageTransition transition)
    {
    var page = await transition.LoadPage();
    await page.Initialize();
    _pages.Add(page); // ページリストの管理
    }
    2021/03/24 Gotanda.unity #17 12

    View full-size slide

  13. 処理の流れ
    2021/03/24 Gotanda.unity #17 13

    View full-size slide

  14. ライフサイクル
    Unityのライフサイクルに合わせるとハンドリングしにくい
    StartはPageManager側から⾒て初期化済みか保証できない
    Destroyで破棄はできるが⼀時停⽌とか再開がない
    なのでざっくりと4つのライフサイクルイベントを⽤意
    Initialize
    Suspend
    Resume
    Discard
    2021/03/24 Gotanda.unity #17 14

    View full-size slide

  15. Pushすると、現在のPageのSupend、新しいPageのInitializeが呼ばれ

    Popすると、現在のPageのSuspend->Discard、⼀つ前のPageの
    Resumeが呼ばれる
    2021/03/24 Gotanda.unity #17 15

    View full-size slide

  16. BasePage
    画⾯⾃体を司るクラス
    ライフサイクルを実装しており、初期化とか停⽌とかできる
    PageManagerとか外の世界とのやり取りを⾏う際の受け⼝
    なので内部はMVCとかMVPとか好きに分割してる
    2021/03/24 Gotanda.unity #17 16

    View full-size slide

  17. public abstract class BasePage
    {
    public virtual UniTask Initialize();
    public virtual UniTask Suspend();
    public virtual UniTask Resume();
    public virtual UniTask Discard();
    }
    public abstract class BasePage : BasePage
    {
    protected TParam Parameter { get; }
    }
    2021/03/24 Gotanda.unity #17 17

    View full-size slide

  18. IPageTransition
    Pageの呼び出し⽅を知っているやつ
    PageManagerが直接Pageの呼び出しを⾏うのではなく
    IPageTransitionに具体的な呼び出しをお任せする
    public class PageTransitionBase : IPageTransition where TPage : BasePage {
    public virtual async UniTask LoadPage() {
    var pageInstance = await DoPrefabLoad(pageAssetName);
    var pageLifetimeScope = pageInstance.GetComponent();
    InitializePageParameter(pageLifetimeScope);
    pageLifetimeScope.Build(); // Page側の依存関係解決
    var page = pageLifetimeScope.Container.Resolve();
    return page;
    }
    }
    2021/03/24 Gotanda.unity #17 18

    View full-size slide

  19. リソースとクラスのマッピング
    どのアセットを引っ張ってくるのかパスを渡したい
    毎回⼿打ちで渡したくはない
    クラス定義とPageアセットは原則1:1と考えて良い
    // pageAssetNameをどう渡すか
    var pageInstance = await DoPrefabLoad(pageAssetName);
    カスタムアトリビュートで直接紐付けてしまう
    2021/03/24 Gotanda.unity #17 19

    View full-size slide

  20. [AttributeUsage(AttributeTargets.Class, Inherited = false)]
    public class PageAssetAttribute : Attribute
    {
    public string PrefabName { get; }
    public PageAssetAttribute(string prefabName)
    {
    this.PrefabName = prefabName;
    }
    }
    [PageAsset("BarPage.prefab")]
    public class BarPage : BasePage
    {
    public class Transition : BasePageTransition{}
    }
    // PageAssetAttributeを取り出す
    var pageNameAttr = Attribute.GetCustomAttribute(typeof(TPage),
    typeof(PageAssetAttribute)) as PageAssetAttribute;
    var pageInstance = await DoPrefabLoad(pageNameAttr.PrefabName);
    2021/03/24 Gotanda.unity #17 20

    View full-size slide

  21. パラメータ渡し
    [PageAsset("FooPage.prefab")]
    public class FooPage : BasePage
    {
    public class CreateParameter
    {
    public string Bar;
    }
    public class Transition : BasePageTransition { }
    }
    ジェネリクスで定義できるようにする
    2021/03/24 Gotanda.unity #17 21

    View full-size slide

  22. そもそも外部パラメータをどう扱っているのか
    public class LifetimeScopeWithParameter : LifetimeScope
    {
    public TParam Param;
    protected override void Configure(IContainerBuilder builder)
    {
    builder.RegisterInstance(Param);
    }
    }
    外からパラメータを設定できるようなLifetimeScopeを⽤意
    2021/03/24 Gotanda.unity #17 22

    View full-size slide

  23. パラメータを設定した上でBuildする
    public class BasePageTransition : IPageTransition where TPage : BasePage {
    public virtual async UniTask LoadPage() {
    var pageInstance = await DoPrefabLoad(pageAssetName);
    var pageLifetimeScope = pageInstance.GetComponent();
    InitializePageParameter(pageLifetimeScope);
    pageLifetimeScope.Build(); // Page側の依存関係解決
    var page = pageLifetimeScope.Container.Resolve();
    return page;
    }
    protected virtual void InitializePageParameter(LifetimeScope container) { }
    }
    public class BasePageTransition : PageTransitionBase where TPage : BasePage {
    public TParam Parameter { get; set; }
    protected override void InitializePageParameter(LifetimeScope scope) {
    if (scope is LifetimeScopeWithParameter s)
    {
    s.Parameter = Parameter;
    }
    }
    }
    2021/03/24 Gotanda.unity #17 23

    View full-size slide

  24. PageのアセットにLifetimeScopeをアタッチする
    public class FooLifetimeScope : LifetimeScopeWithParameter {
    [SerializeField]
    private FooView _fooView;
    protected override void Configure(IContainerBuilder builder) {
    base.Configure(builder);
    builder.Register(Lifetime.Scoped);
    builder.Register(Lifetime.Scoped);
    builder.Register(Lifetime.Scoped);
    builder.RegisterComponent(_fooView);
    }
    }
    これでPageのオブジェクトに外部データをInjectionできる
    2021/03/24 Gotanda.unity #17 24

    View full-size slide

  25. 好きなところ
    型ベースで画⾯遷移を⾏える
    定数を管理しなくていい
    渡すべきパラメータも型で管理できるので楽
    コード⾒ればリソースがわかる
    1:1の関係なら密接でもいいんじゃない派
    2021/03/24 Gotanda.unity #17 25

    View full-size slide

  26. PageManagerまとめ
    遷移先をクラス名で指定できる
    パラメータも型で定義して渡せる
    Managerから詳細な遷移処理を切り出している
    Pageは独⾃にライフサイクルを持たせている
    クラスとアセットをカスタムアトリビュートで紐付けしている
    2021/03/24 Gotanda.unity #17 26

    View full-size slide

  27. みなさんの
    画⾯遷移マネージャーの話を
    お待ちしています
    2021/03/24 Gotanda.unity #17 27

    View full-size slide