【UE4】Player ControllerのControl Rotationとは何者か？

:::note info
対象バージョンはUE4.27.2です。
:::

# なんとなくしか分かってなかったUse Controller Rotation Pitch/Yaw/Rollについて

Pawnを継承して作ったアクタの詳細パネルに現れるこの(↓)パラメータ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/7366f44f-400a-45c6-e91f-c7fb34b3cdd6.png)
…の最初の３項目：

- Use Controller Rotation Pitch
- Use Controller Rotation Yaw
- Use Controller Rotation Roll

今まで、なんとなくOn/Offしてみて思い通りに動いたら良しとしてきたパラメータである。
これをエンジンソースをみてちゃんと調べたのでメモを残しておく。

## 標準のADefaultPawnを真似したBlueprintアクタを組んでみる。

まずはデフォルトで作られるDefault Pawnを真似たBlueprintアクタを組んでみる。
参考にしたエンジンソースは`DefaultPawn.h`と`DefaultPawn.cpp`。クラス名は`ADefaultPawn`。非常にシンプルでBlueprint化もしやすい。

### Project Settings > Input

お馴染みのパターンでインプットバインディングを作っておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/5abf4e3e-ce1d-8266-8be3-07f9df397b83.png)
今回はWASDとマウスだけに対応することにする。

### BP_DefaultPawn

Pawnを継承したアクタを作る。
コンポーネントは以下のようにした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/5168030b-1cf8-ec86-45b1-15d0e5e38254.png)

- ルートコンポーネントとして Billboardを使用。
    - 詳細パネルのRendering > Hidden in Game を Falseにしておく。
    - Billboardにした理由は単なる好み。個人的にあのアイコンが好きなだけ。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/80e95593-4d7f-c1ff-c462-907417546e97.png)

- 前後左右(と上下)の移動用に FloatingPawnMovementコンポーネントを追加。
    - こちらは特にパラメータの設定は必要ない。
    - 好みでMax Speedなどを変えてもよいかもしれない。

C++のADefaultPawnを参考にして、イベントグラフは以下のようにした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/468c7b4b-4c65-ac9b-655f-64576a9c5642.png)

- WASDによる上下左右(と上下)の移動はFloating Pawn Movementにインプットベクトルを与える。
- マウスによる向きの変更はAdd Controller Yaw/Pitch Inputを使う。

最後に例のUse Controller Rotation Pitch/Yaw/Rollをセットする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/80d31015-fc2e-e520-17fa-7fe22693a524.png)
今回はUse Controller Rotatiaon Rollは使わない。

ここまで出来たら実行する。
いつものDefault Pawnと同じ挙動をするはずである。

# ではエンジンソースに潜っていく。

## APawn::FaceRotation()

まずは、Use Controller Rotation Pitch/Yaw/Rollでエンジンソース内を検索すると、`APawn::FaceRotation()`が見つかる。
ソースコードを丸ごとコピペするのは憚られるので、やっていることが分かる程度に抜粋して書くと次のようになる。

```c++:Pawn.cpp&#x20;(888行目,UE4.27.2)
void APawn::FaceRotation(FRotator NewControlRotation, float DeltaTime)
{
    if (bUseControllerRotationPitch || bUseControllerRotationYaw || bUseControllerRotationRoll)
    {
        const FRotator CurrentRotation = GetActorRotation();
        if (/*not*/!bUseControllerRotationPitch) { NewControlRotation.Pitch = CurrentRotation.Pitch; }
        if (/*Yaw*/) { /*同様*/ }
        if (/*Roll*/) { /*同様*/ }

        SetActorRotation(NewControlRotation);
    }
}
```

- Use Controller Rotation Pitch/Yaw/Rollのうちどれか一つでもTrueならば
    - PawnのRotation(`CurrentRotation`)のPitch/Yaw/Rollを引数`NewControlRotation`で上書きする。
    - 最後に`SetActorRotation()`でPawnのRotationをセットする。
- Use Controller Rotation Pitch/Yaw/RollすべてがFalseならば
    - 何もしない。(PawnのRotationは現状維持される)

つまりは、PawnのRotationをNewControlRotationなる回転で上書きしようという関数である。
では、このNewControlRotationなる値は何者なのか？

## APlayerController::UpdateRotation()

その`APawn::FaceRotation()`の呼び出し元の`APlayerController::UpdateRotation()`を見てみる。
コードの記述は、例によってニュアンスが分かる程度にとどめておく。

```c++:PlayerController.cpp&#x20;(924行目,UE4.27.2)
void APlayerController::UpdateRotation(float DeltaTime)
{
    FRotator DeltaRotation(RotationInput);
    FRotator ViewRotation = GetControlRotation();

    // 実際はAPlayerCameraManager->ProcessViewRotation()の処理
    {
        ViewRotation += DeltaRotation;
        DeltaRotation = FRotator::ZeroRotator;
    }

    SetControlRotation(ViewRotation);

    const APawn* P = GetPawnOrSpectator();
    if (P) { P->FaceRotation(ViewRotation, DeltaTime); }
}
```

