第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,331 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Components/UGC_SpringArmComponentBase.h"
#include "DrawDebugHelpers.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Pawn.h"
#include "Kismet/KismetMathLibrary.h"
#include "Math/RotationMatrix.h"
#include "PhysicsEngine/PhysicsSettings.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Camera/UGC_PlayerCameraManager.h"
#include "Kismet/GameplayStatics.h"
void UUGC_SpringArmComponentBase::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
void UUGC_SpringArmComponentBase::UpdateDesiredArmLocation(bool bDoTrace, bool bDoLocationLag, bool bDoRotationLag, float DeltaTime)
{
FRotator DesiredRot = GetTargetRotation();
// If our viewtarget is simulating using physics, we may need to clamp deltatime
if (bClampToMaxPhysicsDeltaTime)
{
// Use the same max timestep cap as the physics system to avoid camera jitter when the viewtarget simulates less time than the camera
DeltaTime = FMath::Min(DeltaTime, UPhysicsSettings::Get()->MaxPhysicsDeltaTime);
}
// Apply 'lag' to rotation if desired
if (bDoRotationLag)
{
if (bUseCameraLagSubstepping && DeltaTime > CameraLagMaxTimeStep && CameraRotationLagSpeed > 0.f)
{
const FRotator ArmRotStep = (DesiredRot - PreviousDesiredRot).GetNormalized() * (1.f / DeltaTime);
FRotator LerpTarget = PreviousDesiredRot;
float RemainingTime = DeltaTime;
while (RemainingTime > UE_KINDA_SMALL_NUMBER)
{
const float LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime);
LerpTarget += ArmRotStep * LerpAmount;
RemainingTime -= LerpAmount;
DesiredRot = FRotator(FMath::QInterpTo(FQuat(PreviousDesiredRot), FQuat(LerpTarget), LerpAmount, CameraRotationLagSpeed));
PreviousDesiredRot = DesiredRot;
}
}
else
{
DesiredRot = FRotator(FMath::QInterpTo(FQuat(PreviousDesiredRot), FQuat(DesiredRot), DeltaTime, CameraRotationLagSpeed));
}
}
PreviousDesiredRot = DesiredRot;
// Get the spring arm 'origin', the target we want to look at
FVector const OriginalArmOrigin = GetComponentLocation() + TargetOffset;
FVector const ArmOriginWithSocketOffset = OriginalArmOrigin + FRotationMatrix(DesiredRot).TransformVector(SocketOffset);
// Get the spring arm 'origin', the target we want to look at
FVector ArmOrigin = OriginalArmOrigin;
// We lag the target, not the actual camera position, so rotating the camera around does not have lag
FVector DesiredLoc = ArmOrigin;
if (bDoLocationLag)
{
if (bUseCameraLagSubstepping && DeltaTime > CameraLagMaxTimeStep && CameraLagSpeed > 0.f)
{
const FVector ArmMovementStep = (DesiredLoc - PreviousDesiredLoc) * (1.f / DeltaTime);
FVector LerpTarget = PreviousDesiredLoc;
float RemainingTime = DeltaTime;
while (RemainingTime > UE_KINDA_SMALL_NUMBER)
{
const float LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime);
LerpTarget += ArmMovementStep * LerpAmount;
RemainingTime -= LerpAmount;
DesiredLoc = FMath::VInterpTo(PreviousDesiredLoc, LerpTarget, LerpAmount, CameraLagSpeed);
PreviousDesiredLoc = DesiredLoc;
}
}
else
{
DesiredLoc = FMath::VInterpTo(PreviousDesiredLoc, DesiredLoc, DeltaTime, CameraLagSpeed);
}
// Clamp distance if requested
bool bClampedDist = false;
if (CameraLagMaxDistance > 0.f)
{
const FVector FromOrigin = DesiredLoc - ArmOrigin;
if (FromOrigin.SizeSquared() > FMath::Square(CameraLagMaxDistance))
{
DesiredLoc = ArmOrigin + FromOrigin.GetClampedToMaxSize(CameraLagMaxDistance);
bClampedDist = true;
}
}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bDrawDebugLagMarkers)
{
DrawDebugSphere(GetWorld(), ArmOrigin, 5.f, 8, FColor::Green);
DrawDebugSphere(GetWorld(), DesiredLoc, 5.f, 8, FColor::Yellow);
const FVector ToOrigin = ArmOrigin - DesiredLoc;
DrawDebugDirectionalArrow(GetWorld(), DesiredLoc, DesiredLoc + ToOrigin * 0.5f, 7.5f, bClampedDist ? FColor::Red : FColor::Green);
DrawDebugDirectionalArrow(GetWorld(), DesiredLoc + ToOrigin * 0.5f, ArmOrigin, 7.5f, bClampedDist ? FColor::Red : FColor::Green);
}
#endif
}
PreviousArmOrigin = ArmOrigin;
PreviousDesiredLoc = DesiredLoc;
// Now offset camera position back along our rotation
DesiredLoc -= DesiredRot.Vector() * TargetArmLength;
// Add socket offset in local space
DesiredLoc += FRotationMatrix(DesiredRot).TransformVector(SocketOffset);
// Do a sweep to ensure we are not penetrating the world
FVector ResultLoc;
if (bDoTrace && (TargetArmLength != 0.0f))
{
bIsCameraFixed = true;
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(SpringArm), false, GetOwner());
FHitResult Result;
// It's important to use the OriginalArmOrigin for the traces otherwise jumping over obstacles will trigger collisions (because ArmOrigin stays low).
GetWorld()->SweepSingleByChannel(Result, OriginalArmOrigin, 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;
}
// Form a transform for new world transform for camera
FTransform WorldCamTM(DesiredRot, ResultLoc);
// Convert to relative to component
FTransform RelCamTM = WorldCamTM.GetRelativeTransform(GetComponentTransform());
// Update socket location/rotation
RelativeSocketLocation = RelCamTM.GetLocation();
RelativeSocketRotation = RelCamTM.GetRotation();
UpdateChildTransforms();
}
FVector UUGC_SpringArmComponentBase::BlendLocations(const FVector& DesiredArmLocation, const FVector& TraceHitLocation, bool bHitSomething, float DeltaTime)
{
if (!bDoCollisionTest || !CameraCollisionSettings.bPreventPenetration)
{
return DesiredArmLocation;
}
const bool bShouldComplexTrace = CameraCollisionSettings.bDoPredictiveAvoidance && CameraCollisionSettings.PenetrationAvoidanceFeelers.Num() > 0 && IsPlayerControlled();
if (!bShouldComplexTrace)
{
return Super::BlendLocations(DesiredArmLocation, TraceHitLocation, bHitSomething, DeltaTime);
}
FVector SafeLoc = FVector::ZeroVector;
if (bMaintainFramingDuringCollisions)
{
FRotator DesiredRot = GetTargetRotation();
const FVector OriginalArmOrigin = GetComponentLocation() + TargetOffset;
SafeLoc = OriginalArmOrigin + FRotationMatrix(DesiredRot).TransformVector(SocketOffset);
}
else
{
SafeLoc = PreviousArmOrigin;
}
const FVector CameraLoc = DesiredArmLocation;
const FVector BaseRay = CameraLoc - SafeLoc;
if (BaseRay.IsNearlyZero())
{
return DesiredArmLocation;
}
const FRotationMatrix BaseMatrix(BaseRay.Rotation());
const FVector Right = BaseMatrix.GetUnitAxis(EAxis::Y);
const FVector Up = BaseMatrix.GetUnitAxis(EAxis::Z);
float HardBlockedPct = DistBlockedPct;
float SoftBlockedPct = DistBlockedPct;
float BlockedThisFrame = 1.f;
UWorld* World = GetWorld();
int32 NbrHits = 0;
const AActor* OwningActor = GetOwner();
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(CameraPen), false, OwningActor);
QueryParams.AddIgnoredActor(OwningActor);
#if WITH_EDITORONLY_DATA
const int32 NewCapacity = CameraCollisionSettings.PenetrationAvoidanceFeelers.Num();
const int32 OldCapacity = HitActors.Max();
HitActors.Reset(FMath::Max(NewCapacity, OldCapacity)); // Reset array elements but keep allocation to max capacity.
#endif
for (int32 i = 0; i < CameraCollisionSettings.PenetrationAvoidanceFeelers.Num(); ++i)
{
const FPenetrationAvoidanceFeeler& Feeler = CameraCollisionSettings.PenetrationAvoidanceFeelers[i];
FRotator OffsetRot = Feeler.AdjustmentRot;
if (i == 0 && Feeler.AdjustmentRot != FRotator::ZeroRotator)
{
#if ENABLE_DRAW_DEBUG
if (GEngine != nullptr)
{
GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor::Red, TEXT("DSA_SpringArmComponent: First Penetration Avoidance Feeler should always have an adjustment roation equal to 0,0,0!."));
}
#endif
OffsetRot = FRotator::ZeroRotator;
}
FVector RayTarget = BaseRay.RotateAngleAxis(OffsetRot.Yaw, Up).RotateAngleAxis(OffsetRot.Pitch, Right) + SafeLoc;
FHitResult Hit;
FCollisionShape Shape = FCollisionShape::MakeSphere(Feeler.ProbeRadius);
bool bHit = World->SweepSingleByChannel(Hit, SafeLoc, RayTarget, FQuat::Identity, ProbeChannel, Shape, QueryParams);
if (bHit && Hit.GetActor())
{
if (Hit.GetActor()->ActorHasTag(CameraCollisionSettings.IgnoreCameraCollisionTag))
{
QueryParams.AddIgnoredActor(Hit.GetActor());
continue;
}
++NbrHits;
const float Weight = Hit.GetActor()->IsA<APawn>() ? Feeler.PawnWeight : Feeler.WorldWeight;
#if WITH_EDITORONLY_DATA
HitActors.AddUnique(Hit.GetActor());
#endif
float NewBlockPct = Hit.Time + (1.f - Hit.Time) * (1.f - Weight);
NewBlockPct = (Hit.Location - SafeLoc).Size() / (RayTarget - SafeLoc).Size();
BlockedThisFrame = FMath::Min(NewBlockPct, BlockedThisFrame);
if (i == 0)
HardBlockedPct = BlockedThisFrame;
else
SoftBlockedPct = BlockedThisFrame;
}
}
#if ENABLE_DRAW_DEBUG
if (NbrHits > 0)
{
if (bPrintCollisionDebug && GEngine != nullptr)
{
if (bPrintHitActors)
{
#if WITH_EDITORONLY_DATA
for (int i = HitActors.Num() - 1; i >= 0; --i)
{
if (AActor* HitActor = HitActors[i])
{
FString const DebugText = FString::Printf(TEXT("DSA_SpringArmComponent: Colliding with %s."), *GetNameSafe(HitActor));
GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor(150, 150, 200), DebugText);
}
}
#endif
}
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6
FString const DebugText = FString::Printf(TEXT("DSA_SpringArmComponent: %d feeler%hs colliding."), NbrHits, NbrHits > 1 ? "s" : "");
#else
FString const DebugText = FString::Printf(TEXT("DSA_SpringArmComponent: %d feeler%s colliding."), NbrHits, NbrHits > 1 ? "s" : "");
#endif
GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor(150, 150, 200), DebugText);
}
}
#endif
if (DistBlockedPct < BlockedThisFrame)
{
float BlendOutTime = FMath::Max(CameraCollisionSettings.PenetrationBlendOutTime, UE_KINDA_SMALL_NUMBER);
DistBlockedPct += DeltaTime / BlendOutTime * (BlockedThisFrame - DistBlockedPct);
}
else if (DistBlockedPct > HardBlockedPct)
{
DistBlockedPct = HardBlockedPct;
}
else if (DistBlockedPct > SoftBlockedPct)
{
float BlendInTime = FMath::Max(CameraCollisionSettings.PenetrationBlendInTime, UE_KINDA_SMALL_NUMBER);
DistBlockedPct -= DeltaTime / BlendInTime * (DistBlockedPct - SoftBlockedPct);
}
DistBlockedPct = FMath::Clamp(DistBlockedPct, 0.f, 1.f);
return SafeLoc + (CameraLoc - SafeLoc) * DistBlockedPct;
}
bool UUGC_SpringArmComponentBase::IsPlayerControlled() const
{
APawn* PawnOwner = Cast<APawn>(GetOwner());
AController* Controller = PawnOwner ? PawnOwner->GetController() : nullptr;
bool bPlayerControlled = (PawnOwner && Controller && PawnOwner->IsPlayerControlled());
if (!bPlayerControlled)
{
if (GEngine)
{
if (APlayerController* PC = GEngine->GetFirstLocalPlayerController(GetWorld()))
{
if (APlayerCameraManager* PCM = PC->PlayerCameraManager)
{
bPlayerControlled = PCM->PendingViewTarget.Target == GetOwner() || (Controller != nullptr && PCM->PendingViewTarget.Target == Controller);
}
}
}
}
return bPlayerControlled;
}

View File

@@ -0,0 +1,39 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Data/UGC_CameraData.h"
FPenetrationAvoidanceFeeler::FPenetrationAvoidanceFeeler()
: AdjustmentRot(ForceInit)
, WorldWeight(0.f)
, PawnWeight(0.f)
, ProbeRadius(0)
{
}
FPenetrationAvoidanceFeeler::FPenetrationAvoidanceFeeler(const FRotator& InAdjustmentRot, const float& InWorldWeight, const float& InPawnWeight, const float& InExtent)
: AdjustmentRot(InAdjustmentRot)
, WorldWeight(InWorldWeight)
, PawnWeight(InPawnWeight)
, ProbeRadius(InExtent)
{
}
FCameraCollisionSettings::FCameraCollisionSettings()
{
// AdjusmentRotation, WorldWeight, PawnWeight, Extent
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, +00.00f, 0.00f), 0.50f, 1.00f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, +5.00f, 0.00f), 0.20f, 0.75f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, +10.0f, 0.00f), 0.20f, 0.75f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, -5.00f, 0.00f), 0.20f, 0.75f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, -10.0f, 0.00f), 0.20f, 0.75f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, +5.00f, 0.00f), 0.15f, 0.50f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, +10.0f, 0.00f), 0.15f, 0.50f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, -5.00f, 0.00f), 0.15f, 0.50f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+0.00f, -10.0f, 0.00f), 0.15f, 0.50f, 15.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(+15.0f, +0.00f, 0.00f), 0.50f, 1.00f, 10.00f));
PenetrationAvoidanceFeelers.Add(FPenetrationAvoidanceFeeler(FRotator(-10.0f, +0.00f, 0.00f), 0.50f, 0.50f, 10.00f));
}

View File

@@ -0,0 +1,18 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Methods/UGC_IFocusTargetMethod.h"
#include "Engine/World.h"
AActor* UUGC_IFocusTargetMethod::GetTargetLocation_Implementation(AActor* InOwner, FVector OwnerLocation, FVector ViewPointLocation, FRotator ViewPointRotation, FVector& OutTargetLocation)
{
return nullptr;
}
UWorld* UUGC_IFocusTargetMethod::GetWorld() const
{
if (GWorld && GWorld->IsGameWorld() && GWorld->HasBegunPlay())
{
return GWorld;
}
return nullptr;
}

View File

@@ -0,0 +1,17 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Methods/UGC_IGetActorsMethod.h"
#include "Engine/World.h"
void UUGC_IGetActorsMethod::GetActors_Implementation(AActor* InOwner, FVector OwnerLocation, FVector ViewPointLocation, FRotator ViewPointRotation, TArray<AActor*>& OutActors)
{
}
UWorld* UUGC_IGetActorsMethod::GetWorld() const
{
if (GWorld && GWorld->IsGameWorld() && GWorld->HasBegunPlay())
{
return GWorld;
}
return nullptr;
}

