Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

呼び⽅ 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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

[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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

パラメータを設定した上で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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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