5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

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

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

Pawnを継承して作ったアクタの詳細パネルに現れるこの(↓)パラメータ
image.png
…の最初の3項目:

  • Use Controller Rotation Pitch
  • Use Controller Rotation Yaw
  • Use Controller Rotation Roll

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

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

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

Project Settings > Input

お馴染みのパターンでインプットバインディングを作っておく。
image.png
今回はWASDとマウスだけに対応することにする。

BP_DefaultPawn

Pawnを継承したアクタを作る。
コンポーネントは以下のようにした。
image.png

  • ルートコンポーネントとして Billboardを使用。
    • 詳細パネルのRendering > Hidden in Game を Falseにしておく。
    • Billboardにした理由は単なる好み。個人的にあのアイコンが好きなだけ。

image.png

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

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

image.png

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

最後に例のUse Controller Rotation Pitch/Yaw/Rollをセットする。
image.png
今回はUse Controller Rotatiaon Rollは使わない。

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

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

APawn::FaceRotation()

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

Pawn.cpp (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()を見てみる。
コードの記述は、例によってニュアンスが分かる程度にとどめておく。

PlayerController.cpp (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. ViewRotationControl Rotationなるもので初期化される。
  2. ViewRotationDeltaRotation (=RotationInput)を足すことで更新される。
  3. その結果をControl Rotationなるものにセットする。
  4. そのViewRotation (=Control Rotation)でPawnのRotationを更新する。

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

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

APlayerController::TickActor()

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

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

ここまでのまとめ。

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

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

BP_DefaultPawnを振り返る

では冒頭で例に挙げたBP_DefaultPawnを振り返ってみる。
Control Rotationについて見たいので、Rotation系の操作に注目する。
image.png
要するに、ここで使われているAdd Controller Yaw/Pitch InputControl Rotationを操作しているのではないか、という考えである。エンジンソースを見ると、こういう感じになっている。

// 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が更新される。
  2. Player ControllerのUpdateRotation()で、Rotation Inputを使ったControl Rotationが更新される。
  3. UpdateRotation()の最後で呼ばれるAPawn::FaceRotation()Control Rotationが更新され、さらにPawnのRotationが更新される。

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

まとめと考察

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

また、BlueprintにもControl Rotationを直接参照・変更する関数は用意されている。
image.png
Control Rotationのしくみを知っていれば、うまく使える局面が出てきそうだ。

取得したビューは最新か?

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

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

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

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を呼び出す。
  2. その結果、例えば今回のBP_DefaultPawnのInputAxis TurnInputAxis Lookupが呼び出される。
  3. 次に、UpdateRotation()Control RotationとPawnのRotationの更新。

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

  • ビューとPawnのフレームずれがない。入力により変更されたビューの回転値がControl Rotationに保存された後、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)

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

最後に

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


以上です。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?