View File

@@ -0,0 +1,602 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Modifiers/UGC_CameraAnimationModifier.h"
#include "Camera/CameraAnimationHelper.h"
#include "Camera/Modifiers/UGC_CameraCollisionModifier.h"
#include "Camera/PlayerCameraManager.h"
#include "CameraAnimationSequence.h"
#include "CameraAnimationSequencePlayer.h"
#include "Components/SkeletalMeshComponent.h"
#include "DisplayDebugHelpers.h"
#include "Engine/Canvas.h"
#include "Engine/Engine.h"
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "MovieSceneFwd.h"
#include "Camera/UGC_PlayerCameraManager.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "ProfilingDebugging/CountersTrace.h"
#include "UObject/UObjectGlobals.h"
#include "Runtime/Launch/Resources/Version.h"
namespace UGCCameraAnimationHelper
{
FCameraAnimationHandle const UGCInvalid(MAX_int16, 0);
FPenetrationAvoidanceFeeler const CollisionProbe = FPenetrationAvoidanceFeeler(FRotator(+0.00f, +00.00f, 0.00f), 0.50f, 1.00f, 15.00f);
float constexpr CollisionBlendInTime = 0.05f;
float constexpr CollisionBlendOutTime = 0.5f;
FName const CollisionIgnoreActorWithTag = FName("UGC_AnimIgnoreCollision");
}
FCameraAnimationHandle UUGC_CameraAnimationModifier::PlaySingleCameraAnimation(UCameraAnimationSequence* Sequence, FCameraAnimationParams Params, ECameraAnimationResetType ResetType, bool bInterruptOthers, bool bDoCollisionChecks)
{
if (!ensure(Sequence))
{
return UGCCameraAnimationHelper::UGCInvalid;
}
TArray<UCameraAnimationSequence*> InterruptedSequences;
if (IsAnyCameraAnimationSequence())
{
if (bInterruptOthers)
{
InterruptedSequences.Reserve(ActiveAnimations.Num());
for (int i = 0; i < ActiveAnimations.Num(); ++i)
{
if (ActiveAnimations[i].IsValid() && ActiveAnimations[i].Player != nullptr && ActiveAnimations[i].Player->GetPlaybackStatus() == EMovieScenePlayerStatus::Playing)
{
InterruptedSequences.Add(ActiveAnimations[i].Sequence);
}
}
}
}
// Always play one animation only
int32 const NewIndex = FindInactiveCameraAnimation();
check(NewIndex < MAX_uint16);
const uint16 InstanceSerial = NextInstanceSerialNumber++;
FCameraAnimationHandle InstanceHandle{ (uint16)NewIndex, InstanceSerial };
FActiveCameraAnimationInfo& NewCameraAnimation = ActiveAnimations[NewIndex];
// Update UGCAnimInfo size
if (ActiveAnimations.Num() > UGCAnimInfo.Num())
{
int const InfoIndex = UGCAnimInfo.Emplace();
ensure(InfoIndex == NewIndex);
}
UGCAnimInfo[NewIndex].ResetType = ResetType;
UGCAnimInfo[NewIndex].bWasEasingOut = false;
UGCAnimInfo[NewIndex].bDoCollisionChecks = bDoCollisionChecks;
UGCAnimInfo[NewIndex].DistBlockedPct = 1.f;
NewCameraAnimation.Sequence = Sequence;
NewCameraAnimation.Params = Params;
NewCameraAnimation.Handle = InstanceHandle;
const FName PlayerName = MakeUniqueObjectName(this, UCameraAnimationSequencePlayer::StaticClass(), TEXT("CameraAnimationPlayer"));
NewCameraAnimation.Player = NewObject<UCameraAnimationSequencePlayer>(this, PlayerName);
const FName CameraStandInName = MakeUniqueObjectName(this, UCameraAnimationSequenceCameraStandIn::StaticClass(), TEXT("CameraStandIn"));
NewCameraAnimation.CameraStandIn = NewObject<UCameraAnimationSequenceCameraStandIn>(this, CameraStandInName);
// Start easing in immediately if there's any defined.
NewCameraAnimation.bIsEasingIn = (Params.EaseInDuration > 0.f);
NewCameraAnimation.EaseInCurrentTime = 0.f;
NewCameraAnimation.bIsEasingOut = false;
NewCameraAnimation.EaseOutCurrentTime = 0.f;
// Initialize our stand-in object.
NewCameraAnimation.CameraStandIn->Initialize(Sequence);
// Make the player always use our stand-in object whenever a sequence wants to spawn or possess an object.
NewCameraAnimation.Player->SetBoundObjectOverride(NewCameraAnimation.CameraStandIn);
// Initialize it and start playing.
NewCameraAnimation.Player->Initialize(Sequence);
NewCameraAnimation.Player->Play(Params.bLoop, Params.bRandomStartTime);
LastIndex = NewIndex;
if (bInterruptOthers && InterruptedSequences.Num() > 0)
{
static bool constexpr bInterrupt = true;
for (int i = 0; i < InterruptedSequences.Num(); ++i)
{
OnAnimationEnded.ExecuteIfBound(InterruptedSequences[i], bInterrupt);
}
}
return InstanceHandle;
}
void UUGC_CameraAnimationModifier::StopCameraAnimationSequence(UCameraAnimationSequence* Sequence, bool bImmediate)
{
if (!ActiveAnimations.IsEmpty())
{
for (int i = 0; i < ActiveAnimations.Num(); ++i)
{
if (ActiveAnimations[i].IsValid() && (Sequence == nullptr || ActiveAnimations[i].Sequence == Sequence))
{
if (UCameraAnimationSequence* InterruptedSequence = ActiveAnimations[i].Sequence)
{
StopCameraAnimation(ActiveAnimations[i].Handle, bImmediate);
static bool constexpr bInterrupt = true;
OnAnimationEnded.ExecuteIfBound(InterruptedSequence, bInterrupt);
}
}
}
}
}
void UUGC_CameraAnimationModifier::GetCurrentCameraAnimations(TArray<UCameraAnimationSequence*>& OutAnimations) const
{
if (!ActiveAnimations.IsEmpty())
{
for (int i = 0; i < ActiveAnimations.Num(); ++i)
{
if (ActiveAnimations[i].IsValid() && ActiveAnimations[i].Player && ActiveAnimations[i].Player->GetPlaybackStatus() == EMovieScenePlayerStatus::Playing)
{
OutAnimations.Add(ActiveAnimations[i].Sequence);
}
}
}
}
bool UUGC_CameraAnimationModifier::IsCameraAnimationSequenceActive(UCameraAnimationSequence* Sequence) const
{
if (!ActiveAnimations.IsEmpty())
{
for (int i = 0; i < ActiveAnimations.Num(); ++i)
{
if (ActiveAnimations[i].IsValid() && ActiveAnimations[i].Sequence == Sequence && ActiveAnimations[i].Player->GetPlaybackStatus() == EMovieScenePlayerStatus::Playing)
{
return true;
}
}
}
return false;
}
bool UUGC_CameraAnimationModifier::IsAnyCameraAnimationSequence() const
{
if (!ActiveAnimations.IsEmpty())
{
for (int i = 0; i < ActiveAnimations.Num(); ++i)
{
if (ActiveAnimations[i].IsValid() && ActiveAnimations[i].Player && ActiveAnimations[i].Player->GetPlaybackStatus() == EMovieScenePlayerStatus::Playing)
{
return true;
}
}
}
return false;
}
bool UUGC_CameraAnimationModifier::ModifyCamera(float DeltaTime, FMinimalViewInfo& InOutPOV)
{
UCameraModifier::ModifyCamera(DeltaTime, InOutPOV);
if (UGCCameraManager)
{
bool const bAnyActive = IsAnyCameraAnimationSequence();
if (!CollisionModifier)
{
CollisionModifier = UGCCameraManager->FindCameraModifierOfType<UUGC_CameraCollisionModifier>();
}
if (CollisionModifier)
{
bAnyActive ? CollisionModifier->AddSingleRayOverrider(this) : CollisionModifier->RemoveSingleRayOverrider(this);
}
}
UGCTickActiveAnimation(DeltaTime, InOutPOV);
return false;
}
void UUGC_CameraAnimationModifier::CameraAnimation_SetEasingOutDelegate(FOnCameraAnimationEaseOutStarted& InOnAnimationEaseOutStarted, FCameraAnimationHandle AnimationHandle)
{
if (AnimationHandle.IsValid() && IsCameraAnimationActive(AnimationHandle))
{
OnAnimationEaseOutStarted = InOnAnimationEaseOutStarted;
}
}
void UUGC_CameraAnimationModifier::CameraAnimation_SetEndedDelegate(FOnCameraAnimationEnded& InOnAnimationEnded, FCameraAnimationHandle AnimationHandle)
{
if (AnimationHandle.IsValid() && IsCameraAnimationActive(AnimationHandle))
{
OnAnimationEnded = InOnAnimationEnded;
}
}
void UUGC_CameraAnimationModifier::UGCDeactivateCameraAnimation(FActiveCameraAnimationInfo& ActiveAnimation)
{
for (auto& a : ActiveAnimations)
{
if (a.Handle == ActiveAnimation.Handle)
{
if (a.Player && !ensure(a.Player->GetPlaybackStatus() == EMovieScenePlayerStatus::Stopped))
{
a.Player->Stop();
}
a = FActiveCameraAnimationInfo();
}
}
}
void UUGC_CameraAnimationModifier::UGCTickActiveAnimation(float DeltaTime, FMinimalViewInfo& InOutPOV)
{
UGCCameraManager = Cast<AUGC_PlayerCameraManager>(CameraOwner);
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
if (!UGCCameraManager)
{
return;
}
if (ActiveAnimations.Num() >= 1)
{
for (int i = 0; i < ActiveAnimations.Num(); ++i)
{
FActiveCameraAnimationInfo& ActiveAnimation = ActiveAnimations[i];
if (ActiveAnimation.IsValid())
{
#if ENABLE_DRAW_DEBUG
UGCDebugAnimation(ActiveAnimation, DeltaTime);
#endif
// float const Dilation = UGameplayStatics::GetGlobalTimeDilation(GetWorld());
// float const UndilatedDeltaTime = FMath::IsNearlyZero(Dilation) ? 0.f : DeltaTime / Dilation;
UGCTickAnimation(ActiveAnimation, DeltaTime, InOutPOV, i);
UGCTickAnimCollision(ActiveAnimation, DeltaTime, InOutPOV, i);
if (ActiveAnimation.Player->GetPlaybackStatus() == EMovieScenePlayerStatus::Stopped)
{
// Here animation has just finished (ease out has completed as well)
UGCDeactivateCameraAnimation(ActiveAnimation);
static bool constexpr bInterrupt = false;
OnAnimationEnded.ExecuteIfBound(ActiveAnimation.Sequence, bInterrupt);
}
}
}
}
}
void UUGC_CameraAnimationModifier::UGCTickAnimation(FActiveCameraAnimationInfo& CameraAnimation, float DeltaTime, FMinimalViewInfo& InOutPOV, int Index)
{
check(CameraAnimation.Player);
check(CameraAnimation.CameraStandIn);
const FCameraAnimationParams Params = CameraAnimation.Params;
UCameraAnimationSequencePlayer* Player = CameraAnimation.Player;
UCameraAnimationSequenceCameraStandIn* CameraStandIn = CameraAnimation.CameraStandIn;
const FFrameRate InputRate = Player->GetInputRate();
const FFrameTime CurrentPosition = Player->GetCurrentPosition();
const float CurrentTime = InputRate.AsSeconds(CurrentPosition);
const float DurationTime = InputRate.AsSeconds(Player->GetDuration()) * Params.PlayRate;
const float ScaledDeltaTime = DeltaTime * Params.PlayRate;
const float NewTime = CurrentTime + ScaledDeltaTime;
const FFrameTime NewPosition = CurrentPosition + DeltaTime * Params.PlayRate * InputRate;
// Advance any easing times.
if (CameraAnimation.bIsEasingIn)
{
CameraAnimation.EaseInCurrentTime += DeltaTime;
}
if (CameraAnimation.bIsEasingOut)
{
CameraAnimation.EaseOutCurrentTime += DeltaTime;
}
ECameraAnimationResetType ResetType = UGCAnimInfo[Index].ResetType;
bool const bWasEasingOut = UGCAnimInfo[Index].bWasEasingOut;
// Start easing out if we're nearing the end.
// CameraAnimation may already be easing out if StopCameraAnimation has been called.
if (!Player->GetIsLooping() && !CameraAnimation.bIsEasingOut)
{
const float BlendOutStartTime = DurationTime - Params.EaseOutDuration;
if (NewTime > BlendOutStartTime)
{
CameraAnimation.bIsEasingOut = true;
CameraAnimation.EaseOutCurrentTime = NewTime - BlendOutStartTime;
if (!bWasEasingOut)
{
// Here animation has just started easing out but hasn't finished yet
OnAnimationEaseOutStarted.ExecuteIfBound(CameraAnimation.Sequence);
}
}
}
// Check if we're done easing in or out.
bool bIsDoneEasingOut = false;
if (CameraAnimation.bIsEasingIn)
{
if (CameraAnimation.EaseInCurrentTime > Params.EaseInDuration || Params.EaseInDuration == 0.f)
{
CameraAnimation.bIsEasingIn = false;
}
}
if (CameraAnimation.bIsEasingOut)
{
if (CameraAnimation.EaseOutCurrentTime > Params.EaseOutDuration)
{
bIsDoneEasingOut = true;
}
}
// Figure out the final easing weight.
const float EasingInT = FMath::Clamp((CameraAnimation.EaseInCurrentTime / Params.EaseInDuration), 0.f, 1.f);
const float EasingInWeight = CameraAnimation.bIsEasingIn ?
EvaluateEasing(Params.EaseInType, EasingInT) : 1.f;
const float EasingOutT = FMath::Clamp((1.f - CameraAnimation.EaseOutCurrentTime / Params.EaseOutDuration), 0.f, 1.f);
const float EasingOutWeight = CameraAnimation.bIsEasingOut ?
EvaluateEasing(Params.EaseOutType, EasingOutT) : 1.f;
const float TotalEasingWeight = FMath::Min(EasingInWeight, EasingOutWeight);
// We might be done playing. Normally the player will stop on its own, but there are other situation in which
// the responsibility falls to this code:
// - If the animation is looping and waiting for an explicit Stop() call on us.
// - If there was a Stop() call with bImmediate=false to let an animation blend out.
if (bIsDoneEasingOut || TotalEasingWeight <= 0.f)
{
Player->Stop();
return;
}
UMovieSceneEntitySystemLinker* Linker = Player->GetEvaluationTemplate().GetEntitySystemLinker();
CameraStandIn->Reset(InOutPOV, Linker);
// Get the "unanimated" properties that need to be treated additively.
const float OriginalFieldOfView = CameraStandIn->FieldOfView;
// Update the sequence.
Player->Update(NewPosition);
// Recalculate properties that might be invalidated by other properties having been animated.
CameraStandIn->RecalcDerivedData();
// Grab the final animated (animated) values, figure out the delta, apply scale, and feed that into the result.
// Transform is always treated as a local, additive value. The data better be good.
const float Scale = Params.Scale * TotalEasingWeight;
const FTransform AnimatedTransform = CameraStandIn->GetTransform();
FVector AnimatedLocation = AnimatedTransform.GetLocation() * Scale;
FRotator AnimatedRotation = AnimatedTransform.GetRotation().Rotator() * Scale;
const FCameraAnimationHelperOffset CameraOffset{ AnimatedLocation, AnimatedRotation };
FVector OwnerLocation = GetViewTarget()->GetActorLocation();
// If using a character, camera should start from the pivot location of the mesh.
{
ACharacter* OwnerCharacter = GetViewTargetAs<ACharacter>();
if (OwnerCharacter && OwnerCharacter->GetMesh())
{
OwnerLocation = OwnerCharacter->GetMesh()->GetComponentLocation();
}
}
// TO DO #GravityCompatibility
FRotator const OwnerRot = FRotator(0.f, GetViewTarget()->GetActorRotation().Yaw, 0.f);
const FMatrix OwnerRotationMatrix = FRotationMatrix(OwnerRot);
FMinimalViewInfo InPOV = InOutPOV;
// Blend from current camera location to actor location
InPOV.Location = FMath::Lerp(InOutPOV.Location, OwnerLocation, Scale);
InPOV.Rotation = FMath::Lerp(InOutPOV.Rotation, OwnerRot, Scale);
FCameraAnimationHelper::ApplyOffset(OwnerRotationMatrix, InPOV, CameraOffset, AnimatedLocation, AnimatedRotation);
InOutPOV.Location = AnimatedLocation;
InOutPOV.Rotation = AnimatedRotation;
// Blend back depending on reset type
if (ResetType != ECameraAnimationResetType::BackToStart
&& CameraOwner && CameraOwner->GetOwningPlayerController() && CameraAnimation.bIsEasingOut && !bWasEasingOut)
{
FRotator TargetRot = OwnerRot;
switch (ResetType)
{
case ECameraAnimationResetType::ContinueFromEnd:
{
bool const bIsStrafing = UGCCameraManager->IsOwnerStrafing();
if (!bIsStrafing)
{
// TO DO #GravityCompatibility
TargetRot = FRotator(InOutPOV.Rotation.Pitch, InOutPOV.Rotation.Yaw, 0.f);
}
break;
}
default:
break;
}
CameraOwner->GetOwningPlayerController()->SetControlRotation(TargetRot);
}
// FieldOfView follows the current camera's value every frame, so we can compute how much the animation is
// changing it.
const float AnimatedFieldOfView = CameraStandIn->FieldOfView;
const float DeltaFieldOfView = AnimatedFieldOfView - OriginalFieldOfView;
InOutPOV.FOV = OriginalFieldOfView + DeltaFieldOfView * Scale;
// Add the post-process settings.
if (CameraOwner != nullptr && CameraStandIn->PostProcessBlendWeight > 0.f)
{
CameraOwner->AddCachedPPBlend(CameraStandIn->PostProcessSettings, CameraStandIn->PostProcessBlendWeight);
}
UGCAnimInfo[Index].bWasEasingOut = CameraAnimation.bIsEasingOut;
}
FVector UUGC_CameraAnimationModifier::GetTraceSafeLocation(FMinimalViewInfo const& InPOV)
{
AActor* TargetActor = GetViewTarget();
FVector SafeLocation = TargetActor ? TargetActor->GetActorLocation() : FVector::Zero();
if (TargetActor && UGCCameraManager)
{
if (USpringArmComponent* SpringArm = UGCCameraManager->GetOwnerSpringArmComponent())
{
SafeLocation = SpringArm->GetComponentLocation() + SpringArm->TargetOffset;
}
else if (UPrimitiveComponent const* PPActorRootComponent = Cast<UPrimitiveComponent>(TargetActor->GetRootComponent()))
{
// Attempt at picking SafeLocation automatically, so we reduce camera translation when aiming.
// Our camera is our reticle, so we want to preserve our aim and keep that as steady and smooth as possible.
// Pick closest point on capsule to our aim line.
FVector ClosestPointOnLineToCapsuleCenter;
FMath::PointDistToLine(SafeLocation, InPOV.Rotation.Vector(), InPOV.Location, ClosestPointOnLineToCapsuleCenter);
// Adjust Safe distance height to be same as aim line, but within capsule.
float const PushInDistance = UGCCameraAnimationHelper::CollisionProbe.ProbeRadius;
float const MaxHalfHeight = TargetActor->GetSimpleCollisionHalfHeight() - PushInDistance;
SafeLocation.Z = FMath::Clamp(ClosestPointOnLineToCapsuleCenter.Z, SafeLocation.Z - MaxHalfHeight, SafeLocation.Z + MaxHalfHeight);
float DistanceSqr = 0.f;
PPActorRootComponent->GetSquaredDistanceToCollision(ClosestPointOnLineToCapsuleCenter, DistanceSqr, SafeLocation);
// Push back inside capsule to avoid initial penetration when doing line checks.
SafeLocation += (SafeLocation - ClosestPointOnLineToCapsuleCenter).GetSafeNormal() * PushInDistance;
}
}
return SafeLocation;
}
void UUGC_CameraAnimationModifier::UGCTickAnimCollision(FActiveCameraAnimationInfo& CameraAnimation, float DeltaTime, FMinimalViewInfo& InOutPOV, int Index)
{
if (!UGCAnimInfo[Index].bDoCollisionChecks)
{
return;
}
check(CameraAnimation.Player);
check(CameraAnimation.CameraStandIn);
const FCameraAnimationParams& Params = CameraAnimation.Params;
const float BlendInTime = FMath::Max(UGCCameraAnimationHelper::CollisionBlendInTime, UE_KINDA_SMALL_NUMBER);
float BlendOutTime = FMath::Max(UGCCameraAnimationHelper::CollisionBlendOutTime, UE_KINDA_SMALL_NUMBER);
if (CameraAnimation.bIsEasingOut)
{
BlendOutTime = FMath::Min(Params.EaseOutDuration - CameraAnimation.EaseOutCurrentTime, UGCCameraAnimationHelper::CollisionBlendOutTime);
BlendOutTime = FMath::Max(BlendOutTime, UE_KINDA_SMALL_NUMBER);
}
FVector SafeLoc = GetTraceSafeLocation(InOutPOV);
const FVector CameraLoc = InOutPOV.Location;
const FVector BaseRay = CameraLoc - SafeLoc;
if (BaseRay.IsNearlyZero())
{
return;
}
const FRotationMatrix BaseMatrix(BaseRay.Rotation());
const FVector Right = BaseMatrix.GetUnitAxis(EAxis::Y);
const FVector Up = BaseMatrix.GetUnitAxis(EAxis::Z);
float HardBlockedPct = UGCAnimInfo[Index].DistBlockedPct;
float SoftBlockedPct = UGCAnimInfo[Index].DistBlockedPct;
float BlockedThisFrame = 1.f;
UWorld* World = GetWorld();
int32 NbrHits = 0;
const AActor* OwningActor = GetViewTarget();
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(CameraPen), false, OwningActor);
QueryParams.AddIgnoredActor(OwningActor);
{
const FPenetrationAvoidanceFeeler& Feeler = UGCCameraAnimationHelper::CollisionProbe;
const FRotator OffsetRot = Feeler.AdjustmentRot;
FVector RayTarget = BaseRay.RotateAngleAxis(OffsetRot.Yaw, Up).RotateAngleAxis(OffsetRot.Pitch, Right) + SafeLoc;
FHitResult Hit;
FCollisionShape Shape = FCollisionShape::MakeSphere(Feeler.ProbeRadius);
bool bHit = World->SweepSingleByChannel(Hit, SafeLoc, RayTarget, FQuat::Identity, ECollisionChannel::ECC_Camera, Shape, QueryParams);
if (bHit && Hit.GetActor())
{
if (Hit.GetActor()->ActorHasTag(UGCCameraAnimationHelper::CollisionIgnoreActorWithTag))
{
QueryParams.AddIgnoredActor(Hit.GetActor());
}
else
{
++NbrHits;
const float Weight = Hit.GetActor()->IsA<APawn>() ? Feeler.PawnWeight : Feeler.WorldWeight;
float NewBlockPct = Hit.Time + (1.f - Hit.Time) * (1.f - Weight);
NewBlockPct = (Hit.Location - SafeLoc).Size() / (RayTarget - SafeLoc).Size();
BlockedThisFrame = FMath::Min(NewBlockPct, BlockedThisFrame);
HardBlockedPct = BlockedThisFrame;
}
}
}
#if ENABLE_DRAW_DEBUG
if (NbrHits > 0)
{
if (bDebug && GEngine != nullptr)
{
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6
FString const& DebugText = FString::Printf(TEXT("UGC_CameraAnimationModifier: %d feeler%hs colliding."), NbrHits, NbrHits > 1 ? "s" : "");
#else
FString const& DebugText = FString::Printf(TEXT("UGC_CameraAnimationModifier: %d feeler%s colliding."), NbrHits, NbrHits > 1 ? "s" : "");
#endif
GEngine->AddOnScreenDebugMessage(-1, DeltaTime, FColor(150, 150, 200), DebugText);
}
}
#endif
if (UGCAnimInfo[Index].DistBlockedPct < BlockedThisFrame)
{
UGCAnimInfo[Index].DistBlockedPct += DeltaTime / BlendOutTime * (BlockedThisFrame - UGCAnimInfo[Index].DistBlockedPct);
}
else if (UGCAnimInfo[Index].DistBlockedPct > HardBlockedPct)
{
UGCAnimInfo[Index].DistBlockedPct = HardBlockedPct;
}
else if (UGCAnimInfo[Index].DistBlockedPct > SoftBlockedPct)
{
UGCAnimInfo[Index].DistBlockedPct -= DeltaTime / BlendInTime * (UGCAnimInfo[Index].DistBlockedPct - SoftBlockedPct);
}
UGCAnimInfo[Index].DistBlockedPct = FMath::Clamp(UGCAnimInfo[Index].DistBlockedPct, 0.f, 1.f);
InOutPOV.Location = SafeLoc + (CameraLoc - SafeLoc) * UGCAnimInfo[Index].DistBlockedPct;
}
#if ENABLE_DRAW_DEBUG
void UUGC_CameraAnimationModifier::UGCDebugAnimation(FActiveCameraAnimationInfo& ActiveAnimation, float DeltaTime)
{
if (bDebug && ActiveAnimation.IsValid() && GEngine)
{
const FFrameRate InputRate = ActiveAnimation.Player->GetInputRate();
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 3
const FFrameTime DurationFrames = ActiveAnimation.Player->GetDuration();
#elif ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 3
const FFrameNumber DurationFrames = ActiveAnimation.Player->GetDuration();
#endif
const FFrameTime CurrentPosition = ActiveAnimation.Player->GetCurrentPosition();
const float CurrentTime = InputRate.AsSeconds(CurrentPosition);
const float DurationSeconds = InputRate.AsSeconds(DurationFrames);
const FString LoopString = ActiveAnimation.Params.bLoop ? TEXT(" - Looping") : TEXT("");
const FString EaseInString = ActiveAnimation.bIsEasingIn ? FString::Printf(TEXT(" - Easing In: %f / %f"), ActiveAnimation.EaseInCurrentTime, ActiveAnimation.Params.EaseInDuration) : TEXT("");
const FString EaseOutString = ActiveAnimation.bIsEasingOut ? FString::Printf(TEXT(" - Easing Out: %f / %f"), ActiveAnimation.EaseOutCurrentTime, ActiveAnimation.Params.EaseOutDuration) : TEXT("");
const FString DebugText = FString::Printf(TEXT("UGC_CameraAnimationModifier: %s - PlayRate: %f%s - Duration: %f - Elapsed: %f%s%s"), *GetNameSafe(ActiveAnimation.Sequence), ActiveAnimation.Params.PlayRate, *LoopString, DurationSeconds, CurrentTime, *EaseInString, *EaseOutString);
GEngine->AddOnScreenDebugMessage(-1, DeltaTime, FColor(49, 61, 255), DebugText);
}
}
#endif

