Core
ここではCoreについて学習します。
- 学習時間の目安
- 60分
- Unityバージョン
- 2021.3.16f1
プロジェクトの作成からCoreの機能を導入していき、VContainerやUniRxも導入しExtrealが想定するアプリケーションアーキテクチャを構築します。
Create project
まずはプロジェクトを作りましょう。
Unity Hubから新しいプロジェクトを作成します。
- エディターバージョン:
2021.3.16f1
- テンプレート:
3D
- プロジェクト名:
ExtrealCoreLearning
Unityエディタが起動したら実行して問題がないことを確認します。
SampleSceneを実行してみましょう。 シーンに何も配置していないので何も起こりません。 Consoleに何も出ていなければ成功です。
Create entry point
アプリケーション用のディレクトリとエントリーポイントとなるAppシーンを作ります。
- 初期設定してある
Assets/Scenes
ディレクトリを削除します。 Assets/ExtrealCoreLearning/App
となるディレクトリを作ります。- Appディレクトリに
App
シーンを作成します。
Add Logging
Loggingをアプリケーションに追加します。
Package ManagerからLoggingを追加します。
Git URL指定でLoggingを追加します。 Git URLはLoggingのPackageから取得します。
Package ManagerにExtreal.Core.Logging
が追加されれば成功です。
バージョンは実施タイミングにより異なります。
スクリプトからLoggingを参照できるようにします。
アプリケーションのAssembly DefinitionにLoggingを設定します。
- ExtrealCoreLearningディレクトリに
ExtrealCoreLearning
という名前のAssembly Definitionを作成します。 - Root Namespaceに
ExtrealCoreLearning
を指定します。 - Assembly Definition Referencesに
Extreal.Core.Logging
を追加します。 - Assembly Definition ReferencesのUse GUIDsのチェックは外します。
開発用にDebugレベルのログを出力したいのでLoggingの設定を追加します。
LoggingのデフォルトのログレベルはInfoのため、Loggingの設定を追加してログレベルを変更します。
LoggingのSettingsを参考にしてAppTest
スクリプトをAppシーンに作成します。
- AppTestスクリプトをAppディレクトリに作成します。
- AppTestスクリプトをアタッチしたGameObjectをAppシーンに作成します。
using Extreal.Core.Logging;
using UnityEngine;
namespace ExtrealCoreLearning.App
{
public class AppTest : MonoBehaviour
{
private static void InitializeApp()
{
const LogLevel logLevel = LogLevel.Debug;
LoggingManager.Initialize(logLevel: logLevel);
}
private void Awake()
{
InitializeApp();
}
}
}
このままではLoggingの動作確認ができないので、Loggingの初期化直後にログ出力するように処理を追加します。
private static void InitializeApp()
{
const LogLevel logLevel = LogLevel.Debug;
LoggingManager.Initialize(logLevel: logLevel);
var logger = LoggingManager.GetLogger(nameof(AppTest));
if (logger.IsDebug())
{
logger.LogDebug("Hello, world!");
}
}
Appシーンを実行してみましょう。
ConsoleにHello, world!
と出力されれば成功です。
Add Stage Navigation
Stage Navigationをアプリケーションに追加します。
UniTaskとUniRxはOpenUPMで提供されているためOpenUPMの設定を追加します。
Edit
-> Project Settings...
-> Package Manager
からScoped RegistriesにOpenUPMを追加します。
- Name:
OpenUPM
- URL:
https://package.openupm.com
- Scope(s):
- UniTask
com.cysharp
- UniRx
com.neuecc
- UniTask
UniTaskとUniRxを追加できたのでStage Navigationをスクリプトから使えるようにします。
Loggingの時と同じ手順でPackage ManagerとAssembly Definitionを操作します。 Stage NavigationのGit URLはStage NavigationのPackageから取得します。 Assembly DefinitionにUniTaskとUniRxも設定します。
ステージ遷移を試すためタイトル画面を追加し、アプリ起動後すぐにタイトル画面に遷移させてみましょう。
まずはタイトル画面を追加します。
- Appディレクトリと同じ階層に
TitleScreen
ディレクトリを作成します。 - その中に
TitleScreen
シーンを作成します。 - TitleScreenシーンのカメラなど初期設定されているGameObjectを削除しシーンを一旦空にします。
- シーンに
Canvas
を追加します。インスペクタで次の設定をします。- Canvas Scaler
- UI Scale Mode:
Scale With Screen Size
- UI Scale Mode:
- Canvas Scaler
- Canvasの下に
Image
を追加します。インスペクタで次の設定をします。- Rect Transform
- Anchor Presets
- X:
stretch
- Y:
stretch
- X:
- LeftからButtomまで全て
0(Zero)
- Anchor Presets
- Image
- Color
- Hexadecimal:
FF6F61
- Hexadecimal:
- Color
- Rect Transform
- Imageの下に
Text - TextMeshPro
を追加します。Extreal Core Learning
というタイトルを作ります。- サイズやポジションは自由に決めてください。
- TMP Importerが表示されたら
Import TMP Essentials
を選択します。TextMeshProの初回利用時のみTMP Importerが表示されます。インポートされたらTMP Importerを閉じます。
- Imageの下に
Button - TextMeshPro
を追加します。Go
というボタンを作ります。- こちらもサイズやポジションは自由に決めてください。
タイトル画面を追加できたのでステージ設定を作成します。
Stage NavigationのSettingsを参照してステージ設定を作成します。 ステージ設定で作成する3つのタイプはAppディレクトリに配置します。
public enum StageName
{
TitleStage = 0,
}
public enum SceneName
{
TitleScreen = 100,
}
3つのタイプが作成できたらStageConfig
オブジェクトを作ります。
- Assetsメニューから
StageConfig
オブジェクトをAppディレクトリに作成します。 - StageConfigオブジェクトにタイトル画面のステージを設定します。
これでステージ遷移の準備が整ったのでアプリ起動後すぐにタイトル画面に遷移させる処理を追加します。
- AppTestスクリプトにSerializeFieldとStartメソッドを追加します。
- インスペクタでStageConfigオブジェクトを指定します。
using Extreal.Core.Logging;
using Extreal.Core.StageNavigation;
using UnityEngine;
namespace ExtrealCoreLearning.App
{
public class AppTest : MonoBehaviour
{
private static void InitializeApp()
{
// Omitted due to no changes
}
private void Awake()
{
// Omitted due to no changes
}
[SerializeField] private StageConfig stageConfig;
private void Start()
{
var stageNavigator = new StageNavigator<StageName, SceneName>(stageConfig);
stageNavigator.ReplaceAsync(StageName.TitleStage);
}
}
}
Appシーンを実行してみましょう。
この状態でAppシーンを実行すると次のエラーになります。
Build SettingsのScenes In BuildにTitleScreen
シーンを追加します。
もう一度Appシーンを実行してみましょう。
タイトル画面が表示され、Consoleに[Debug:StageNavigator] Transitions to 'TitleStage'
と出ていれば成功です。
Apply MV(R)P pattern
Extrealが提供するCoreの機能は以上となりますが、VContainerを追加してExtrealが想定するアプリケーションアーキテクチャに近づけていきたいと思います。
タイトル画面の遷移先となるアバター選択画面を追加します。
アバター選択画面は遷移先として使用するだけなのでタイトル画面をコピーして作ります。
- Appディレクトリと同じ階層に
AvatarSelectionScreen
ディレクトリを作成します。 - その中に
AvatarSelectionScreen
シーンを作成します。 - AvatarSelectionScreenシーンのカメラなど初期設定されているGameObjectを削除しシーンを一旦空にします。
- TitleScreenシーンのCanvasとEventSystemをコピーしてAvatarSelectionScreenシーンにペーストします。
- タイトルを
Avatar Selection
に変更します。
続けてステージ設定を追加します。
- StageNameに
AvatarSelectionStage
、SceneNameにAvatarSelectionScreen
を追加します。 - StageConfigオブジェクトのインスペクタで
AvatarSelectionStage
を追加します。
Build SettingsのScenes In BuildにAvatarSelectionScreen
シーンを追加します。
UIとステージ設定の準備が整いました。
VContainerをアプリケーションに追加します。
VContainerはUniTaskやUniRxと同様にOpenUPMから取得します。
Edit
-> Project Settings...
-> Package Manager
からOpenUPMのScope(s)を追加します。
- VContainer:
jp.hadashikick
続けてPackage ManagerとAssembly DefinitionにVContainerを追加します。
- Package ManagerでPackages:
My Registries
を選択しVContainerをインストールします。 - アプリケーションのAssembly DefinitionにVContainerを設定します。
これでVContainerが使える状態になりました。
VContainer
VContainerを使って確認用に作成したAppTestを作り変えます。
まずエントリーポイントとなるPresenterスクリプトを作成します。
Appディレクトリに作成します。 Appシーンが開始するとタイトル画面に遷移させます。
using System.Threading;
using Cysharp.Threading.Tasks;
using Extreal.Core.StageNavigation;
using VContainer.Unity;
namespace ExtrealCoreLearning.App
{
public class AppPresenter : IAsyncStartable
{
private StageNavigator<StageName, SceneName> stageNavigator;
public AppPresenter(StageNavigator<StageName, SceneName> stageNavigator)
{
this.stageNavigator = stageNavigator;
}
public async UniTask StartAsync(CancellationToken cancellation)
{
await stageNavigator.ReplaceAsync(StageName.TitleStage);
}
}
}
次にVContainerのLifetimeScopeとしてScopeスクリプトを作成します。
Appディレクトリに作成します。 ScopeスクリプトのAwakeをオーバーライドしてVContainerの処理よりも先にLoggingの設定を行います。
using Extreal.Core.Logging;
using Extreal.Core.StageNavigation;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace ExtrealCoreLearning.App
{
public class AppScope : LifetimeScope
{
[SerializeField] private StageConfig stageConfig;
private static void InitializeApp()
{
const LogLevel logLevel = LogLevel.Debug;
LoggingManager.Initialize(logLevel: logLevel);
var logger = LoggingManager.GetLogger(nameof(AppScope));
if (logger.IsDebug())
{
logger.LogDebug("Hello, world!");
}
}
protected override void Awake()
{
InitializeApp();
base.Awake();
}
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterComponent(stageConfig).AsImplementedInterfaces();
builder.Register<StageNavigator<StageName, SceneName>>(Lifetime.Singleton);
builder.RegisterEntryPoint<AppPresenter>();
}
}
}
StageConfigとStageNavigator、エントリーポイントとしてAppPresenterを登録しています。 この登録により、StageConfigがStageNavigatorに設定され、StageNavigatorがAppPresenterに設定されます。 このようにVContainerが提供するDIコンテナを使ってオブジェクトの構造を作り上げ、各オブジェクトが処理を実行できるようにします。
ScopeスクリプトをAppシーンに設定します。
- Appシーンに
Scope
という名前でAppScopeスクリプトをアタッチしたGameObjectを作成します。 - StageConfigオブジェクトをインスペクタで設定します。
- 不要になったのでAppTestスクリプトとAppTestスクリプトをアタッチしたAppシーンのGameObjectを削除します。
Appシーンを実行します。 先ほどと同様にタイトル画面とConsoleのログ出力が出ていれば成功です。
UniRx
UniRxを使ってタイトル画面のGoボタンを実装します。
タイトル画面に対応するViewスクリプトを作成します。
TitleScreenディレクトリに作成します。
using System;
using UniRx;
using UnityEngine;
using UnityEngine.UI;
namespace ExtrealCoreLearning.TitleScreen
{
public class TitleScreenView : MonoBehaviour
{
[SerializeField] private Button goButton;
public IObservable<Unit> OnGoButtonClicked
=> goButton.OnClickAsObservable().TakeUntilDestroy(this);
}
}
UniRxを使ってGoボタンが押された場合にイベントを通知するOnGoButtonClicked
を定義しています。
次にGoボタンが押された場合にアバター選択画面に遷移させるPresenterスクリプトを作成します。
TitleScreenディレクトリに作成します。
using System;
using Cysharp.Threading.Tasks;
using Extreal.Core.StageNavigation;
using ExtrealCoreLearning.App;
using UniRx;
using VContainer.Unity;
namespace ExtrealCoreLearning.TitleScreen
{
public class TitleScreenPresenter : IInitializable, IDisposable
{
private StageNavigator<StageName, SceneName> stageNavigator;
private TitleScreenView titleScreenView;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
public TitleScreenPresenter(StageNavigator<StageName, SceneName> stageNavigator,
TitleScreenView titleScreenView)
{
this.stageNavigator = stageNavigator;
this.titleScreenView = titleScreenView;
}
public void Initialize()
{
titleScreenView.OnGoButtonClicked.Subscribe(_ =>
{
stageNavigator.ReplaceAsync(StageName.AvatarSelectionStage).Forget();
}).AddTo(compositeDisposable);
}
public void Dispose()
{
compositeDisposable?.Dispose();
}
}
}
Goボタンのイベント通知とアバター選択画面への遷移をマッピングしています。
最後にViewやPresenterを紐づけるScopeスクリプトを作成します。
TitleScreenディレクトリに作成します。
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace ExtrealCoreLearning.TitleScreen
{
public class TitleScreenScope : LifetimeScope
{
[SerializeField] private TitleScreenView titleScreenView;
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterComponent(titleScreenView);
builder.RegisterEntryPoint<TitleScreenPresenter>();
}
}
}
ViewスクリプトとScopeスクリプトをTitleScreenシーンに設定します。
- TitleScreenシーンに
View
という名前でTitleScreenViewスクリプトをアタッチしたGameObjectを作成します。 - Buttonオブジェクトをインスペクタで設定します。
- TitleScreenシーンに
Scope
という名前でTitleScreenScopeスクリプトをアタッチしたGameObjectを作成します。 - Viewオブジェクトをインスペクタで設定します。
Appシーンを実行してみましょう。
この状態でAppシーンを実行すると次のエラーになります。
TitleScreenPresenterにStageNavigatorを設定していますがスコープに登録されていないためエラーとなっています。 StageNavigatorはAppScope、TitleScreenPresenterはTitleScreenScopeと異なるスコープのためこのエラーが発生しています。
VContainerではスコープの親を指定してオブジェクトの検索範囲を親まで広げることができます。 TitleScreenScopeのインスペクタでParentにAppScopeを指定するとこのエラーが解消します。
このようにスコープは階層を意識して作成します。 共通利用されるスコープをより親となるように全体のスコープを設計します。
もう一度Appシーンを実行してみましょう。
タイトル画面からアバター選択画面に遷移できれば成功です。
Next Step
これでCoreのハンズオンは終了です。 お疲れさまでした。
このハンズオンを通じてExtrealが想定するアプリケーションアーキテクチャに必要な機能を構築済みです。 次のステップとしてハンズオンで構築したアーキテクチャがより本格的なアプリケーションでどのように使われるのか関心があると思います。 その期待に応えるため、より本格的な実装例としてSample Applicationを提供しています。 ぜひSample Applicationをご覧ください。