ゲーム フレームワーク コンポーネント マネージャー は、Modular Gameplay プラグイン の Game Instance Subsystem であり、Game Feature プラグイン で使用するために設計された機能を提供します。 このサブシステムに実装されている機能は、Game Feature Actions で使用して拡張性をサポートすることができます。Game Feature Actions は、一般的なゲームプレイ コードによって、異なるゲームプレイ オブジェクト間の通信を調整するために使用されます。このマネージャーは 2 つの基本システム、拡張ハンドラ と 初期化状態 を実装しています。
拡張ハンドラ システム
拡張ハンドラ システムを使用すると、ゲーム機能が有効になっている場合にゲーム オブジェクトを修正することができます。このシステムには次の 2 つの部分があります。アクタ は拡張対象を登録する レシーバー として機能し、拡張ハンドラ はイベントに応答して起動されるデリゲートです。これらのイベントには、新しいレシーバーの処理、既存のレシーバーの削除、およびゲームプレイ コードによって呼び出される任意のイベントが含まれます。
レシーバーと拡張ハンドラ
レシーバーとして正しく登録するには、アクタは PreInitializeComponents メソッドから AddGameFrameworkComponentReceiver 関数を呼び出し、EndPlay メソッドから RemoveGameFrameworkComponentReceiver 関数を呼び出す必要があります。これにより、アクタは通常のコンポーネントの初期化時にレシーバーとして登録され、アクタが削除されたり無効化されたときに登録が解除されます。
レシーバーは SendGameFrameworkComponentExtensionEvent 関数を呼び出すことで、任意のイベントを送信することができます。後述の 初期化状態システム とは異なり、これらの拡張イベントはステートフルではなく、現在アクティブになっているハンドラのみを変更します。
拡張ハンドラを正しく登録するために、GameFeatureAction_AddComponents などのクラスは手動のデリゲートを登録する AddExtensionHandler を呼び出すか、目的のコンポーネントを自動的に追加するラッパー関数を呼び出す AddComponentRequest を呼び出すことができます。
どちらの場合も、add 関数が返すハンドルは、配列と同様に格納される必要があります。これは、デリゲートは、返されたハンドル構造体へのライブの共有ポインタ参照がある場合に限り、登録された状態を維持するためです。
Lyra サンプル
このシステムの使用方法の例では、 Lyra サンプル ゲーム の実装を確認することができます。ALyraCharacter クラスは、ゲーム内のすべてのキャラクターに使用され、レシーバーとして登録する処理を行う AModularCharacter クラスを継承しています。また、この関数を手動で呼び出すことで UI 拡張を有効にする LyraHUD アクタを確認することができます。
ShooterCore などの Lyra の Game feature プラグインは、エンジンで定義された UGameFeatureAction_AddComponents アクションを使用して、スポーンされたアクタにコンポーネントを追加します。Lyra では、UGameFeatureAction_AddInputBinding などのゲーム固有のアクションを使用して、一部のゲーム固有のケースを処理しています。
ゲーム固有のアクションである UGameFeatureAction_AddInputBinding では、HandlePawnExtension 関数が手動拡張ハンドラとして登録されており、いくつかの異なる拡張イベントに応答します。NAME_ExtensionRemoved、NAME_ExtensionAdded などのイベントは、関連するすべてのアクタに対して拡張ハンドラが最初に追加または削除されたときに呼び出されます。これは、ゲーム固有の NAME_BindInputsNow イベントに応答します。このイベントは、機能固有の入力イベントをバインドするときになると LyraHeroComponent によって発行されます。
初期化状態システム
初期化状態システム (Init State) は、ゲーム ワールドのアクタにアタッチされている各種機能 (通常はコンポーネントによる実装) の初期化および一般的なライフサイクルを追跡するための関数を提供します。このシステムは、ゲーム全体の状態がグローバルに定義されており、作成から完全な初期化まで直線的に配置されているため、一般的なゲームプレイのステート マシンとして機能することを意図していません。
アクタ上のコンポーネントの初期化の同期は、特にネットワーク レプリケーションがかかわる複雑なプロセスです。このシステムでは、登録関数と通知関数を提供することで、システムをより簡単に調整できます。低レベルの関数はゲーム フレームワーク コンポーネント マネージャーによって実装されます。また、指定の機能を実装したコンポーネント (または他のゲームプレイ オブジェクト) によって継承できるオプションのネイティブの GameFrameworkInitStateInterface があります。
アクタの機能
このシステムに登録されたアクタは、複数の アクタの機能 を備えており、それらは一意の 名前 として定義されます。これらの名前はゲームによって定義され、ネイティブ クラス名や機能的な特長に対応させることができます。
このサブシステムでは、アクタに登録されたすべての機能の Init State (初期化状態) と Implementer オブジェクト (多くの場合、コンポーネント) を追跡しています。GameFrameworkInitStateInterface を実装したオブジェクトでは、機能名は GetFeatureName インターフェース関数で返され、他のすべての操作に使用されます。
初期化状態
初期化状態 は ゲームプレイ タグ として実装されており、GameInstance の初期化中に RegisterInitState を呼び出してサブシステムに登録する必要があります。これらの状態は順に登録され、ゲーム内のすべてのアクタで共有されます。たとえば、ゲームでは InitState.Spawning、InitState.Ready を使用したシンプルな 2 ステート システムや、以下の Lyra サンプル などの複雑なシステムをサポートしています。
状態を報告およびクエリする
このシステムで登録されたすべての機能は、初期化状態を変更するたびに ゲーム フレームワーク コンポーネント マネージャーに報告する必要があります。これは、このマネージャーが後続のクエリの実行のためにこの状態を格納するためです。マネージャーが状態の変更に関して制限を強制することはなく、柔軟に設計されています。
GameFrameworkInitStateInterface はいくつかの関数をオーバーライドすることですばやく実装できる、シンプルな C++ ステート マシンのフレームワークを提供します。
| 関数 | オーバーライドの説明 |
|---|---|
CanChangeInitState |
この関数は、要求された状態遷移が許可されている場合に true を返すようにオーバーライドする必要があります。これは必要なデータが使用可能かどうかを確認するチェックを実装します。 |
HandleChangeInitState |
この関数は、特定の状態遷移で発生する必要のあるオブジェクト固有の変更を実行するためにオーバーライドする必要があります。 |
CheckDefaultInitialization |
その機能のデフォルトの初期化パスを辿ることを試みる場合はオーバーライドすることができます。ContinueInitStateChain 関数が初期化状態の配列で呼び出された場合、CanChangeInitState および HandleChangeInitState を呼び出し、ステート チェーンで可能な限りの距離を取得します。この関数は、初期化を進めることができる OnRep 関数などから呼び出す必要があります。 |
さらに、サブシステムとインターフェースは、登録関数およびクエリ関数を提供します。
| 関数 | 説明 |
|---|---|
RegisterInitStateFeature |
システムに登録しますが、状態を設定しません。これはコンポーネント OnRegister から呼び出すうえで役立ちます。 |
UnregisterInitStateFeature |
これは、通常、システムから登録を解除して通知デリゲートをアンバインドするために EndPlay から呼び出される必要があります。 |
HasReachedInitState |
これは、機能が指定された状態、または初期化順序の後の状態のいずれかに到達したかどうかを確認するために呼び出すことができます。 |
HaveAllFeaturesReachedInitState |
すべての機能が特定の状態に達したかどうかを確認するために、マネージャーで呼び出されます。これは拡張機能を調整するうえで役立ちます。なぜなら、次の状態に移行する前に、他のすべての機能が準備完了になるのを待機するように中心となる機能を設定できるためです。 |
状態の変化を登録する
このシステムで最も役立つ部分は、初期化状態の変化を登録し、特定の状態になった後にデリゲートを呼び出す機能です。RegisterAndCallForActorInitState などの登録関数は、機能が特定の状態に達したときに指定されたデリゲートを呼び出し、すでにその状態に達している場合は直ちにそのデリゲートを呼び出します。
RegisterAndCallForClassInitState は、クラス名を指定して呼び出すことができ、任意の機能がその状態に到達するのを待ちます。これは、グローバルな初期化をリッスンするうえで役立ちます。これらの関数は、C++ コードまたはブループリントから呼び出すことができ、インターフェース上のバージョンで機能名を入力します。デリゲート実行ロジックは、連続して発生する複数の状態遷移を処理するように設計されており、関連するデリゲートがすべて呼び出されます。
操作性を実現するため、BindOnActorInitStateChanged と OnActorInitStateChanged をインターフェースで使用すると、同じアクタ上の他の機能に対して行われた変更をすばやくリッスンすることができます。これを使用することで、機能の初期化状態を進めることができる CheckDefaultInitialization などの関数を呼び出すことができます。
Lyra サンプル
このシステムの使用方法の例として、5.1 以降のバージョンの Lyra サンプル ゲーム の実装を確認してください。Lyra 5.0 リリースは、初期化状態システムより前に作成されており、複数の競合状態が含まれていますが、このシステムで対処することができます。以下は、ULyraGameInstance::Init での登録に従って、Lyra サンプルで使用されている状態です。
| 初期化状態 | 説明 |
|---|---|
InitState.Spawned |
この機能は BeginPlay から呼び出され、スポーンと初期レプリケーションを完了しました。 |
InitState.DataAvailable |
その機能が必要とするすべてのデータは、レプリケートが必要な可能性のある他のアクタへの依存関係も含めて、レプリケートまたはロードされました。 |
InitState.DataInitialized |
すべてのデータが使用可能になった後、ゲームプレイ機能を追加するなどの他の初期化アクションを完了するために使用されます。 |
InitState.GameplayReady |
オブジェクトがすべての初期化を完了し、通常のゲームプレイで操作できる状態になっています。 |
このシステムを使用する 2 つの主要なコンポーネントは全体の初期化を調整する ULyraPawnExtensionComponent とカメラや入力などのプレイヤーが制御するシステムの初期化を処理する ULyraHeroComponent です。
両方のコンポーネントの初期化は複数のソースからレプリケートされたデータに依存します。また、コンポーネント マネージャーに存在を知らせるために OnRegister メソッドから RegisterInitStateFeature 関数を呼び出します。どちらのコンポーネントも、最初のレプリケーションが完了した後に、BeginPlay メソッドから CheckDefaultInitialization 関数を呼び出します。
これらの 2 つのコンポーネントには完全な初期化ステート マシンが必要です。これは、ダウンロードに時間がかかる LyraPlayerState などの他のアクタによってレプリケートされたデータに依存します。以下のリストに、Lyra キャラクターの初期化に関する全体的なタイムラインを示します。
-
キャラクターがクライアントとサーバー上で最初にスポーンされると、2 つの初期化状態コンポーネントや LyraAbilitySystemComponent などを含むすべてのコンポーネントがアタッチされて登録されます。
-
キャラクターで
BeginPlayが呼び出されると、すべてのコンポーネントでBeginPlayの呼び出しを試行します。サーバー上ではこれはすぐに実行されますが、クライアント上では、レプリケートされたすべてのプロパティが初期データを送信するまでBeginPlayは呼び出されません。これは、各コンポーネントがどのくらいのデータをレプリケートする必要があるかに応じて、異なるタイミングで実行されます。 -
Hero コンポーネントまたは Lyra Pawn コンポーネントで
BeginPlayが呼び出されると、これらのコンポーネントはBindOnActorInitStateChangedを呼び出して、初期化状態の変更をリッスンし、次にCheckDefaultInitializationを呼び出して 4 ステートの初期化チェーンに辿ろうとします。この時点で、両方のコンポーネントはInitState.Spawnedに到達し、初期化の続行を試行します。 -
Hero コンポーネントが
InitState.DataAvailableに遷移しようとすると、プレイヤーの状態と入力コンポーネントの準備ができたかどうかを確認します。データが利用できない場合、ステート マシンは、何らかの要素がCheckDefaultInitializationを呼び出すまで停滞します。必要なデータが使用可能な場合は、DataAvailableに遷移しますが、まだDataInitializedに遷移することはできません。 -
Pawn Extension コンポーネントが
CheckDefaultInitializationを呼び出す場合、可能であれば、まず他のコンポーネント (Hero コンポーネントなど) に初期化ステート マシンを前進させるように指示します。次に、自身の状態をInitState.DataAvailableまで進めようとするときに、PawnDataとコントローラーが完全に使用可能かどうかを確認します。 Pawn Extension コンポーネントはさまざまなOnRep関数からCheckDefaultInitializationを呼び出し、重要なクロスアクタ参照がレプリケートを終了した後にステート マシンを先に進めようとします。もう 1 つの選択肢は、ネイティブのティック関数から初期化関数を呼び出すことです。 -
Pawn Extension コンポーネントが
InitState.DataInitializedに進もうとすると、他のすべての機能 (Hero コンポーネントなど) がDataAvailableに到達するまで、進みません。実際に遷移すると、Hero コンポーネントなどリッスンしているその他すべての要素に対してOnActorInitStateChanged関数を有効にします。 -
この場合、拡張コンポーネントは
InitState.DataInitializedに移行します。Hero コンポーネントもDataInitializedに移行します。この移行時に、ゲームプレイ アビリティが作成され、プレイヤーの入力にバインドされます。 -
Hero コンポーネントと Pawn Extension コンポーネントの両方が
InitState.GameplayReadyに遷移し、この状態になるのを待機するために登録した W_Nameplate などのクラスでブループリント コールバックが有効になります。
Lyra キャラクターの初期化フローは複雑ですが、多くのネットワーク ゲームでも、同様に複雑な初期化フローを必要とします。初期化状態システムは、複雑なシステムのセットアップを容易にし、競合状態やランダムな遅延ループを回避するように設計されています。