View File

@@ -0,0 +1,332 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Modifiers/UGC_CameraCollisionModifier.h"
#include "Camera/PlayerCameraManager.h"
#include "Camera/UGC_PlayerCameraManager.h"
#include "CineCameraActor.h"
#include "CollisionQueryParams.h"
#include "CollisionShape.h"
#include "Components/PrimitiveComponent.h"
#include "DrawDebugHelpers.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/Actor.h"
#include "GameFramework/Character.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/CameraBlockingVolume.h"
#include "GameFramework/SpringArmComponent.h"
#include "Runtime/Launch/Resources/Version.h"
/*
* DEPRECATED. USE UGC_SpringArmComponent INSTEAD.
*/
namespace CollisionHelpers
{
#if ENABLE_DRAW_DEBUG
FColor const DebugColor = FColor(150, 150, 200);
#endif
}
UUGC_CameraCollisionModifier::UUGC_CameraCollisionModifier()
{
Priority = 134;
bExclusive = true;
}
bool UUGC_CameraCollisionModifier::ModifyCamera(float DeltaTime, FMinimalViewInfo& InOutPOV)
{
Super::ModifyCamera(DeltaTime, InOutPOV);
if (!bPlayDuringCameraAnimations)
{
if (UGCCameraManager && UGCCameraManager->IsPlayingAnyCameraAnimation())
{
return false;
}
}
if (SpringArm)
{
// Don't do collision tests is spring arm is taking care of them.
if (SpringArm->bDoCollisionTest && CollisionSettings.bPreventPenetration)
{
#if ENABLE_DRAW_DEBUG
if (GEngine != nullptr)
{
FString const CameraManagerName = GetNameSafe(CameraOwner);
FString const ActorName = GetNameSafe(GetViewTarget());
FString const SpringName = GetNameSafe(SpringArm);
FString const DebugText = FString::Printf(TEXT("Actor %s has Collision Checks enabled on SpringArm %s but Camera Manager %s has Camera Modifier UGC_CameraCollisionModifier. This is not allowed so the modifier will be aborted.")
, *ActorName, *SpringName, *CameraManagerName);
GEngine->AddOnScreenDebugMessage(-1, DeltaTime, FColor::Red, DebugText);
}
#endif
return false;
}
}
// Do collision checks
UpdatePreventPenetration(DeltaTime, InOutPOV);
return false;
}
void UUGC_CameraCollisionModifier::UpdatePreventPenetration(float DeltaTime, FMinimalViewInfo& InOutPOV)
{
if (!CollisionSettings.bPreventPenetration)
{
return;
}
FVector const SafeLocation = GetTraceSafeLocation(InOutPOV);
// Then aim line to desired camera position
bool const bSingleRayPenetrationCheck = !CollisionSettings.bDoPredictiveAvoidance || !SingleRayOverriders.IsEmpty();
if (bSingleRayPenetrationCheck)
{
#if ENABLE_DRAW_DEBUG
if (bDebug && GEngine != nullptr)
{
FString const& DebugText = FString::Printf(TEXT("UGC_CameraCollisionModifier: Single Ray mode enabled."));
GEngine->AddOnScreenDebugMessage(-1, DeltaTime, CollisionHelpers::DebugColor, DebugText);
}
#endif
}
if (AActor* TargetActor = GetViewTarget())
{
PreventCameraPenetration(*TargetActor, SafeLocation, InOutPOV.Location, DeltaTime, AimLineToDesiredPosBlockedPct, bSingleRayPenetrationCheck);
}
}
FVector UUGC_CameraCollisionModifier::GetTraceSafeLocation(FMinimalViewInfo const& InPOV)
{
AActor* TargetActor = GetViewTarget();
FVector SafeLocation = TargetActor ? TargetActor->GetActorLocation() : FVector::Zero();
if (TargetActor)
{
if (SpringArm)
{
SafeLocation = SpringArm->GetComponentLocation() + SpringArm->TargetOffset;
}
else if (UPrimitiveComponent const* PPActorRootComponent = Cast<UPrimitiveComponent>(TargetActor->GetRootComponent()))
{
// Attempt at picking SafeLocation automatically, so we reduce camera translation when aiming.
// Our camera is our reticle, so we want to preserve our aim and keep that as steady and smooth as possible.
// Pick closest point on capsule to our aim line.
FVector ClosestPointOnLineToCapsuleCenter;
FMath::PointDistToLine(SafeLocation, InPOV.Rotation.Vector(), InPOV.Location, ClosestPointOnLineToCapsuleCenter);
// Adjust Safe distance height to be same as aim line, but within capsule.
float const PushInDistance = CollisionSettings.PenetrationAvoidanceFeelers[0].ProbeRadius;
float const MaxHalfHeight = TargetActor->GetSimpleCollisionHalfHeight() - PushInDistance;
SafeLocation.Z = FMath::Clamp(ClosestPointOnLineToCapsuleCenter.Z, SafeLocation.Z - MaxHalfHeight, SafeLocation.Z + MaxHalfHeight);
float DistanceSqr = 0.f;
PPActorRootComponent->GetSquaredDistanceToCollision(ClosestPointOnLineToCapsuleCenter, DistanceSqr, SafeLocation);
// Push back inside capsule to avoid initial penetration when doing line checks.
if (CollisionSettings.PenetrationAvoidanceFeelers.Num() > 0)
{
SafeLocation += (SafeLocation - ClosestPointOnLineToCapsuleCenter).GetSafeNormal() * PushInDistance;
}
}
}
return SafeLocation;
}
void UUGC_CameraCollisionModifier::PreventCameraPenetration(AActor const& ViewTarget, FVector const& SafeLoc, FVector& OutCameraLoc, float const& DeltaTime, float& OutDistBlockedPct, bool bSingleRayOnly)
{
#if ENABLE_DRAW_DEBUG
DebugActorsHitDuringCameraPenetration.Reset();
#endif
FVector const& CameraLoc = OutCameraLoc;
float HardBlockedPct = OutDistBlockedPct;
float SoftBlockedPct = OutDistBlockedPct;
FVector BaseRay = CameraLoc - SafeLoc;
FRotationMatrix BaseRayMatrix(BaseRay.Rotation());
FVector BaseRayLocalUp, BaseRayLocalFwd, BaseRayLocalRight;
BaseRayMatrix.GetScaledAxes(BaseRayLocalFwd, BaseRayLocalRight, BaseRayLocalUp);
float DistBlockedPctThisFrame = 1.f;
int32 const NumRaysToShoot = bSingleRayOnly ? FMath::Min(1, CollisionSettings.PenetrationAvoidanceFeelers.Num()) : CollisionSettings.PenetrationAvoidanceFeelers.Num();
CollidingFeelers = TStaticBitArray<128U>();
FCollisionQueryParams SphereParams(SCENE_QUERY_STAT(CameraPen), false, nullptr/*PlayerCamera*/);
SphereParams.AddIgnoredActor(&ViewTarget);
FCollisionShape SphereShape = FCollisionShape::MakeSphere(0.f);
UWorld* World = GetWorld();
int32 NbrHits = 0;
for (int32 RayIdx = 0; RayIdx < NumRaysToShoot; ++RayIdx)
{
FPenetrationAvoidanceFeeler& Feeler = CollisionSettings.PenetrationAvoidanceFeelers[RayIdx];
FRotator AdjustmentRot = Feeler.AdjustmentRot;
if (RayIdx == 0 && Feeler.AdjustmentRot != FRotator::ZeroRotator)
{
#if ENABLE_DRAW_DEBUG
if (GEngine != nullptr)
{
GEngine->AddOnScreenDebugMessage(-1, DeltaTime, FColor::Red, TEXT("UGC_CameraCollisionModifier: First Penetration Avoidance Feeler should always have an adjustment roation equal to 0,0,0!."));
}
#endif
AdjustmentRot = FRotator::ZeroRotator;
}
// calc ray target
FVector RayTarget;
{
// TO DO #GravityCompatibility
FVector RotatedRay = BaseRay.RotateAngleAxis(AdjustmentRot.Yaw, BaseRayLocalUp);
RotatedRay = RotatedRay.RotateAngleAxis(AdjustmentRot.Pitch, BaseRayLocalRight);
RayTarget = SafeLoc + RotatedRay;
}
// Cast for world and pawn hits separately. this is so we can safely ignore the camera's target pawn.
bool const bForceSmallShape = bSingleRayOnly && !SingleRayOverriders.IsEmpty();
SphereShape.Sphere.Radius = bForceSmallShape ? 2.f : Feeler.ProbeRadius;
ECollisionChannel TraceChannel = ECC_Camera;
// Do multi-line check to make sure the hits we throw out aren't masking real hits behind (these are important rays).
// Passing camera as actor so that camerablockingvolumes know when it's the camera doing traces.
FHitResult Hit;
const bool bHit = World->SweepSingleByChannel(Hit, SafeLoc, RayTarget, FQuat::Identity, TraceChannel, SphereShape, SphereParams);
#if ENABLE_DRAW_DEBUG
if (World->TimeSince(LastDrawDebugTime) < 1.f)
{
DrawDebugSphere(World, SafeLoc, SphereShape.Sphere.Radius, 8, FColor::Red);
DrawDebugSphere(World, bHit ? Hit.Location : RayTarget, SphereShape.Sphere.Radius, 8, FColor::Red);
DrawDebugLine(World, SafeLoc, bHit ? Hit.Location : RayTarget, FColor::Red);
}
#endif // ENABLE_DRAW_DEBUG
const AActor* HitActor = Hit.GetActor();
if (bHit && HitActor)
{
bool bIgnoreHit = false;
if (HitActor->ActorHasTag(CollisionSettings.IgnoreCameraCollisionTag))
{
bIgnoreHit = true;
SphereParams.AddIgnoredActor(HitActor);
}
// Ignore CameraBlockingVolume hits that occur in front of the ViewTarget.
if (!bIgnoreHit && HitActor->IsA<ACameraBlockingVolume>())
{
const FVector ViewTargetForwardXY = ViewTarget.GetActorForwardVector().GetSafeNormal2D();
const FVector ViewTargetLocation = ViewTarget.GetActorLocation();
const FVector HitOffset = Hit.Location - ViewTargetLocation;
const FVector HitDirectionXY = HitOffset.GetSafeNormal2D();
const float DotHitDirection = FVector::DotProduct(ViewTargetForwardXY, HitDirectionXY);
if (DotHitDirection > 0.0f)
{
bIgnoreHit = true;
// Ignore this CameraBlockingVolume on the remaining sweeps.
SphereParams.AddIgnoredActor(HitActor);
}
else
{
#if ENABLE_DRAW_DEBUG
DebugActorsHitDuringCameraPenetration.AddUnique(TObjectPtr<const AActor>(HitActor));
#endif
}
}
if (!bIgnoreHit)
{
CollidingFeelers[RayIdx] = true;
++NbrHits;
float const Weight = Cast<APawn>(Hit.GetActor()) ? Feeler.PawnWeight : Feeler.WorldWeight;
float NewBlockPct = Hit.Time;
NewBlockPct += (1.f - NewBlockPct) * (1.f - Weight);
// Recompute blocked pct taking into account pushout distance.
NewBlockPct = (Hit.Location - SafeLoc).Size() / (RayTarget - SafeLoc).Size();
DistBlockedPctThisFrame = FMath::Min(NewBlockPct, DistBlockedPctThisFrame);
#if ENABLE_DRAW_DEBUG
DebugActorsHitDuringCameraPenetration.AddUnique(TObjectPtr<const AActor>(HitActor));
#endif
}
}
if (RayIdx == 0)
{
// Don't interpolate toward this one, snap to it. THIS ASSUMES RAY 0 IS THE CENTER/MAIN RAY.
HardBlockedPct = DistBlockedPctThisFrame;
}
else
{
SoftBlockedPct = DistBlockedPctThisFrame;
}
}
#if ENABLE_DRAW_DEBUG
if (NbrHits > 0)
{
if (bDebug && GEngine != nullptr)
{
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6
FString const& DebugText = FString::Printf(TEXT("UGC_CameraCollisionModifier: %d feeler%hs colliding."), NbrHits, NbrHits > 1 ? "s" : "");
#else
FString const& DebugText = FString::Printf(TEXT("UGC_CameraCollisionModifier: %d feeler%s colliding."), NbrHits, NbrHits > 1 ? "s" : "");
#endif
GEngine->AddOnScreenDebugMessage(-1, DeltaTime, CollisionHelpers::DebugColor, DebugText);
}
}
#endif
if (OutDistBlockedPct < DistBlockedPctThisFrame)
{
// interpolate smoothly out
if (CollisionSettings.PenetrationBlendOutTime > DeltaTime)
{
OutDistBlockedPct = OutDistBlockedPct + DeltaTime / CollisionSettings.PenetrationBlendOutTime * (DistBlockedPctThisFrame - OutDistBlockedPct);
}
else
{
OutDistBlockedPct = DistBlockedPctThisFrame;
}
}
else
{
if (OutDistBlockedPct > HardBlockedPct)
{
OutDistBlockedPct = HardBlockedPct;
}
else if (OutDistBlockedPct > SoftBlockedPct)
{
// interpolate smoothly in
if (CollisionSettings.PenetrationBlendInTime > DeltaTime)
{
OutDistBlockedPct = OutDistBlockedPct - DeltaTime / CollisionSettings.PenetrationBlendInTime * (OutDistBlockedPct - SoftBlockedPct);
}
else
{
OutDistBlockedPct = SoftBlockedPct;
}
}
}
OutDistBlockedPct = FMath::Clamp<float>(OutDistBlockedPct, 0.f, 1.f);
if (OutDistBlockedPct < (1.f - ZERO_ANIMWEIGHT_THRESH))
{
OutCameraLoc = SafeLoc + (CameraLoc - SafeLoc) * OutDistBlockedPct;
}
}

