【UE5】マルチプレイにおけるPawnのIsLocallyControlled()の各状況での値まとめ

# 概要
マルチプレイにおけるPawnのIsLocallyControlled()の各状況での値まとめです。   
非常に限定的ですが、**ListenServer(Authority)環境のIsLocallyControlled()がtrueのPawnをAGameModeBase::RestartPlayerした際に、BeginPlay()のタイミングではIsLocallyControlled()がfalseになる**ようです。
知らないと意図しない挙動に繋がる可能性があると思われます。

また、事前にググって調べてみたところ、Client環境ではControllerの同期が遅れることがあるから数フレームはIsLocallyControlled()が正常な判定が取得できないという説がありました(※ServerTravelの際にも)。
しかし、今回検証してみたところ、APawn::ControllerはPawnがClient環境で生成される段階で同期されており、APawn::BeginPlay()でも問題なくIsLocallyControlled()の判定を行うことができました。
検証したのが非常に軽いレベル＆ローカルのLANで通信が非常に速いのでそうなってしまったのかもしれません。
その辺に詳しい方がおられましたら教えていただけると幸いです。

検証のためにPawnのIsLocallyControlled()の情報をログ出力する検証用C++プロジェクトを作成し、GitHubにアップロードしました。

https://github.com/ogamita777/MultiControllerTest

何かしら気になる方は、ダウンロードして手元で試していただければと思います。

■確認した環境
- UnrealEngine：5.5.4
- エディタビルド構成：DebugGame Editor
- パッケージビルド構成：Development

■ゲームプレイ設定
〇エディタ
- Run Under One Process：true
- NetMode: PlayAsListenServer
- Play Number Of Clients(Number of Players)：2

〇パッケージ(コマンドライン引数)
- ListenServer環境：`TestMap?listen?bIsLanMatch=1 -windowed -ResX=960 -ResY=540`
- Client環境：`[IPアドレス] -windowed -ResX=960 -ResY=540`


# まとめ
最終的にIsLocallyControlled()がtrueになるPawnの各状況でのIsLocallyControlld()の値は下記となります。

■ゲーム開始時
|  | BeginPlay() | Tick() |備考|
|---|---|---|---|
|ListenServer(Authority)環境  |true  |true  ||
|Client環境  |true  |true  |同期が遅れて正常な判定が取れないという説があったが確認できず|

■AGameModeBase::RestartPlayer()したとき
|  |BeginPlay()  | Tick() |備考|
|---|---|---|---|
|ListenServer(Authority)環境  |**false**  |true  ||
|Client環境  |true  |true  |同期が遅れて正常な判定が取れないという説があったが確認できず|


# 何故そうなるのか？

AGameModeBase::RestartPlayerするときの実行順が下記のようになっているようです。

AGameModeBase::RestartPlayer()
↓
APawnのSpawnActor()
↓
**APawn::BeginPlay()**
↓
**AController::Possess()**
↓
APawn::Tick()

APawn::BeginPlay()のタイミングではAController::Possess()がまだ呼ばれておらず、APawn::Controllerがまだ設定されておりません。Controllerがnullptrになっている場合はIsLocallyControlled()はfalseを返します。

ゲーム開始時はAPawn::BeginPlay()とAController::Possess()の順番が入れ替わっており、APawn::Controllerが先に設定されているから大丈夫のようです。

APawnのSpawnActor()
↓
**AController::Possess()**
↓
**APawn::BeginPlay()**
↓
APawn::Tick()

BeginPlayが呼ばれるタイミングは下記のようです。
- ゲーム開始時：事前に作成が必要な全Actorが作成されてから最後に呼ばれる
- ゲーム開始後：SpawnActorが呼ばれたタイミングで呼ばれる

# 参考文献
- [[5.3] "is locally controlled" returns false if instanced through RestartPlayeratPlayerStart - Programming & Scripting / Blueprint - Epic Developer Community Forums](https://forums.unrealengine.com/t/5-3-is-locally-controlled-returns-false-if-instanced-through-restartplayeratplayerstart/1863886
)
    - 本記事の関連するフォーラムの質問だと思われます。質問者の方はBeginPlay()で処理を行わず、遅延を入れることで問題を解決したようです
- [プレイヤー キャラクターをリスポーンする | Unreal Engine 5.5 ドキュメンテーション | Epic Developer Community](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/respawning-a-player-character)
    - RestartPlayer()する場合の公式のドキュメントです。このドキュメントを参考にしてRestartPlayer()を作成いたしました
- [Unreal Engine でのネットワーキングの概要 | Unreal Engine 5.5 ドキュメンテーション | Epic Developer Community](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/networking-overview-for-unreal-engine)
    - > コンストラクション時にはポーンにコントローラーが割り当てられていない可能性があるため、カスタム Pawn クラスの コンストラクタで IsLocallyControlled を使用しないでください
        - (↑のページより引用)とあります。コンストラクション(コンストラクタ)時とはBeginPlay()のことを指すのだと思われます