`FaceRotation()`のところで話題にあげた引数`NewControlRotation`は、`APlayerController::UpdateRotation()`の`ViewRotation`に相当することが分かる。
つまり、Use Controller Rotation Pitch/Yaw/Rollがtrueになっていると、PawnのRotation (のPitch and/or Yaw and/or Roll)は、このViewRotationで上書きされる。
ここで、`ViewRotation`の挙動を見てみると：

1. `ViewRotation`は<b>Control Rotation</b>なるもので初期化される。
1. `ViewRotation`は`DeltaRotation` (=`RotationInput`)を足すことで更新される。
1. その結果を<b>Control Rotation</b>なるものにセットする。
1. その`ViewRotation` (=Control Rotation)でPawnのRotationを更新する。

という流れになっている。ここでわかることは：

- どうやらPlayer Controllerが持つ<b>Control Rotation</b>なる回転の方向にPawnが向けられているようだ。
-  <b>Control Rotation</b>なる回転は別名View Rotationと言ってもよい。「ビュー」つまり「カメラ」に相当するもののようだ。
- もう一つ、Player Controllerのメンバ変数`RotationInput`は別名Delta Rotationと言ってもよい。つまりはこのフレームでの回転の変化量と想像できる。

## APlayerController::TickActor()

上記の`UpdateRotation()`は以下の流れで呼び出されている。
例によってニュアンスを伝えるためのダミーコードを記載する。(かなりの量のコードを省略しているので注意)

```c++:PlayerController.cpp
void APlayerController::TickActor(float DeltaSeconds, /*以下引数省略*/)
{
    // PlayerInputが存在するならPlayerTick()を呼び出す。 // PlayerController.cpp 4704行目 (UE4.27.2)
    if (PlayerInput) { PlayerTick(DeltaSeconds); } 
    // 最後にRotationInputをゼロクリアする。 // PlayerController.cpp 4748行目 (UE4.27.2)
    RotationInput = FRotator::ZeroRotator; 
}

void APlayerController::PlayerTick(float DeltaTime)
{
    // 入力の更新とインプット系delegateの呼び出し // PlayerController.cpp 2182行目 (UE4.27.2)
    TickPlayerInput(DeltaTime, /*bGamePaused=*/DeltaTime == 0.0f);
    // Control Rotationの更新 // PlayerController.cpp 2222行目 (UE4.27.2)
    UpdateRotation(DeltaTime);
}
```

- `TickActor()`はその名の通り、Player Controller自身のTick関数である。つまり上記の処理が１フレームに１回ずつ定期的に行われている。
- まず、`PlayerInput`が存在するならば`PlayerTick()`を呼びだす。
    - `PlayerInput`は、実行直後の初期化の流れで呼び出され、Player ControllerがローカルプレイヤーであればNewObjectされる。(APlayerController::SetPlayer(), PlayerController.cpp 4449行目)
    - つまり、`PlayerInput`の存在条件は「ローカルプレイヤーであること」である。
- `PlayerTick()`ではまず`TickPlayerInput()`が呼び出されユーザー入力の更新が行われる。これはインプット系のdelegate (イベント)の呼び出しも含む。
- その後、`UpdateRotation()`を呼び出して、<b>Control Rotation</b>と (必要ならば)PawnのRotationの更新が行われる。

## ここまでのまとめ。

- Player Controllerは<b>Control Rotation</b>なるRotatorをメンバ変数として持っている。
-  <b>Control Rotation</b>は、いわゆるビュー(カメラ)を表す回転値である。
-  <b>Control Rotation</b>は、Player ControllerのTickの度に更新されている。
-  <b>Control Rotation</b>は、Player Controllerの`InputRotation`を毎フレーム消費することで更新されている。

ここで疑問に思うことは、我々は (少なくとも私自身は) <b>Control Rotation</b>を意識してPlayer Controllerを操作したことがない、ということだ。何やら重要そうな機能なのに…

# BP_DefaultPawnを振り返る

では冒頭で例に挙げたBP_DefaultPawnを振り返ってみる。
<b>Control Rotation</b>について見たいので、Rotation系の操作に注目する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/27686661-c574-164e-f1e2-37b8067e23ca.png)
要するに、ここで使われている`Add Controller Yaw/Pitch Input`が<b>Control Rotation</b>を操作しているのではないか、という考えである。エンジンソースを見ると、こういう感じになっている。

```c++
// Pawn.cpp (691行目, UE4.27.2)
void APawn::AddControllerPitchInput(float Val)
{
    const APlayerController* PC = CastChecked<APlayerController>(Controller);
    PC->AddPitchInput(Val);
}

// PlayerController.cpp (5105行目, UE4.27.2)
void APlayerController::AddPitchInput(float Val)
{
    RotationInput.Pitch += Val;
}
```

つまり、こういう流れになっている。

1. BP_DefaultPawnでマウス操作を`Add Controller Pitch/Yaw Input`に渡すと、Player ControllerのRotation Inputが更新される。
1. Player Controllerの`UpdateRotation()`で、Rotation Inputを使った<b>Control Rotation</b>が更新される。
1. `UpdateRotation()`の最後で呼ばれる`APawn::FaceRotation()`で<b>Control Rotation</b>が更新され、さらにPawnのRotationが更新される。