View File

@@ -0,0 +1,421 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Modifiers/UGC_CameraDitheringModifier.h"
#include "CollisionQueryParams.h"
#include "CollisionShape.h"
#include "Camera/PlayerCameraManager.h"
#include "Camera/UGC_PlayerCameraManager.h"
#include "DrawDebugHelpers.h"
#include "Engine/Engine.h"
#include "Runtime/Launch/Resources/Version.h"
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 4
#include "Engine/OverlapResult.h"
#endif
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/Actor.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Components/PrimitiveComponent.h"
#include "Components/MeshComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/Actor.h"
#include "GameFramework/Pawn.h"
#include "Materials/MaterialInterface.h"
#include "Materials/MaterialLayersFunctions.h"
#include "Materials/MaterialParameterCollection.h"
#include "Materials/MaterialParameterCollectionInstance.h"
#include "Misc/Guid.h"
namespace DitherHelpers
{
bool DoAnyComponentsOverlap(AActor* inActor, ECollisionChannel overlapChannel)
{
if (inActor)
{
for (UActorComponent* ActorComponent : inActor->GetComponents())
{
UPrimitiveComponent* Primitive = Cast<UPrimitiveComponent>(ActorComponent);
if (Primitive && Primitive->IsCollisionEnabled())
{
if (Primitive->GetCollisionResponseToChannel(overlapChannel) == ECollisionResponse::ECR_Overlap)
{
return true;
}
}
}
}
return false;
}
int32 FindInactiveDitherState(TArray<FDitheredActorState>& DitherStates)
{
for (int32 Index = 0; Index < DitherStates.Num(); ++Index)
{
const FDitheredActorState& DitherState(DitherStates[Index]);
if (!DitherState.IsValid())
{
return Index;
}
}
return DitherStates.Emplace();
}
bool DitherStatesContain(TArray<FDitheredActorState>& DitherStates, AActor* InActor)
{
if (InActor)
{
for (auto const& State : DitherStates)
{
if (State.Actor == InActor)
{
return true;
}
}
}
return false;
}
FVector GetTraceSafeLocation(AActor* TargetActor, USpringArmComponent* SpringArm, FMinimalViewInfo const& InPOV)
{
FVector SafeLocation = TargetActor ? TargetActor->GetActorLocation() : FVector::Zero();
if (TargetActor)
{
// Try to get spring arm component
if (SpringArm)
{
SafeLocation = SpringArm->GetComponentLocation() + SpringArm->TargetOffset;
}
else if (UPrimitiveComponent const* PPActorRootComponent = Cast<UPrimitiveComponent>(TargetActor->GetRootComponent()))
{
// Attempt at picking SafeLocation automatically, so we reduce camera translation when aiming.
// Our camera is our reticle, so we want to preserve our aim and keep that as steady and smooth as possible.
// Pick closest point on capsule to our aim line.
FVector ClosestPointOnLineToCapsuleCenter;
FMath::PointDistToLine(SafeLocation, InPOV.Rotation.Vector(), InPOV.Location, ClosestPointOnLineToCapsuleCenter);
// Adjust Safe distance height to be same as aim line, but within capsule.
float const PushInDistance = 0.f;
float const MaxHalfHeight = TargetActor->GetSimpleCollisionHalfHeight() - PushInDistance;
SafeLocation.Z = FMath::Clamp(ClosestPointOnLineToCapsuleCenter.Z, SafeLocation.Z - MaxHalfHeight, SafeLocation.Z + MaxHalfHeight);
float DistanceSqr = 0.f;
PPActorRootComponent->GetSquaredDistanceToCollision(ClosestPointOnLineToCapsuleCenter, DistanceSqr, SafeLocation);
// Push back inside capsule to avoid initial penetration when doing line checks.
SafeLocation += (SafeLocation - ClosestPointOnLineToCapsuleCenter).GetSafeNormal() * PushInDistance;
}
}
return SafeLocation;
}
}
void FDitheredActorState::StartDithering(AActor* InActor, EDitherType InDitherType)
{
if (InActor)
{
Actor = InActor;
CollisionTime = 0.f;
bIsDitheringIn = false;
bIsDitheringOut = false;
DitherType = InDitherType;
}
}
void FDitheredActorState::Invalidate()
{
Actor = nullptr;
CurrentOpacity = 1.f;
CollisionTime = 0.f;
bIsDitheringIn = false;
bIsDitheringOut = false;
DitherType = EDitherType::None;
}
void FDitheredActorState::ComputeOpacity(float DeltaTime, float DitherInSpeed, float DitherOutSpeed, float DitherMin)
{
if (!IsValid())
{
return;
}
bool const bCanDither = !bIsDitheringOut && bIsDitheringIn;
CurrentOpacity = FMath::FInterpTo(CurrentOpacity, bCanDither ? DitherMin : 1.f, DeltaTime, bIsDitheringOut ? DitherOutSpeed : DitherInSpeed);
}
UUGC_CameraDitheringModifier::UUGC_CameraDitheringModifier()
{
Priority = 255; // You want this modifier to be the last one to be executed so that it always knows the final camera position.
}
void UUGC_CameraDitheringModifier::ResetDitheredActors_Implementation()
{
for (auto& DitherState : DitheredActorStates)
{
DitherState.bIsDitheringIn = false;
DitherState.bIsDitheringOut = true;
DitherState.CurrentOpacity = 1.f;
static float constexpr DeltaTime = 1 / 60.f;
ApplyDithering(DeltaTime, DitherState);
DitherState.Invalidate();
}
}
bool UUGC_CameraDitheringModifier::ModifyCamera(float DeltaTime, FMinimalViewInfo& InOutPOV)
{
Super::ModifyCamera(DeltaTime, InOutPOV);
if (CameraOwner && CameraOwner->PCOwner && CameraOwner->PCOwner->GetPawn())
{
FCollisionQueryParams QueryParams(TEXT("CameraDithering"));
// Get All overlapped actors and LOS-blocking actors
TArray<AActor*> ActorsToDither;
if (DitheringSettings.bDitherOverlaps)
{
if (!DitheringSettings.bDitherOwner)
{
QueryParams.AddIgnoredActor(CameraOwner->PCOwner->GetPawn());
if (AActor* PendingTarget = CameraOwner->PendingViewTarget.Target)
{
QueryParams.AddIgnoredActor(PendingTarget);
}
}
TArray<FOverlapResult> OutOverlaps;
GetWorld()->OverlapMultiByChannel(OutOverlaps, InOutPOV.Location, FQuat::Identity, DitheringSettings.DitherOverlapChannel, FCollisionShape::MakeSphere(DitheringSettings.SphereCollisionRadius), QueryParams);
#if ENABLE_DRAW_DEBUG
if (GetWorld()->DebugDrawTraceTag == TEXT("CameraDithering"))
{
DrawDebugSphere(GetWorld(), InOutPOV.Location, DitheringSettings.SphereCollisionRadius, 32, FColor::Red);
}
#endif
for (auto const& Overlap : OutOverlaps)
{
// Ignore null actors
if (!Overlap.GetActor())
{
continue;
}
TArray<AActor*> OverlapActors;
OverlapActors.Add(Overlap.GetActor());
if (DitheringSettings.bDitherAttachedComponents)
{
Overlap.GetActor()->GetAttachedActors(OverlapActors, false, true);
}
for (auto& Actor : OverlapActors)
{
// Ignore null actors or actors with IgnoreDitheringTag
if (!Actor || Actor->ActorHasTag(DitheringSettings.IgnoreDitheringTag))
{
continue;
}
bool const bIsCollisionResponseCorrect = DitherHelpers::DoAnyComponentsOverlap(Actor, DitheringSettings.DitherOverlapChannel);
if (!bIsCollisionResponseCorrect)
{
continue;
}
ActorsToDither.Add(Actor);
// Start dithering actor if not done already
if (!DitherHelpers::DitherStatesContain(DitheredActorStates, Actor))
{
QueryParams.AddIgnoredActor(Actor);
int32 Index = DitherHelpers::FindInactiveDitherState(DitheredActorStates);
DitheredActorStates[Index].StartDithering(Actor, EDitherType::OverlappingCamera);
}
}
}
}
if (DitheringSettings.bDitherLineOfSight)
{
QueryParams.AddIgnoredActor(CameraOwner->PCOwner->GetPawn());
if (AActor* PendingTarget = CameraOwner->PendingViewTarget.Target)
{
QueryParams.AddIgnoredActor(PendingTarget);
}
TArray<FOverlapResult> OutOverlaps;
FVector const EndLocation = DitherHelpers::GetTraceSafeLocation(GetViewTarget(), SpringArm, InOutPOV);
FVector const ToTarget = (EndLocation - InOutPOV.Location).GetSafeNormal();
FVector const StartLocation = InOutPOV.Location + ToTarget * DitheringSettings.SphereCollisionRadius;
FVector const Delta = EndLocation - StartLocation;
FQuat const Rotation = FRotationMatrix::MakeFromX(Delta.GetSafeNormal()).ToQuat();
FVector const BoxOrigin = Delta * 0.5f + StartLocation;
FVector const BoxExtent = FVector(Delta.Size() * 0.495f, DitheringSettings.LOSProbeSize, DitheringSettings.LOSProbeSize);
FCollisionShape const Box = FCollisionShape::MakeBox(BoxExtent);
GetWorld()->OverlapMultiByChannel(OutOverlaps, BoxOrigin, Rotation, DitheringSettings.DitherLOSChannel, Box, QueryParams);
#if ENABLE_DRAW_DEBUG
if (GetWorld()->DebugDrawTraceTag == TEXT("CameraDithering"))
{
DrawDebugBox(GetWorld(), BoxOrigin, BoxExtent, Rotation, FColor::Red);
}
#endif
for (auto const& Overlap : OutOverlaps)
{
// Ignore null actors
if (!Overlap.GetActor())
{
continue;
}
TArray<AActor*> OverlapActors;
OverlapActors.Add(Overlap.GetActor());
if (DitheringSettings.bDitherAttachedComponents)
{
Overlap.GetActor()->GetAttachedActors(OverlapActors, false, true);
}
for (auto& Actor : OverlapActors)
{
// Ignore null actors or actors with IgnoreDitheringTag
if (!Actor || Actor->ActorHasTag(DitheringSettings.IgnoreDitheringTag))
{
continue;
}
ActorsToDither.Add(Actor);
// Start dithering actor if not done already
if (!DitherHelpers::DitherStatesContain(DitheredActorStates, Actor))
{
int32 Index = DitherHelpers::FindInactiveDitherState(DitheredActorStates);
DitheredActorStates[Index].StartDithering(Actor, EDitherType::BlockingLOS);
}
}
}
}
// Update dithered state of actors
for (auto& DitherState : DitheredActorStates)
{
if (!DitherState.IsValid())
{
continue;
}
// Set the opacity
DitherState.ComputeOpacity(DeltaTime, DitheringSettings.DitherInSpeed, DitheringSettings.DitherOutSpeed, DitheringSettings.MaterialDitherMinimum);
ApplyDithering(DeltaTime, DitherState);
#if ENABLE_DRAW_DEBUG
UGCDebugDithering(DitherState, DeltaTime, DitheringSettings.MaterialDitherMinimum);
#endif
// If the dithered actor isn't overlapped
if (!ActorsToDither.Contains(DitherState.Actor))
{
// Stop and do not dither in
DitherState.bIsDitheringIn = false;
DitherState.CollisionTime = 0.f;
DitherState.bIsDitheringOut = true;
// If finished dithering out
if (DitherState.CurrentOpacity >= 1.f)
{
DitherState.Invalidate();
}
}
// Otherwise if the dithered actor is still overlapped
else
{
// Do not dither out
DitherState.bIsDitheringOut = false;
if (DitherState.DitherType == EDitherType::OverlappingCamera || (DitherState.DitherType == EDitherType::BlockingLOS && DitherState.CollisionTime >= DitheringSettings.CollisionTimeThreshold))
{
DitherState.bIsDitheringIn = true;
}
DitherState.CollisionTime += DeltaTime;
}
}
}
return false;
}
void UUGC_CameraDitheringModifier::ApplyDithering(float DeltaTime, FDitheredActorState& DitherState)
{
if (!DitherState.IsValid())
{
return;
}
if (DitheringSettings.bUpdateMaterialPlayerPosition && CameraOwner && CameraOwner->PCOwner && CameraOwner->PCOwner->GetPawn() && DitheringMPC && GetWorld())
{
if (!DitheringMPCInstance)
{
DitheringMPCInstance = GetWorld()->GetParameterCollectionInstance(DitheringMPC.Get());
}
if (DitheringMPCInstance)
{
DitheringMPCInstance->SetVectorParameterValue(DitheringSettings.MaterialPlayerPositionParameterName, CameraOwner->PCOwner->GetPawn()->GetActorLocation());
}
}
// Get all mesh components
TArray<UMeshComponent*> Meshes;
DitherState.Actor->GetComponents(Meshes, DitheringSettings.bDitherChildActors);
for (auto& Mesh : Meshes)
{
if (!Mesh)
{
continue;
}
// Get all materials
const TArray<UMaterialInterface*> Materials = Mesh->GetMaterials();
for (auto& Material : Materials)
{
if (!Material)
{
continue;
}
// Get all float parameters
TArray<FMaterialParameterInfo> ScalarParams;
TArray<FGuid> ScalarParamIds;
Material->GetAllScalarParameterInfo(ScalarParams, ScalarParamIds);
// Set the params that have the same name as MaterialOpacityParameterName
for (auto& ScalarParam : ScalarParams)
{
if (ScalarParam.Name != DitheringSettings.MaterialOpacityParameterName)
{
continue;
}
Mesh->SetScalarParameterValueOnMaterials(DitheringSettings.MaterialOpacityParameterName, DitherState.CurrentOpacity);
}
}
}
}
#if ENABLE_DRAW_DEBUG
void UUGC_CameraDitheringModifier::UGCDebugDithering(FDitheredActorState& DitherState, float DeltaTime, float DitherMin)
{
if (bDebug && DitherState.IsValid() && GEngine)
{
const FString EaseInString = DitherState.bIsDitheringIn ? FString::Printf(TEXT(" - Dithering In")) : TEXT("");
const FString EaseOutString = DitherState.bIsDitheringOut ? FString::Printf(TEXT(" - Dithering Out")) : TEXT("");
const FString DebugText = FString::Printf(TEXT("UGC_CameraDitheringModifier: %s - Type: %s - Opacity: %f - Elapsed: %f%s%s")
, *GetNameSafe(DitherState.Actor), *UEnum::GetDisplayValueAsText(DitherState.DitherType).ToString(), DitherState.CurrentOpacity, DitherState.CollisionTime, *EaseInString, *EaseOutString);
GEngine->AddOnScreenDebugMessage(-1, DeltaTime, FColor::Silver, DebugText);
}
}
#endif

