関連ブログ
- [UE4][UE5]開発環境の容量を少しでも減らす 2024.08.14UE
- [UE5] PushModel型のReplicationを使い、ネットワーク最適化を図る 2024.05.29UE
- [UE5]マテリアルでメッシュをスケールする方法 2024.01.17UE
CATEGORY
2022.11.09UE4UE/ C++
執筆バージョン: Unreal Engine 4.27 |
こんにちは!
UEのTemplateとしてThirdPersonがありますが、実際にプレイしてみると
プレイヤーとカメラの間に障害物があった場合の挙動について何か違和感を感じたことがないでしょうか?
カメラが急接近したり、急に離れたりしてカメラワークとしてあまりよくない挙動になっています。
今回はこの問題について、SpringArmComponentを拡張してもう少し滑らかにカメラが動くようにしていきます!
※本記事ではC++にてSpringArmの改造を行うため、プロジェクト設定をC++プロジェクトに設定してください。
先述した通り、今回の記事では自分で1からSpringArmを実装するのではなく、あくまで拡張なのでまずはエンジンのSpringArmが
どのような挙動で動いているのかある程度理解し、該当の処理にロジックを追加するまたは置き換える必要性があります。
Engine\Source\Runtime\Engine\Private\GameFramework\SpringArmComponent.cpp
を見ていくと、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
FVector ResultLoc; if (bDoTrace && (TargetArmLength != 0.0f)) { bIsCameraFixed = true; FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(SpringArm), false, GetOwner()); FHitResult Result; GetWorld()->SweepSingleByChannel(Result, ArmOrigin, DesiredLoc, FQuat::Identity, ProbeChannel, FCollisionShape::MakeSphere(ProbeSize), QueryParams); UnfixedCameraPosition = DesiredLoc; ResultLoc = BlendLocations(DesiredLoc, Result.Location, Result.bBlockingHit, DeltaTime); if (ResultLoc == DesiredLoc) { bIsCameraFixed = false; } } else { ResultLoc = DesiredLoc; bIsCameraFixed = false; UnfixedCameraPosition = ResultLoc; } |
というコードが見当たります。
明らかにここにプレイヤーとカメラの間に障害物があった場合のロジックが書かれてそうです。
右の赤丸(ArmOrigin)を始点、左の赤丸(DesiredLoc)を終点としたTraceを行ってSpringArmの伸縮を決めてるみたいですね!
GetWorld()->SweepSingleByChannelの少し下にBlendLocationsという関数があります、これについても何を行っているのか見てみましょう。
1 2 3 4 |
FVector USpringArmComponent::BlendLocations(const FVector& DesiredArmLocation, const FVector& TraceHitLocation, bool bHitSomething, float DeltaTime) { return bHitSomething ? TraceHitLocation : DesiredArmLocation; } |
Traceの結果がHitしてるならTraceHitLocationを、HitしていないならDesiredArmLocation(DesiredLoc)を返すようです。
補足としてDesiredArmLocationというのは、Armが伸びきっている状態の位置です。
そしてこのBlendLocationsの戻り値がカメラの位置となるようですね。
つまり、プレイヤーとカメラ間に障害物がある状態から障害物がない状態とその逆、障害物がない状態から障害物がある状態に
遷移する場合なんの補間もなく、Armが伸びきったり、縮み切ったりする挙動となっています。
ということはこの関数の内部でArmの伸縮に補間を入れることで今回の問題を解決することができそうです。
余談にはなりますが、この関数の定義を
Engine\Source\Runtime\Engine\Private\GameFramework\SpringArmComponent.h
で見てみると、
This function allows subclasses to blend the trace hit location with the desired arm location;
というコメントがあり、翻訳をかけると
この関数により、サブクラスはトレース ヒット位置を目的のアーム位置とブレンドできます。
ということらしいので、この関数はユーザーがサブクラスからカスタマイズするように作られた関数だということがわかります。
さて、最初に話したエンジン側のSpringArmの挙動を掴むということはできたので、次は本題のSpringArmを改造していきましょう!
コメントにもあったようにプロジェクト専用のSpringArmComponentをエンジンのSpringArmComponentの
サブクラスとして作成して、BlendLocations関数をOverrideすることでArmの伸縮を制御していきましょう!
エンジン側のSpringArmComponentを親クラスとして、プロジェクト専用のSpringArmComponentを作成します。
それでは少しずつ解説しながら実装していきます。
まずプレイヤーとカメラ間に障害物があり、Armが縮む際の補間処理
1 2 3 4 5 6 7 |
if (bHitSomething) { FVector Interp = FMath::VInterpTo(PrevHitInterpLoc, TraceHitLocation, DeltaTime, HitInterpSpeed); PrevHitInterpLoc = Interp; return Interp; } |
HitInterpSpeed(補間スピード)の値に応じて、PrevHitInterpLoc(前フレームの位置)から
TraceHitLocation(Traceで最初にHitした位置)まで補間しながら近づけています。
次に、プレイヤーとカメラ間に障害物がなくなりArmが伸びる際の補間処理を追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 壁にぶつかった if (bHitSomething) { FVector Interp = FMath::VInterpTo(PrevHitInterpLoc, TraceHitLocation, DeltaTime, HitInterpSpeed); PrevHitInterpLoc = Interp; //★★壁と衝突したのでCurrentHitReturnInterpTimeをHitReturnInterpTimeにリセット!! CurrentHitReturnInterpTime = HitReturnInterpTime; return Interp; } // 壁ぶつかり終了後の復帰補間 if (CurrentHitReturnInterpTime > 0.0f) { CurrentHitReturnInterpTime -= DeltaTime; FVector Interp = FMath::VInterpTo(PrevHitInterpLoc, DesiredArmLocation, 1.0f - (CurrentHitReturnInterpTime / HitReturnInterpTime), 1.0f); PrevHitInterpLoc = Interp; return Interp; } |
HitReturnInterpTime(補間時間)に応じて、PrevHitInterpLoc(前フレームの位置)からDesiredArmLocation(Armが伸びきっている状態の位置)まで補間しながら近づけるようにしました。
あとはBlueprint側で補完の値を調節できるようにしたり、
壁にぶつかっておらず復帰補間も終わっているという状態の処理を追加して
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
UCLASS(ClassGroup = Camera, meta = (BlueprintSpawnableComponent), hideCategories = (Mobility)) class INTERPSPRINGARM_API UInterpSpringArmComponent : public USpringArmComponent { GENERATED_BODY() private: FVector PrevHitInterpLoc; float CurrentHitReturnInterpTime; public: // 壁にぶつかったときの補間スピード UPROPERTY(EditAnywhere, Category = CameraSettings, meta = (ClampMin = "0.0", ClampMax = "1000.0", EditCondition = EnableCollision)) float HitInterpSpeed; // 壁にぶつからなくなった後、何秒かけて補間で元の位置に戻るか UPROPERTY(EditAnywhere, Category = CameraSettings, meta = (EditCondition = EnableCollision)) float HitReturnInterpTime; protected: virtual FVector BlendLocations(const FVector& DesiredArmLocation, const FVector& TraceHitLocation, bool bHitSomething, float DeltaTime) override; }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
FVector UInterpSpringArmComponent::BlendLocations(const FVector& DesiredArmLocation, const FVector& TraceHitLocation, bool bHitSomething, float DeltaTime) { // 壁にぶつかった if (bHitSomething) { FVector Interp = FMath::VInterpTo(PrevHitInterpLoc, TraceHitLocation, DeltaTime, HitInterpSpeed); PrevHitInterpLoc = Interp; //★★壁と衝突したのでCurrentHitReturnInterpTimeをHitReturnInterpTimeにリセット!! CurrentHitReturnInterpTime = HitReturnInterpTime; return Interp; } // 壁ぶつかり終了後の復帰補間 if (CurrentHitReturnInterpTime > 0.0f) { CurrentHitReturnInterpTime -= DeltaTime; FVector Interp = FMath::VInterpTo(PrevHitInterpLoc, DesiredArmLocation, 1.0f - (CurrentHitReturnInterpTime / HitReturnInterpTime), 1.0f); PrevHitInterpLoc = Interp; return Interp; } // 上の条件に入らなかったのでArmは伸びきっている状態のはず PrevHitInterpLoc = DesiredArmLocation; return DesiredArmLocation; } |
こんな感じになりました!
それでは作成したSpringArmComponentの子要素にカメラを設置して、HitInterpSpeedとHitReturnInterpTimeを調整します。
それでは実際にPlayしてみましょう!
Armの伸縮に補間が入ったことにより、カメラの移動が緩やかになったと思います。
長々と解説してしまいましたが、実装コストとしてはあまり重くないものなので
皆さんも実装を検討してみてはいかかでしょうか?