はじめに
「お!見たことないノード!ググっても情報少ないし新しいノードか!?」と意気揚々とツイートしたら…
え、こんなノードあったんだ…Calculate Direction #UE4 #UE4Study pic.twitter.com/qpbCixUrqu
— おかず (@pafuhana1213) 2021年1月20日
フルボッコにされました(´;ω;`)ブワッ
ということで、泣きながら試しに使ってみた所いい感じだったのでサクッと記事にしてみました
公式ドキュメント:
Calculate Direction | Unreal Engine Documentation
Calculate Directionノード、完全に理解した!サクッと記事にしようっと https://t.co/3v7UZyLqvs pic.twitter.com/1E3L1quaZA
— おかず (@pafuhana1213) 2021年1月26日
Calculate Directionノードの使い方
とっても簡単です!これで「移動方向のベクトルとキャラクタ前方のベクトルの角度」を取得できます。
なお、AnimationBPのEventGraphでしか使うことができません。
AnimationBPのEventGraph、またはSkeletalMeshComponent -> Get Anim Instance経由で使えます。
これだけだと少しイメージしづらいので…2つのベクトルをArrowComponentで可視化してみました。赤が移動方向(=Velocity)、青がキャラクタ(=Actor)の向きです。
キャラクタが左を向いている状態で右方向に移動し始めると…以下のようになります。
そして、Calculate Directionノードは下図のこの角度を計算してくれます!
べんり!で、これで終わっては少し寂しいのです。せっかくなので取得した角度を使って何かしてみます。
Calculate Directionノードの結果を使ってキャラクタの体を傾けてみる
こちらの しょーごさんの ツイートにもある通り、BlendSpaceと併用することで移動方向に応じたアニメーションを再生しより自然な動きを表現することができます。
僕はアニメーションスターターパック?(シューティング作れるアニメーションのやつ)でtps作ろうとしたときに見つけましたね。blend space で横移動作るときに通る道ですし!
— しょーご/Shogo (@kelu_alfheim39) 2021年1月21日
docs.unrealengine.com
というか、まさにCalculate DirectionノードとBlendSpaceの併用に関するチュートリアルがありますね!ガハハ!(ここまで書いた後に気づいた)
docs.unrealengine.com
とは言え、BlendSpace用にAnimationSequenceを複数個用意するのは大変です。なので、AnimGraphで使えるTransfom(Modify)Boneノードを使って簡単に傾けてみます。
と言っても簡単で、先程取得した角度を使って腰辺りの骨(spine_03ボーン)を回すだけです。なお、下図の赤枠にある通り、RotationModeはAddtoExistingにする必要があります。他の設定の場合だと、角度制御が効かなかったりアニメーションによる骨の回転量を無視して上書きするようになってしまいます。
そして、この結果が下図です!…骨の追加する回転量が大きすぎるため、全く自然な動きではありません。
これを解決するには色んな方法が考えられますが、恐らく最も手っ取り早いには追加する回転量に制限をかけちゃうことです。
このように -30 ~ 30 の範囲に抑えると…
このようにいい感じになります!ちょっとしたことですが、デフォルトより自然な動きになりました!
より自然な動きにしたい場合はBlendSpaceを用いるか、Control Rigを用いてより複雑な補正を行うことになるかと思います。後者に関してはいつか試してみたいなぁと思ったりしてます。
さいごに
Calculate Directionノードの使い方と簡易的な活用方法について説明してみました。移動方向に体を少し傾けるというちょっとしたことではありますが、アニメーションのクオリティはこういう事の積み重ねで上げていくものかと思います(逆に言うと、この辺りが疎かになっていると違和感が出てきてしまいます…)。
今回の方法でしたらサクッと導入できるかと思いますので、是非一度試してみてください!
おまけ
ちなみに、CalculateDirectionの内部実装はこんな感じ。…ふつうですね!
float UAnimInstance::CalculateDirection(const FVector& Velocity, const FRotator& BaseRotation) const { if (!Velocity.IsNearlyZero()) { FMatrix RotMatrix = FRotationMatrix(BaseRotation); FVector ForwardVector = RotMatrix.GetScaledAxis(EAxis::X); FVector RightVector = RotMatrix.GetScaledAxis(EAxis::Y); FVector NormalizedVel = Velocity.GetSafeNormal2D(); // get a cos(alpha) of forward vector vs velocity float ForwardCosAngle = FVector::DotProduct(ForwardVector, NormalizedVel); // now get the alpha and convert to degree float ForwardDeltaDegree = FMath::RadiansToDegrees(FMath::Acos(ForwardCosAngle)); // depending on where right vector is, flip it float RightCosAngle = FVector::DotProduct(RightVector, NormalizedVel); if (RightCosAngle < 0) { ForwardDeltaDegree *= -1; } return ForwardDeltaDegree; } return 0.f; } <||