View File

@@ -0,0 +1,250 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Modifiers/UGC_CameraModifier.h"
#include "AuroraDevs_UGC.h"
#include "Camera/Modifiers/UGC_CameraAnimationModifier.h"
#include "Camera/PlayerCameraManager.h"
#include "Camera/UGC_PlayerCameraManager.h"
#include "GameFramework/Character.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
void UUGC_CameraModifier::EnableModifier()
{
Super::EnableModifier();
OnModifierEnabled(CameraOwner->GetCameraCacheView());
}
void UUGC_CameraModifier::DisableModifier(bool bImmediate)
{
Super::DisableModifier(bImmediate);
if (bImmediate && bDisabled && !bPendingDisable)
{
Alpha = 0.f;
}
OnModifierDisabled(CameraOwner->GetCameraCacheView(), bImmediate);
}
void UUGC_CameraModifier::OnModifierEnabled_Implementation(FMinimalViewInfo const& LastPOV)
{
}
void UUGC_CameraModifier::OnModifierDisabled_Implementation(FMinimalViewInfo const& LastPOV, bool bImmediate)
{
}
void UUGC_CameraModifier::ProcessBoomLengthAndFOV_Implementation(float DeltaTime, float InFOV, float InArmLength, FVector ViewLocation, FRotator ViewRotation, float& OutFOV, float& OutArmLength)
{
OutFOV = InFOV;
OutArmLength = InArmLength;
}
void UUGC_CameraModifier::ProcessBoomOffsets_Implementation(float DeltaTime, FVector InSocketOffset, FVector InTargetOffset, FVector ViewLocation, FRotator ViewRotation, FVector& OutSocketOffset, FVector& OutTargetOffset)
{
OutSocketOffset = InSocketOffset;
OutTargetOffset = InTargetOffset;
}
void UUGC_CameraModifier::OnAnyLevelSequenceStarted_Implementation()
{
}
void UUGC_CameraModifier::OnAnyLevelSequenceEnded_Implementation()
{
}
void UUGC_CameraModifier::OnSetViewTarget_Implementation(bool bImmediate, bool bNewTargetIsOwner)
{
}
void UUGC_CameraModifier::PostUpdate_Implementation(float DeltaTime, FVector ViewLocation, FRotator ViewRotation)
{
}
bool UUGC_CameraModifier::IsDebugEnabled() const
{
return bDebug;
}
void UUGC_CameraModifier::ToggleDebug(bool const bEnabled)
{
bDebug = bEnabled;
}
bool UUGC_CameraModifier::ModifyCamera(float DeltaTime, FMinimalViewInfo& InOutPOV)
{
return Super::ModifyCamera(DeltaTime, InOutPOV);
}
void UUGC_CameraModifier::ModifyCamera(float DeltaTime, FVector ViewLocation, FRotator ViewRotation, float FOV, FVector& OutViewLocation, FRotator& OutViewRotation, float& OutFOV)
{
Super::ModifyCamera(DeltaTime, ViewLocation, ViewRotation, FOV, OutViewLocation, OutViewRotation, OutFOV);
OutViewLocation = ViewLocation;
OutViewRotation = ViewRotation;
OutFOV = FOV;
UpdateOwnerReferences();
if (!UGCCameraManager)
{
return;
}
if (!bPlayDuringCameraAnimations)
{
if (UGCCameraManager->IsPlayingAnyCameraAnimation())
{
return;
}
}
if (OwnerController && OwnerPawn)
{
UpdateInternalVariables(DeltaTime);
if (SpringArm)
{
ProcessBoomLengthAndFOV(DeltaTime, FOV, CurrentArmLength, ViewLocation, ViewRotation, OutFOV, SpringArm->TargetArmLength);
ProcessBoomOffsets(DeltaTime, CurrentSocketOffset, CurrentTargetOffset, ViewLocation, ViewRotation, SpringArm->SocketOffset, SpringArm->TargetOffset);
}
else
{
UGC_LOG_ONCE(InvalidSpringArm, Error, TEXT("%s uses UGC but doesn't have a valid Spring Arm Component."), *GetNameSafe(OwnerPawn));
}
PostUpdate(DeltaTime, ViewLocation, ViewRotation);
}
}
bool UUGC_CameraModifier::ProcessViewRotation(AActor* ViewTarget, float DeltaTime, FRotator& OutViewRotation, FRotator& OutDeltaRot)
{
bool bResult = Super::ProcessViewRotation(ViewTarget, DeltaTime, OutViewRotation, OutDeltaRot);
if (!bPlayDuringCameraAnimations)
{
if (UGCCameraManager && UGCCameraManager->IsPlayingAnyCameraAnimation())
{
return bResult;
}
}
if (ViewTarget && CameraOwner && CameraOwner->GetOwningPlayerController())
{
// TO DO #GravityCompatibility
FVector const CameraLocation = CameraOwner->GetCameraLocation();
FRotator const ControlRotation = CameraOwner->GetOwningPlayerController()->GetControlRotation();
FRotator const OwnerRotation = ViewTarget ? ViewTarget->GetActorRotation() : FRotator::ZeroRotator;
FRotator InLocalControlRotation = ControlRotation - OwnerRotation;
InLocalControlRotation.Normalize();
bResult = ProcessControlRotation(ViewTarget, DeltaTime, CameraLocation, OutViewRotation, InLocalControlRotation, OutDeltaRot, OutDeltaRot);
}
return bResult;
}
void UUGC_CameraModifier::UpdateOwnerReferences()
{
UGCCameraManager = Cast<AUGC_PlayerCameraManager>(CameraOwner);
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
if (!UGCCameraManager)
{
return;
}
OwnerController = UGCCameraManager->GetOwningPlayerController();
OwnerCharacter = UGCCameraManager->OwnerCharacter;
OwnerPawn = UGCCameraManager->OwnerPawn;
if (OwnerPawn && OwnerPawn->IsLocallyControlled())
{
SpringArm = UGCCameraManager->CameraArm;
MovementComponent = UGCCameraManager->MovementComponent;
}
}
void UUGC_CameraModifier::UpdateInternalVariables(float DeltaTime)
{
if (!UGCCameraManager)
{
return;
}
bHasMovementInput = UGCCameraManager->bHasMovementInput;
PreviousMovementInput = MovementInput;
MovementInput = UGCCameraManager->MovementInput;
TimeSinceMovementInput = UGCCameraManager->TimeSinceMovementInput;
bHasRotationInput = UGCCameraManager->bHasRotationInput;
RotationInput = UGCCameraManager->RotationInput;
TimeSinceRotationInput = UGCCameraManager->TimeSinceRotationInput;
if (SpringArm)
{
CurrentSocketOffset = SpringArm->SocketOffset;
CurrentTargetOffset = SpringArm->TargetOffset;
CurrentArmLength = SpringArm->TargetArmLength;
}
}
bool UUGC_CameraModifier::ProcessTurnRate_Implementation(float DeltaTime, FRotator InLocalControlRotation, float InPitchTurnRate, float InYawTurnRate, float& OutPitchTurnRate, float& OutYawTurnRate)
{
return false;
}
bool UUGC_CameraModifier::ProcessControlRotation_Implementation(AActor* ViewTarget, float DeltaTime, FVector InViewLocation, FRotator InViewRotation, FRotator InLocalControlRotation, FRotator InDeltaRot, FRotator& OutDeltaRot)
{
return false;
}
FVector UUGC_CameraModifier::GetOwnerVelocity() const
{
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
return UGCCameraManager->GetOwnerVelocity();
}
bool UUGC_CameraModifier::IsOwnerFalling() const
{
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
return UGCCameraManager->IsOwnerFalling();
}
bool UUGC_CameraModifier::IsOwnerStrafing() const
{
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
return UGCCameraManager->IsOwnerStrafing();
}
bool UUGC_CameraModifier::IsOwnerMovingOnGround() const
{
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
return UGCCameraManager->IsOwnerMovingOnGround();
}
void UUGC_CameraModifier::ComputeOwnerFloorDistance(float SweepDistance, float CapsuleRadius, bool& bOutFloorExists, float& OutFloorDistance) const
{
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
return UGCCameraManager->ComputeOwnerFloorDist(SweepDistance, CapsuleRadius, bOutFloorExists, OutFloorDistance);
}
void UUGC_CameraModifier::ComputeOwnerFloorNormal(float SweepDistance, float CapsuleRadius, bool& bOutFloorExists, FVector& OutFloorNormal) const
{
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
return UGCCameraManager->ComputeOwnerFloorNormal(SweepDistance, CapsuleRadius, bOutFloorExists, OutFloorNormal);
}
void UUGC_CameraModifier::ComputeOwnerSlopeAngle(float& OutSlopePitchDegrees, float& OutSlopeRollDegrees)
{
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
return UGCCameraManager->ComputeOwnerSlopeAngle(OutSlopePitchDegrees, OutSlopeRollDegrees);
}
float UUGC_CameraModifier::ComputeOwnerLookAndMovementDot()
{
ensureMsgf(UGCCameraManager, TEXT("Please use UGC Camera Modifiers only with a player camera manager inheriting from UGC_PlayerCameraManager."));
return UGCCameraManager->ComputeOwnerLookAndMovementDot();
}
void UUGC_CameraAddOnModifier::SetSettings_Implementation(class UUGC_CameraAddOnModifierSettings* InSettings)
{
Settings = InSettings;
}