…ということで、実は (知らない間に)毎フレーム<b>Control Rotation</b>を使っていたのである。

# まとめと考察

特にBlueprintを中心に触っていると<b>Control Rotation</b>を全く意識していないが、実はビュー(カメラ)の方向を決める重要な要素だったということが分かった。実際、エンジンソース内を"SetControlRotation"で検索してみると、AI ControllerやCharacter Movement Componentでも使われていることが分かる。

また、Blueprintにも<b>Control Rotation</b>を直接参照・変更する関数は用意されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/41ff214d-27ab-615a-fd45-a0f5b19a9030.png)
<b>Control Rotation</b>のしくみを知っていれば、うまく使える局面が出てきそうだ。

## 取得したビューは最新か？

一方で、普段の私は明示的にCamera ActorやCamera Componentを用意/運用して、そこからビューの方向ベクトルを取得したりしている。その際の悩みは、そのカメラから取得したビューが実は１フレーム前の値かもしれない点である。なぜなら、UEのTickの順番は不定であり、自分(Pawn)とCameraのTickの順序を制御できないからである。

[prerequisite]:https://docs.unrealengine.com/4.27/en-US/BlueprintAPI/Utilities/AddTickPrerequisiteActor/

【注】[Add Tick Prerequisite Actor][prerequisite]などを使えば順序の操作は可能ではある。
だが、これを多用するといわゆる循環参照に似た状態になることも知っている。

では、Player Controllerの持つ<b>Control Rotation</b>はどうなのか。
上記に書いたPlayer ControllerのTickの流れをもう一度書く。

```c++
void APlayerController::TickActor(float DeltaSeconds, /*略*/)
{
    if (PlayerInput)
    {
        // APlayerController::PlayerTick()の内容もこの中に展開して書いてしまう。
        TickPlayerInput(DeltaSeconds, /*略*/); // 入力の更新と入力系delegateの呼び出し
        UpdateRotation(DeltaSeconds); // Control Rotationの更新とPawnのRotationの更新
    }
    RotationInput = FRotator::ZeroRotator; // ゼロクリア
}
```

ここで注目したいのは、Player ControllerのTickの中で、`TickPlayerInput()`と`UpdateRotation()`がセットで呼び出されているということである。

1. `TickPlayerInput()`でユーザー入力の更新をした後、入力系のdelegateを呼び出す。
1. その結果、例えば今回のBP_DefaultPawnの`InputAxis Turn`や`InputAxis Lookup`が呼び出される。
1. 次に、`UpdateRotation()`で<b>Control Rotation</b>とPawnのRotationの更新。

つまり、Player ControllerのTickの呼び出しの中で (PawnのTickが回ってくるのを待つまでもなく)、PawnのRotationの更新まで行えるということである。これは次の２つの点で有用だと思われる。

- ビューとPawnのフレームずれがない。入力により変更されたビューの回転値が<b>Control Rotation</b>に保存された後、PawnのRotationの更新が入る。
- いわゆるUnreal Way「イベントドリブンで組みなさい」に合致する。個々のアクタが個々のTickで処理をするより効率的。

## Player ControllerやPlayer Camera ManagerのTransformはどうなっている？

今回のBP_DefaultPawnを実行して、WASDやマウスである程度動かしてから、[F8]キーでEjectしてみると分かる。
ここは結果だけ書く。

### Player Controller

- LocationはPlayer Startの位置から動かない。
- Rotationはビュー(カメラ)のRotationになっている。

`SetControlRotation()`内で、ルートコンポーネントのワールドRotationにControl Rotationをセットしているため。(Controller.cpp, 131行目, UE4.27.2)

### Player Camera Manager

- LocationもRotationもビュー(カメラ)に一致する。

Player Camera Managerの`UpdateViewTarget()`内で、Player Camera Manager自身のLocationとRotationにビューのLocationとRotationをセットしているため。(PlayerCameraMaanger.cpp, 606行目, UE4.27.2)

:::note warn
ここでPlayer Camera ManagerのRotationを取得すれば、ビューのRotationと一致するじゃん！便利じゃん！と思ってしまうが、Player Camera ManagerのUpdateViewTarget()は<b><font color="red">すべてのアクタのTickが終わってから</font></b>更新されるので注意。
つまり、各アクタが自分のTickでPlayer Camera ManagerのRotationを取得しても、それは更新前の値である (１フレーム遅れの値が取得されることになる)。
やはりこの場合も、PlayerControllerのControl Rotationを使う方が良いだろう。
(Player Camera Managerの更新はUWorld::Tick()内のTG_PostPhysicsとTG_PostUpdateWorkの間で呼び出されている)
:::

# 最後に

この記事のタイトルは「Player ControllerのControl Rotationとは」と書いているが、実際にControl Rotationを持っているのはControllerクラスである。
ただ、個人的に「Player Controllerの…」と書いた方がイメージがわき易かったのでそうした。

----

以上です。