View File

@@ -0,0 +1,114 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Modifiers/UGC_CameraPropertiesAnimNotifyModifiers.h"
#include "Curves/CurveFloat.h"
#include "Engine/Engine.h"
UUGC_FOVAnimNotifyCameraModifier::UUGC_FOVAnimNotifyCameraModifier()
: Super()
{
bPlayDuringCameraAnimations = true;
RequestHelper.Init(TEXT("FOV"), [this]() -> bool { return bDebug; });
}
void UUGC_FOVAnimNotifyCameraModifier::ProcessBoomLengthAndFOV_Implementation(float DeltaTime, float InFOV, float InArmLength, FVector ViewLocation, FRotator ViewRotation, float& OutFOV, float& OutArmLength)
{
Super::ProcessBoomLengthAndFOV_Implementation(DeltaTime, InFOV, InArmLength, ViewLocation, ViewRotation, OutFOV, OutArmLength);
RequestHelper.ProcessValue(DeltaTime, InFOV, ViewLocation, ViewRotation, OutFOV);
}
void UUGC_FOVAnimNotifyCameraModifier::OnModifierDisabled_Implementation(FMinimalViewInfo const& LastPOV, bool bWasImmediate)
{
Super::OnModifierDisabled_Implementation(LastPOV, bWasImmediate);
RequestHelper.OnModifierDisabled(LastPOV, bWasImmediate);
}
void UUGC_FOVAnimNotifyCameraModifier::PushFOVAnimNotifyRequest(FGuid RequestId, float TargetFOV, float TotalDuration, float BlendInDuration, UCurveFloat* BlendInCurve, float BlendOutDuration, UCurveFloat* BlendOutCurve)
{
RequestHelper.PushValueRequest(RequestId, TargetFOV, TotalDuration, BlendInDuration, BlendInCurve, BlendOutDuration, BlendOutCurve);
}
void UUGC_FOVAnimNotifyCameraModifier::PopFOVAnimNotifyRequest(FGuid RequestId)
{
RequestHelper.PopValueRequest(RequestId);
}
UUGC_ArmOffsetAnimNotifyCameraModifier::UUGC_ArmOffsetAnimNotifyCameraModifier()
: Super()
{
bPlayDuringCameraAnimations = false;
SocketOffsetRequestHelper.Init(TEXT("SocketOffset"), [this]() -> bool { return bDebug; });
TargetOffsetRequestHelper.Init(TEXT("TargetOffset"), [this]() -> bool { return bDebug; });
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
SocketOffsetRequestHelper.PropertyColor = FColor(252, 195, 53, 255);
TargetOffsetRequestHelper.PropertyColor = FColor(255, 212, 105, 255);
#endif
}
void UUGC_ArmOffsetAnimNotifyCameraModifier::ProcessBoomOffsets_Implementation(float DeltaTime, FVector InSocketOffset, FVector InTargetOffset, FVector ViewLocation, FRotator ViewRotation, FVector& OutSocketOffset, FVector& OutTargetOffset)
{
Super::ProcessBoomOffsets_Implementation(DeltaTime, InSocketOffset, InTargetOffset, ViewLocation, ViewRotation, OutSocketOffset, OutTargetOffset);
SocketOffsetRequestHelper.ProcessValue(DeltaTime, InSocketOffset, ViewLocation, ViewRotation, OutSocketOffset);
TargetOffsetRequestHelper.ProcessValue(DeltaTime, InTargetOffset, ViewLocation, ViewRotation, OutTargetOffset);
}
void UUGC_ArmOffsetAnimNotifyCameraModifier::OnModifierDisabled_Implementation(FMinimalViewInfo const& LastPOV, bool bWasImmediate)
{
Super::OnModifierDisabled_Implementation(LastPOV, bWasImmediate);
SocketOffsetRequestHelper.OnModifierDisabled(LastPOV, bWasImmediate);
TargetOffsetRequestHelper.OnModifierDisabled(LastPOV, bWasImmediate);
}
void UUGC_ArmOffsetAnimNotifyCameraModifier::PushArmSocketOffsetAnimNotifyRequest(FGuid RequestId, FVector TargetOffset, float TotalDuration, float BlendInDuration, UCurveFloat* BlendInCurve, float BlendOutDuration, UCurveFloat* BlendOutCurve)
{
SocketOffsetRequestHelper.PushValueRequest(RequestId, TargetOffset, TotalDuration, BlendInDuration, BlendInCurve, BlendOutDuration, BlendOutCurve);
}
void UUGC_ArmOffsetAnimNotifyCameraModifier::PopArmSocketOffsetAnimNotifyRequest(FGuid RequestId)
{
SocketOffsetRequestHelper.PopValueRequest(RequestId);
}
void UUGC_ArmOffsetAnimNotifyCameraModifier::PushArmTargetOffsetAnimNotifyRequest(FGuid RequestId, FVector TargetOffset, float TotalDuration, float BlendInDuration, UCurveFloat* BlendInCurve, float BlendOutDuration, UCurveFloat* BlendOutCurve)
{
TargetOffsetRequestHelper.PushValueRequest(RequestId, TargetOffset, TotalDuration, BlendInDuration, BlendInCurve, BlendOutDuration, BlendOutCurve);
}
void UUGC_ArmOffsetAnimNotifyCameraModifier::PopArmTargetOffsetAnimNotifyRequest(FGuid RequestId)
{
TargetOffsetRequestHelper.PopValueRequest(RequestId);
}
UUGC_ArmLengthAnimNotifyCameraModifier::UUGC_ArmLengthAnimNotifyCameraModifier()
: Super()
{
bPlayDuringCameraAnimations = false;
RequestHelper.Init(TEXT("ArmLength"), [this]() -> bool { return bDebug; });
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
RequestHelper.PropertyColor = FColor(55, 219, 33, 255);
#endif
}
void UUGC_ArmLengthAnimNotifyCameraModifier::ProcessBoomLengthAndFOV_Implementation(float DeltaTime, float InFOV, float InArmLength, FVector ViewLocation, FRotator ViewRotation, float& OutFOV, float& OutArmLength)
{
Super::ProcessBoomLengthAndFOV_Implementation(DeltaTime, InFOV, InArmLength, ViewLocation, ViewRotation, OutFOV, OutArmLength);
RequestHelper.ProcessValue(DeltaTime, InArmLength, ViewLocation, ViewRotation, OutArmLength);
}
void UUGC_ArmLengthAnimNotifyCameraModifier::OnModifierDisabled_Implementation(FMinimalViewInfo const& LastPOV, bool bWasImmediate)
{
Super::OnModifierDisabled_Implementation(LastPOV, bWasImmediate);
RequestHelper.OnModifierDisabled(LastPOV, bWasImmediate);
}
void UUGC_ArmLengthAnimNotifyCameraModifier::PushArmLengthAnimNotifyRequest(FGuid RequestId, float TargetLength, float TotalDuration, float BlendInDuration, UCurveFloat* BlendInCurve, float BlendOutDuration, UCurveFloat* BlendOutCurve)
{
RequestHelper.PushValueRequest(RequestId, TargetLength, TotalDuration, BlendInDuration, BlendInCurve, BlendOutDuration, BlendOutCurve);
}
void UUGC_ArmLengthAnimNotifyCameraModifier::PopArmLengthAnimNotifyRequest(FGuid RequestId)
{
RequestHelper.PopValueRequest(RequestId);
}

View File

@@ -0,0 +1,100 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/Modifiers/UGC_PlayCameraAnimCallbackProxy.h"
#include "Camera/PlayerCameraManager.h"
namespace PlayCameraAnimCallbackProxyHelper
{
FCameraAnimationHandle const UGCInvalid(MAX_int16, 0);
}
UUGC_PlayCameraAnimCallbackProxy::UUGC_PlayCameraAnimCallbackProxy(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bInterruptedCalledBeforeBlendingOut(false)
{
}
FUGCCameraAnimationParams::operator FCameraAnimationParams() const
{
FCameraAnimationParams Params;
Params.PlayRate = PlayRate;
Params.EaseInDuration = EaseInDuration;
Params.EaseOutDuration = EaseOutDuration;
Params.EaseInType = EaseInType;
Params.EaseOutType = EaseOutType;
Params.bLoop = false;
return Params;
}
UUGC_PlayCameraAnimCallbackProxy* UUGC_PlayCameraAnimCallbackProxy::CreateProxyObjectForPlayCameraAnim(APlayerCameraManager* InPlayerCameraManager, TSubclassOf<UUGC_CameraAnimationModifier> ModifierClass, UCameraAnimationSequence* CameraSequence, FUGCCameraAnimationParams Params, FCameraAnimationHandle& Handle, bool bInterruptOthers, bool bDoCollisionChecks)
{
UUGC_PlayCameraAnimCallbackProxy* Proxy = NewObject<UUGC_PlayCameraAnimCallbackProxy>();
Proxy->SetFlags(RF_StrongRefOnFrame);
Proxy->PlayCameraAnimation(InPlayerCameraManager, ModifierClass, CameraSequence, Params, Handle, bInterruptOthers, bDoCollisionChecks);
return Proxy;
}
UUGC_PlayCameraAnimCallbackProxy* UUGC_PlayCameraAnimCallbackProxy::CreateProxyObjectForPlayCameraAnimForModifier(UUGC_CameraAnimationModifier* CameraAnimationModifier, UCameraAnimationSequence* CameraSequence, FUGCCameraAnimationParams Params, FCameraAnimationHandle& Handle, bool bInterruptOthers, bool bDoCollisionChecks)
{
UUGC_PlayCameraAnimCallbackProxy* Proxy = NewObject<UUGC_PlayCameraAnimCallbackProxy>();
Proxy->SetFlags(RF_StrongRefOnFrame);
Proxy->PlayCameraAnimation(CameraAnimationModifier, CameraSequence, Params, Handle, bInterruptOthers, bDoCollisionChecks);
return Proxy;
}
void UUGC_PlayCameraAnimCallbackProxy::PlayCameraAnimation(UUGC_CameraAnimationModifier* CameraAnimModifier, UCameraAnimationSequence* CameraSequence, FUGCCameraAnimationParams Params, FCameraAnimationHandle& Handle, bool bInterruptOthers, bool bDoCollisionChecks)
{
Handle = PlayCameraAnimCallbackProxyHelper::UGCInvalid;
bool bPlayedSuccessfully = false;
if (CameraAnimModifier)
{
Handle = CameraAnimModifier->PlaySingleCameraAnimation(CameraSequence, static_cast<FCameraAnimationParams>(Params), Params.ResetType, bInterruptOthers, bDoCollisionChecks);
bPlayedSuccessfully = Handle.IsValid();
if (bPlayedSuccessfully)
{
CameraAnimationModifierPtr = CameraAnimModifier;
CameraAnimationEasingOutDelegate.BindUObject(this, &UUGC_PlayCameraAnimCallbackProxy::OnCameraAnimationEasingOut);
CameraAnimationModifierPtr->CameraAnimation_SetEasingOutDelegate(CameraAnimationEasingOutDelegate, Handle);
CameraAnimationEndedDelegate.BindUObject(this, &UUGC_PlayCameraAnimCallbackProxy::OnCameraAnimationEnded);
CameraAnimationModifierPtr->CameraAnimation_SetEndedDelegate(CameraAnimationEndedDelegate, Handle);
}
}
if (!bPlayedSuccessfully)
{
OnInterrupted.Broadcast();
}
}
void UUGC_PlayCameraAnimCallbackProxy::PlayCameraAnimation(APlayerCameraManager* InPlayerCameraManager, TSubclassOf<UUGC_CameraAnimationModifier> ModifierClass, UCameraAnimationSequence* CameraSequence, FUGCCameraAnimationParams Params, FCameraAnimationHandle& Handle, bool bInterruptOthers, bool bDoCollisionChecks)
{
if (InPlayerCameraManager)
{
if (UUGC_CameraAnimationModifier* CameraAnimModifier = Cast<UUGC_CameraAnimationModifier>(InPlayerCameraManager->FindCameraModifierByClass(ModifierClass)))
{
PlayCameraAnimation(CameraAnimModifier, CameraSequence, Params, Handle, bInterruptOthers, bDoCollisionChecks);
}
}
}
void UUGC_PlayCameraAnimCallbackProxy::OnCameraAnimationEasingOut(UCameraAnimationSequence* CameraAnimation)
{
OnEaseOut.Broadcast();
}
void UUGC_PlayCameraAnimCallbackProxy::OnCameraAnimationEnded(UCameraAnimationSequence* CameraAnimation, bool bInterrupted)
{
if (!bInterrupted)
{
OnCompleted.Broadcast();
}
else if (!bInterruptedCalledBeforeBlendingOut)
{
OnInterrupted.Broadcast();
}
}

View File

@@ -0,0 +1,791 @@
// Copyright(c) Aurora Devs 2022-2025. All Rights Reserved.
#include "Camera/UGC_PlayerCameraManager.h"
#include "Camera/CameraComponent.h"
#include "Camera/Data/UGC_CameraData.h"
#include "Camera/Modifiers/UGC_CameraAnimationModifier.h"
#include "Camera/Modifiers/UGC_CameraModifier.h"
#include "Camera/Modifiers/UGC_PlayCameraAnimCallbackProxy.h"
#include "CameraAnimationSequence.h"
#include "Components/CapsuleComponent.h"
#include "DrawDebugHelpers.h"
#include "Engine/Canvas.h"
#include "Engine/World.h"
#include "EngineUtils.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "LevelSequenceActor.h"
#include "LevelSequencePlayer.h"
#include "Pawn/UGC_PawnInterface.h"
TAutoConsoleVariable<bool> GShowCameraManagerModifiersCVar(
TEXT("ShowCameraModifiersDebug"),
false,
TEXT("Show information about the currently active camera modifiers and their priorities."));
namespace PlayerCameraHelpers
{
UCameraComponent* GetSpringArmChildCamera(USpringArmComponent* SpringArm)
{
UCameraComponent* Camera = nullptr;
if (SpringArm)
{
for (int32 i = 0; i < SpringArm->GetNumChildrenComponents(); ++i)
{
USceneComponent* Child = SpringArm->GetChildComponent(i);
if (UCameraComponent* PotentialCamera = Cast<UCameraComponent>(Child))
{
Camera = PotentialCamera;
break; // found it
}
}
}
return Camera;
}
}
AUGC_PlayerCameraManager::AUGC_PlayerCameraManager()
{
PrimaryActorTick.bCanEverTick = true;
}
void AUGC_PlayerCameraManager::InitializeFor(APlayerController* PC)
{
Super::InitializeFor(PC);
RefreshLevelSequences(); // TO DO, this might not work for open worlds if level sequences are loaded in runtime.
}
void AUGC_PlayerCameraManager::PrePossess(APawn* NewPawn, UUGC_CameraDataAssetBase* NewCameraDA, bool bBlendSpringArmProperties, bool bMatchCameraRotation)
{
if (!NewPawn)
{
return;
}
USpringArmComponent* NewSpringArm = NewPawn->FindComponentByClass<USpringArmComponent>();
if (!NewSpringArm || !CameraArm)
{
return;
}
if (bBlendSpringArmProperties)
{
const bool bSkipChecks = !NewCameraDA || !GetCurrentCameraDataAsset();
if (bSkipChecks || ((NewCameraDA->ArmLengthSettings.MinArmLength != GetCurrentCameraDataAsset()->ArmLengthSettings.MinArmLength
|| NewCameraDA->ArmLengthSettings.MaxArmLength != GetCurrentCameraDataAsset()->ArmLengthSettings.MaxArmLength)
&& NewCameraDA->ArmLengthSettings.ArmRangeBlendTime > 0.f))
{
NewSpringArm->TargetArmLength = CameraArm->TargetArmLength;
}
if (bSkipChecks || (NewCameraDA->ArmOffsetSettings.ArmSocketOffset != GetCurrentCameraDataAsset()->ArmOffsetSettings.ArmSocketOffset
&& NewCameraDA->ArmOffsetSettings.ArmSocketOffsetBlendTime > 0.f))
{
NewSpringArm->SocketOffset = CameraArm->SocketOffset;
}
if (bSkipChecks || (NewCameraDA->ArmOffsetSettings.ArmTargetOffset != GetCurrentCameraDataAsset()->ArmOffsetSettings.ArmTargetOffset
&& NewCameraDA->ArmOffsetSettings.ArmTargetOffsetBlendTime > 0.f))
{
NewSpringArm->TargetOffset = CameraArm->TargetOffset;
}
if (bSkipChecks || ((NewCameraDA->FOVSettings.MinFOV != GetCurrentCameraDataAsset()->FOVSettings.MinFOV
|| NewCameraDA->FOVSettings.MaxFOV != GetCurrentCameraDataAsset()->FOVSettings.MaxFOV)
&& NewCameraDA->FOVSettings.FOVRangeBlendTime > 0.f))
{
if (UCameraComponent* NewCamera = PlayerCameraHelpers::GetSpringArmChildCamera(NewSpringArm))
{
NewCamera->SetFieldOfView(ViewTarget.POV.FOV);
}
}
}
else if (NewCameraDA)
{
const float PitchRatio = FMath::GetMappedRangeValueClamped(
FVector2D(ViewPitchMin, ViewPitchMax), FVector2D(-1.f, 1.f),
bMatchCameraRotation ? static_cast<float>(ViewTarget.POV.Rotation.Pitch) : 0.f);
NewSpringArm->TargetArmLength = NewCameraDA->PitchToArmAndFOVCurveSettings.PitchToArmLengthCurve ?
FMath::GetMappedRangeValueClamped(FVector2D(-1.f, 1.f), FVector2D(NewCameraDA->ArmLengthSettings.MinArmLength, NewCameraDA->ArmLengthSettings.MaxArmLength), NewCameraDA->PitchToArmAndFOVCurveSettings.PitchToArmLengthCurve->GetFloatValue(PitchRatio))
: NewCameraDA->ArmLengthSettings.MinArmLength;
NewSpringArm->SocketOffset = NewCameraDA->ArmOffsetSettings.ArmSocketOffset;
NewSpringArm->TargetOffset = NewCameraDA->ArmOffsetSettings.ArmTargetOffset;
if (UCameraComponent* NewCamera = PlayerCameraHelpers::GetSpringArmChildCamera(NewSpringArm))
{
NewCamera->SetFieldOfView(NewCameraDA->PitchToArmAndFOVCurveSettings.PitchToFOVCurve ?
FMath::GetMappedRangeValueClamped(FVector2D(-1.f, 1.f), FVector2D(NewCameraDA->FOVSettings.MinFOV, NewCameraDA->FOVSettings.MaxFOV), NewCameraDA->PitchToArmAndFOVCurveSettings.PitchToFOVCurve->GetFloatValue(PitchRatio))
: NewCameraDA->FOVSettings.MinFOV);
}
}
if (bMatchCameraRotation)
{
PendingPossessPayload.bMatchCameraRotation = true;
PendingPossessPayload.PendingControlRotation = GetOwningPlayerController()->GetControlRotation();
}
PendingPossessPayload.PendingCameraDA = NewCameraDA;
PendingPossessPayload.bBlendCameraProperties = bBlendSpringArmProperties;
}
void AUGC_PlayerCameraManager::PostPossess(bool bReplaceCurrentCameraDA)
{
if (PendingPossessPayload.PendingCameraDA)
{
UUGC_CameraDataAssetBase* CurrentHead = GetCurrentCameraDataAsset();
PushCameraData_Internal(PendingPossessPayload.PendingCameraDA, PendingPossessPayload.bBlendCameraProperties);
if (bReplaceCurrentCameraDA && CurrentHead)
{
PopCameraData(CurrentHead);
}
}
if (PendingPossessPayload.bMatchCameraRotation)
{
GetOwningPlayerController()->SetControlRotation(PendingPossessPayload.PendingControlRotation);
}
PendingPossessPayload = AUGC_PlayerCameraManager::PossessPayload();
}
void AUGC_PlayerCameraManager::RefreshLevelSequences()
{
// This resets the array and gets all actors of class.
QUICK_SCOPE_CYCLE_COUNTER(AUGC_PlayerCameraManager_RefreshLevelSequences);
LevelSequences.Reset();
for (TActorIterator<ALevelSequenceActor> It(GetWorld()); It; ++It)
{
ALevelSequenceActor* LevelSequence = *It;
LevelSequences.Add(LevelSequence);
LevelSequence->GetSequencePlayer()->OnPlay.AddDynamic(this, &AUGC_PlayerCameraManager::OnLevelSequenceStarted);
LevelSequence->GetSequencePlayer()->OnPlayReverse.AddDynamic(this, &AUGC_PlayerCameraManager::OnLevelSequenceStarted);
LevelSequence->GetSequencePlayer()->OnStop.AddDynamic(this, &AUGC_PlayerCameraManager::OnLevelSequenceEnded);
LevelSequence->GetSequencePlayer()->OnPause.AddDynamic(this, &AUGC_PlayerCameraManager::OnLevelSequencePaused);
}
}
void AUGC_PlayerCameraManager::OnLevelSequenceStarted()
{
if (NbrActivePausedLevelSequences > 0) --NbrActivePausedLevelSequences;
++NbrActiveLevelSequences;
DoForEachUGCModifier(&UUGC_CameraModifier::OnAnyLevelSequenceStarted);
}
void AUGC_PlayerCameraManager::OnLevelSequencePaused()
{
++NbrActivePausedLevelSequences;
--NbrActiveLevelSequences;
ensure(NbrActiveLevelSequences >= 0);
}
void AUGC_PlayerCameraManager::OnLevelSequenceEnded()
{
--NbrActiveLevelSequences;
ensure(NbrActiveLevelSequences >= 0);
DoForEachUGCModifier(&UUGC_CameraModifier::OnAnyLevelSequenceEnded);
}
void AUGC_PlayerCameraManager::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
UpdateInternalVariables(DeltaTime);
}
bool AUGC_PlayerCameraManager::IsPlayingAnyCameraAnimation() const
{
if (UUGC_CameraAnimationModifier const* CameraAnimModifier = FindCameraModifierOfType<UUGC_CameraAnimationModifier>())
{
return CameraAnimModifier && CameraAnimModifier->IsAnyCameraAnimationSequence();
}
return false;
}
void AUGC_PlayerCameraManager::PlayCameraAnimation(UCameraAnimationSequence* CameraSequence, FUGCCameraAnimationParams const& Params, bool bInterruptOthers, bool bDoCollisionChecks)
{
if (UUGC_CameraAnimationModifier* CameraAnimModifier = FindCameraModifierOfType<UUGC_CameraAnimationModifier>())
{
CameraAnimModifier->PlaySingleCameraAnimation(CameraSequence, static_cast<FCameraAnimationParams>(Params), Params.ResetType, bInterruptOthers, bDoCollisionChecks);
}
}
void AUGC_PlayerCameraManager::SetViewTarget(AActor* NewViewTarget, FViewTargetTransitionParams TransitionParams)
{
auto OldPendingTarget = PendingViewTarget.Target;
auto OldTarget = ViewTarget.Target;
Super::SetViewTarget(NewViewTarget, TransitionParams);
if (!OwnerPawn)
{
return;
}
bool const bAssignedNewTarget = ViewTarget.Target != OldTarget;
bool const bBlendingToNewTarget = PendingViewTarget.Target != OldPendingTarget;
if (bAssignedNewTarget || bBlendingToNewTarget)
{
bool const bWasImmediate = bAssignedNewTarget && !bBlendingToNewTarget;
bool bNewTargetIsOwner = false;
// If old character has been unpossessed, then our new target is the owner!
// Or, if the view target is the controller, we are doing seamless travel which does not unpossess the pawn.
if (bWasImmediate && (OwnerPawn->GetController() == nullptr || OwnerPawn->GetController() == ViewTarget.Target))
{
bNewTargetIsOwner = true;
}
else
{
bNewTargetIsOwner = bWasImmediate ? ViewTarget.Target == OwnerPawn : PendingViewTarget.Target == OwnerPawn;
}
DoForEachUGCModifier([bNewTargetIsOwner, bWasImmediate](UUGC_CameraModifier* UGCModifier)
{
UGCModifier->OnSetViewTarget(bWasImmediate, bNewTargetIsOwner);
});
}
}
UCameraModifier* AUGC_PlayerCameraManager::FindCameraModifierOfClass(TSubclassOf<UCameraModifier> ModifierClass, bool bIncludeInherited)
{
for (UCameraModifier* Mod : ModifierList)
{
if (bIncludeInherited)
{
if (Mod->GetClass()->IsChildOf(ModifierClass))
{
return Mod;
}
}
else
{
if (Mod->GetClass() == ModifierClass)
{
return Mod;
}
}
}
return nullptr;
}
UCameraModifier const* AUGC_PlayerCameraManager::FindCameraModifierOfClass(TSubclassOf<UCameraModifier> ModifierClass, bool bIncludeInherited) const
{
for (UCameraModifier* Mod : ModifierList)
{
if (bIncludeInherited)
{
if (Mod->GetClass()->IsChildOf(ModifierClass))
{
return Mod;
}
}
else
{
if (Mod->GetClass() == ModifierClass)
{
return Mod;
}
}
}
return nullptr;
}
void AUGC_PlayerCameraManager::ToggleUGCCameraModifiers(bool const bEnabled, bool const bImmediate)
{
DoForEachUGCModifier([bEnabled, bImmediate](UUGC_CameraModifier* UGCModifier)
{
if (bEnabled)
{
UGCModifier->EnableModifier();
}
else
{
UGCModifier->DisableModifier(bImmediate);
}
});
}
void AUGC_PlayerCameraManager::ToggleCameraModifiers(bool const bEnabled, bool const bImmediate)
{
for (int32 ModifierIdx = 0; ModifierIdx < ModifierList.Num(); ModifierIdx++)
{
if (ModifierList[ModifierIdx] != nullptr)
{
if (bEnabled)
{
ModifierList[ModifierIdx]->EnableModifier();
}
else
{
ModifierList[ModifierIdx]->DisableModifier(bImmediate);
}
}
}
}
void AUGC_PlayerCameraManager::ToggleAllUGCModifiersDebug(bool const bEnabled)
{
DoForEachUGCModifier([bEnabled](UUGC_CameraModifier* UGCModifier)
{
if (!UGCModifier->IsDisabled())
{
UGCModifier->bDebug = bEnabled;
}
});
}
void AUGC_PlayerCameraManager::ToggleAllModifiersDebug(bool const bEnabled)
{
for (int32 ModifierIdx = 0; ModifierIdx < ModifierList.Num(); ModifierIdx++)
{
if (ModifierList[ModifierIdx] != nullptr && !ModifierList[ModifierIdx]->IsDisabled())
{
ModifierList[ModifierIdx]->bDebug = bEnabled;
}
}
}
void AUGC_PlayerCameraManager::PushCameraData_Internal(UUGC_CameraDataAssetBase* CameraDA, bool bBlendCameraProperties)
{
CameraDataStack.Push(CameraDA);
OnCameraDataStackChanged(CameraDA, bBlendCameraProperties);
}
void AUGC_PlayerCameraManager::PushCameraData(UUGC_CameraDataAssetBase* CameraDA)
{
static constexpr bool bBlendCameraProperties = true;
PushCameraData_Internal(CameraDA, bBlendCameraProperties);
}
void AUGC_PlayerCameraManager::PopCameraDataHead()
{
CameraDataStack.Pop();
OnCameraDataStackChanged(CameraDataStack.IsEmpty() ? nullptr : CameraDataStack[CameraDataStack.Num() - 1]);
}
void AUGC_PlayerCameraManager::PopCameraData(UUGC_CameraDataAssetBase* CameraDA)
{
if (CameraDataStack.IsEmpty())
{
return;
}
if (GetCurrentCameraDataAsset() == CameraDA)
{
PopCameraDataHead();
}
CameraDataStack.Remove(CameraDA);
}
void AUGC_PlayerCameraManager::OnCameraDataStackChanged_Implementation(UUGC_CameraDataAssetBase* CameraDA, bool bBlendSpringArmProperties)
{
}
void AUGC_PlayerCameraManager::ProcessViewRotation(float DeltaTime, FRotator& OutViewRotation, FRotator& OutDeltaRot)
{
Super::ProcessViewRotation(DeltaTime, OutViewRotation, OutDeltaRot);
if (PCOwner && ViewTarget.Target)
{
FRotator const ControlRotation = PCOwner->GetControlRotation();
FRotator const OwnerRotation = ViewTarget.Target->GetActorRotation();
FRotator InLocalControlRotation = ControlRotation - OwnerRotation;
InLocalControlRotation.Normalize();
float OutPitchTurnRate = PitchTurnRate;
float OutYawTurnRate = YawTurnRate;
// TO DO #GravityCompatibility
ProcessTurnRate(DeltaTime, InLocalControlRotation, OutPitchTurnRate, OutYawTurnRate);
PitchTurnRate = FMath::Clamp(OutPitchTurnRate, 0.f, 1.f);
YawTurnRate = FMath::Clamp(OutYawTurnRate, 0.f, 1.f);
}
}
void AUGC_PlayerCameraManager::ProcessTurnRate(float DeltaTime, FRotator InLocalControlRotation, float& OutPitchTurnRate, float& OutYawTurnRate)
{
DoForEachUGCModifierWithBreak([&](UUGC_CameraModifier* UGCModifier) -> bool
{
if (!UGCModifier->IsDisabled())
{
if (!UGCModifier->CanPlayDuringCameraAnimation())
{
if (IsPlayingAnyCameraAnimation())
{
return false;
}
}
// TO DO #GravityCompatibility
return UGCModifier->ProcessTurnRate(DeltaTime, InLocalControlRotation, PitchTurnRate, YawTurnRate, OutPitchTurnRate, OutYawTurnRate);
}
return false; // Don't break
});
}
void AUGC_PlayerCameraManager::UpdateInternalVariables_Implementation(float DeltaTime)
{
AspectRatio = GetCameraCacheView().AspectRatio;
HorizontalFOV = GetFOVAngle();
ensureAlways(!FMath::IsNearlyZero(AspectRatio));
VerticalFOV = FMath::RadiansToDegrees(2.f * FMath::Atan(FMath::Tan(FMath::DegreesToRadians(HorizontalFOV) * 0.5f) / AspectRatio));
if (PCOwner && PCOwner->GetPawn())
{
APawn* NewOwnerPawn = PCOwner->GetPawn();
if (!OwnerPawn || NewOwnerPawn != OwnerPawn)
{
// Either initialising reference, or we have possessed a new character.
OwnerPawn = NewOwnerPawn;
if (OwnerCharacter = PCOwner->GetPawn<ACharacter>(),
OwnerCharacter != nullptr)
{
MovementComponent = OwnerCharacter->GetCharacterMovement();
}
CameraArm = OwnerPawn->FindComponentByClass<USpringArmComponent>();
OriginalArmLength = CameraArm ? CameraArm->TargetArmLength : 0.f;
}
if (OwnerPawn)
{
MovementInput = GetMovementControlInput();
bHasMovementInput = !MovementInput.IsZero();
TimeSinceMovementInput = bHasMovementInput ? 0.f : TimeSinceMovementInput + DeltaTime;
RotationInput = GetRotationInput();
bHasRotationInput = !RotationInput.IsZero();
TimeSinceRotationInput = bHasRotationInput ? 0.f : TimeSinceRotationInput + DeltaTime;
}
}
}
FRotator AUGC_PlayerCameraManager::GetRotationInput_Implementation() const
{
FRotator RotInput = FRotator::ZeroRotator;
if (OwnerPawn && OwnerPawn->GetClass()->ImplementsInterface(UUGC_PawnInterface::StaticClass()))
{
RotInput = IUGC_PawnInterface::Execute_GetRotationInput(OwnerPawn);
}
return RotInput;
}
FVector AUGC_PlayerCameraManager::GetMovementControlInput_Implementation() const
{
FVector MovInput = FVector::ZeroVector;
if (OwnerPawn && OwnerPawn->GetClass()->ImplementsInterface(UUGC_PawnInterface::StaticClass()))
{
MovInput = IUGC_PawnInterface::Execute_GetMovementInput(OwnerPawn);
}
return MovInput;
}
// Limit the view yaw in local space instead of world space.
void AUGC_PlayerCameraManager::LimitViewYaw(FRotator& ViewRotation, float InViewYawMin, float InViewYawMax)
{
// TO DO #GravityCompatibility
if (PCOwner && PCOwner->GetPawn())
{
FRotator ActorRotation = PCOwner->GetPawn()->GetActorRotation();
ViewRotation.Yaw = FMath::ClampAngle(ViewRotation.Yaw, ActorRotation.Yaw + InViewYawMin, ActorRotation.Yaw + InViewYawMax);
ViewRotation.Yaw = FRotator::ClampAxis(ViewRotation.Yaw);
}
}
void AUGC_PlayerCameraManager::DrawRealDebugCamera(float Duration, FLinearColor CameraColor, float Thickness) const
{
#if ENABLE_DRAW_DEBUG
::DrawDebugCamera(GetWorld(), ViewTarget.POV.Location, ViewTarget.POV.Rotation, ViewTarget.POV.FOV, 1.0f, CameraColor.ToFColor(true), false, Duration);
#endif
}
/** Draw a debug camera shape. */
void AUGC_PlayerCameraManager::DrawGameDebugCamera(float Duration, bool bDrawCamera, FLinearColor CameraColor, bool bDrawSpringArm, FLinearColor SpringArmColor, float Thickness) const
{
#if ENABLE_DRAW_DEBUG
if (bDrawCamera && CameraArm)
{
int32 const NbrComponents = CameraArm->GetNumChildrenComponents();
for (int32 i = 0; i < NbrComponents; ++i)
{
if (USceneComponent* ChildComp = CameraArm->GetChildComponent(i))
{
if (UCameraComponent* CameraComp = Cast<UCameraComponent>(ChildComp))
{
::DrawDebugCamera(GetWorld(), CameraComp->GetComponentLocation(), CameraComp->GetComponentRotation(), ViewTarget.POV.FOV, 1.0f, CameraColor.ToFColor(true), false, Duration);
if (bDrawSpringArm)
{
DrawDebugSpringArm(CameraComp->GetComponentLocation(), Duration, SpringArmColor, Thickness);
}
break;
}
}
}
}
#endif
}
void AUGC_PlayerCameraManager::DrawDebugSpringArm(FVector const& CameraLocation, float Duration, FLinearColor SpringArmColor, float Thickness) const
{
#if ENABLE_DRAW_DEBUG
if (CameraArm)
{
FVector const SafeLocation = CameraArm->GetComponentLocation() + CameraArm->TargetOffset;
::DrawDebugLine(GetWorld(), CameraLocation, SafeLocation, SpringArmColor.ToFColor(true), false, Duration, 0, Thickness);
}
#endif
}
void AUGC_PlayerCameraManager::DoForEachUGCModifier(TFunction<void(UUGC_CameraModifier*)> const& Function)
{
if (Function)
{
for (int32 ModifierIdx = 0; ModifierIdx < UGCModifiersList.Num(); ++ModifierIdx)
{
ensure(UGCModifiersList[ModifierIdx]);
if (UGCModifiersList[ModifierIdx])
{
Function(UGCModifiersList[ModifierIdx]);
}
}
}
}
void AUGC_PlayerCameraManager::DoForEachUGCModifierWithBreak(TFunction<bool(UUGC_CameraModifier*)> const& Function)
{
if (Function)
{
for (int32 ModifierIdx = 0; ModifierIdx < UGCModifiersList.Num(); ++ModifierIdx)
{
ensure(UGCModifiersList[ModifierIdx]);
if (UGCModifiersList[ModifierIdx])
{
if (Function(UGCModifiersList[ModifierIdx]))
{
break;
}
}
}
}
}
void AUGC_PlayerCameraManager::DisplayDebug(class UCanvas* Canvas, const FDebugDisplayInfo& DebugDisplay, float& YL, float& YPos)
{
Super::DisplayDebug(Canvas, DebugDisplay, YL, YPos);
const bool bShowModifierList = GShowCameraManagerModifiersCVar.GetValueOnGameThread();
if (bShowModifierList)
{
for (int32 ModifierIdx = 0; ModifierIdx < ModifierList.Num(); ++ModifierIdx)
{
if (ModifierList[ModifierIdx] != nullptr)
{
Canvas->SetDrawColor(FColor::White);
FString DebugString = FString::Printf(TEXT("UGC Modifier %d: %s - Priority %d"), ModifierIdx, *ModifierList[ModifierIdx]->GetName(), ModifierList[ModifierIdx]->Priority);
Canvas->DrawText(GEngine->GetSmallFont(), DebugString, YL, YPos);
YPos += YL;
}
}
}
}
UCameraModifier* AUGC_PlayerCameraManager::AddNewCameraModifier(TSubclassOf<UCameraModifier> ModifierClass)
{
UCameraModifier* AddedModifier = Super::AddNewCameraModifier(ModifierClass);
if (AddedModifier)
{
if (UUGC_CameraModifier* UGCModifier = Cast<UUGC_CameraModifier>(AddedModifier))
{
UGCModifiersList.Add(UGCModifier);
if (UUGC_CameraAddOnModifier* UGCAddOnModifier = Cast<UUGC_CameraAddOnModifier>(AddedModifier))
{
UGCAddOnModifiersList.Add(UGCAddOnModifier);
}
}
}
return AddedModifier;
}
bool AUGC_PlayerCameraManager::RemoveCameraModifier(UCameraModifier* ModifierToRemove)
{
if (ModifierToRemove)
{
if (UUGC_CameraModifier* UGCModifierToRemove = Cast<UUGC_CameraModifier>(ModifierToRemove))
{
// Loop through each modifier in camera
for (int32 ModifierIdx = 0; ModifierIdx < UGCModifiersList.Num(); ++ModifierIdx)
{
// If we found ourselves, remove ourselves from the list and return
if (UGCModifiersList[ModifierIdx] == UGCModifierToRemove)
{
UGCModifiersList.RemoveAt(ModifierIdx, 1);
break;
}
}
if (UUGC_CameraAddOnModifier* UGCAddOnModifier = Cast<UUGC_CameraAddOnModifier>(ModifierToRemove))
{
// Loop through each modifier in camera
for (int32 ModifierIdx = 0; ModifierIdx < UGCAddOnModifiersList.Num(); ++ModifierIdx)
{
// If we found ourselves, remove ourselves from the list and return
if (UGCAddOnModifiersList[ModifierIdx] == UGCModifierToRemove)
{
UGCAddOnModifiersList.RemoveAt(ModifierIdx, 1);
break;
}
}
}
}
}
return Super::RemoveCameraModifier(ModifierToRemove);
}
FVector AUGC_PlayerCameraManager::GetOwnerVelocity() const
{
FVector Velocity = FVector::ZeroVector;
if (MovementComponent)
{
Velocity = MovementComponent->Velocity;
}
else if (OwnerPawn && OwnerPawn->GetClass()->ImplementsInterface(UUGC_PawnMovementInterface::StaticClass()))
{
Velocity = IUGC_PawnMovementInterface::Execute_GetOwnerVelocity(OwnerPawn);
}
return Velocity;
}
bool AUGC_PlayerCameraManager::IsOwnerFalling() const
{
bool bIsFalling = false;
if (MovementComponent)
{
bIsFalling = MovementComponent->IsFalling();
}
else if (OwnerPawn && OwnerPawn->GetClass()->ImplementsInterface(UUGC_PawnMovementInterface::StaticClass()))
{
bIsFalling = IUGC_PawnMovementInterface::Execute_IsOwnerFalling(OwnerPawn);
}
return bIsFalling;
}
bool AUGC_PlayerCameraManager::IsOwnerStrafing() const
{
bool bIsStrafing = false;
if (MovementComponent && OwnerPawn)
{
bIsStrafing = OwnerPawn->bUseControllerRotationYaw || (MovementComponent->bUseControllerDesiredRotation && !MovementComponent->bOrientRotationToMovement);
}
else if (OwnerPawn)
{
if (OwnerPawn->GetClass()->ImplementsInterface(UUGC_PawnMovementInterface::StaticClass()))
{
bIsStrafing = IUGC_PawnMovementInterface::Execute_IsOwnerStrafing(OwnerPawn);
}
else
{
bIsStrafing = OwnerPawn->bUseControllerRotationYaw;
}
}
return bIsStrafing;
}
bool AUGC_PlayerCameraManager::IsOwnerMovingOnGround() const
{
bool bIsMovingOnGround = false;
if (MovementComponent)
{
bIsMovingOnGround = MovementComponent->IsMovingOnGround();
}
else if (OwnerPawn && OwnerPawn->GetClass()->ImplementsInterface(UUGC_PawnMovementInterface::StaticClass()))
{
bIsMovingOnGround = IUGC_PawnMovementInterface::Execute_IsOwnerMovingOnGround(OwnerPawn);
}
return bIsMovingOnGround;
}
void AUGC_PlayerCameraManager::ComputeOwnerFloorDist(float SweepDistance, float CapsuleRadius, bool& bOutFloorExists, float& OutFloorDistance) const
{
if (MovementComponent && OwnerCharacter)
{
CapsuleRadius = FMath::Max(CapsuleRadius, OwnerCharacter->GetCapsuleComponent()->GetScaledCapsuleRadius());
FFindFloorResult OutFloorResult;
MovementComponent->ComputeFloorDist(OwnerPawn->GetActorLocation(), SweepDistance, SweepDistance, OutFloorResult, CapsuleRadius);
bOutFloorExists = OutFloorResult.bBlockingHit;
OutFloorDistance = bOutFloorExists ? OutFloorResult.FloorDist : 0.f;
}
else
{
FHitResult OutHit;
bOutFloorExists = GetWorld()->SweepSingleByChannel(OutHit
, OwnerPawn->GetActorLocation()
, OwnerPawn->GetActorLocation() - SweepDistance * FVector::UpVector
, FQuat::Identity
, ECollisionChannel::ECC_Visibility
, FCollisionShape::MakeSphere(CapsuleRadius));
OutFloorDistance = bOutFloorExists ? OutHit.Distance : 0.f;
}
}
void AUGC_PlayerCameraManager::ComputeOwnerFloorNormal(float SweepDistance, float CapsuleRadius, bool& bOutFloorExists, FVector& OutFloorNormal) const
{
if (MovementComponent && OwnerCharacter)
{
bOutFloorExists = MovementComponent->CurrentFloor.IsWalkableFloor();
OutFloorNormal = MovementComponent->CurrentFloor.HitResult.ImpactNormal;
}
else
{
FHitResult OutHit;
bOutFloorExists = GetWorld()->SweepSingleByChannel(OutHit
, OwnerPawn->GetActorLocation()
, OwnerPawn->GetActorLocation() - SweepDistance * FVector::UpVector
, FQuat::Identity
, ECollisionChannel::ECC_Visibility
, FCollisionShape::MakeSphere(CapsuleRadius));
bOutFloorExists = OutHit.bBlockingHit;
OutFloorNormal = bOutFloorExists ? OutHit.ImpactNormal : FVector::ZeroVector;
}
}
void AUGC_PlayerCameraManager::ComputeOwnerSlopeAngle(float& OutSlopePitchDegrees, float& OutSlopeRollDegrees)
{
bool bOutFloorExists = false;
FVector OutFloorNormal = FVector::ZeroVector;
ComputeOwnerFloorNormal(96.f, 64.f, bOutFloorExists, OutFloorNormal);
UKismetMathLibrary::GetSlopeDegreeAngles(OwnerPawn->GetActorRightVector(), OutFloorNormal, OwnerPawn->GetActorUpVector(), OutSlopePitchDegrees, OutSlopeRollDegrees);
}
float AUGC_PlayerCameraManager::ComputeOwnerLookAndMovementDot()
{
if (IsOwnerStrafing())
{
return 1.f;
}
FVector const Velocity = GetOwnerVelocity();
if (Velocity.IsNearlyZero())
{
return 0.f;
}
float const Dot = Velocity | OwnerPawn->GetControlRotation().Vector();
return Dot;
}