// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraCameraMode.h" #include "Components/CapsuleComponent.h" #include "Containers/UnrealString.h" #include "Engine/Canvas.h" #include "GameFramework/Actor.h" #include "GameFramework/Character.h" #include "GameFramework/Pawn.h" #include "LyraCameraComponent.h" #include "LyraPlayerCameraManager.h" #include "Math/Color.h" #include "Misc/AssertionMacros.h" #include "Templates/Casts.h" #include "UObject/Class.h" #include "UObject/UnrealNames.h" ////////////////////////////////////////////////////////////////////////// // FLyraCameraModeView ////////////////////////////////////////////////////////////////////////// FLyraCameraModeView::FLyraCameraModeView() : Location(ForceInit) , Rotation(ForceInit) , ControlRotation(ForceInit) , FieldOfView(LYRA_CAMERA_DEFAULT_FOV) { } void FLyraCameraModeView::Blend(const FLyraCameraModeView& Other, float OtherWeight) { if (OtherWeight <= 0.0f) { return; } else if (OtherWeight >= 1.0f) { *this = Other; return; } Location = FMath::Lerp(Location, Other.Location, OtherWeight); const FRotator DeltaRotation = (Other.Rotation - Rotation).GetNormalized(); Rotation = Rotation + (OtherWeight * DeltaRotation); const FRotator DeltaControlRotation = (Other.ControlRotation - ControlRotation).GetNormalized(); ControlRotation = ControlRotation + (OtherWeight * DeltaControlRotation); FieldOfView = FMath::Lerp(FieldOfView, Other.FieldOfView, OtherWeight); } ////////////////////////////////////////////////////////////////////////// // ULyraCameraMode ////////////////////////////////////////////////////////////////////////// ULyraCameraMode::ULyraCameraMode() { FieldOfView = LYRA_CAMERA_DEFAULT_FOV; ViewPitchMin = LYRA_CAMERA_DEFAULT_PITCH_MIN; ViewPitchMax = LYRA_CAMERA_DEFAULT_PITCH_MAX; BlendTime = 0.5f; BlendFunction = ELyraCameraModeBlendFunction::EaseOut; BlendExponent = 4.0f; BlendAlpha = 1.0f; BlendWeight = 1.0f; } ULyraCameraComponent* ULyraCameraMode::GetLyraCameraComponent() const { return CastChecked(GetOuter()); } UWorld* ULyraCameraMode::GetWorld() const { return HasAnyFlags(RF_ClassDefaultObject) ? nullptr : GetOuter()->GetWorld(); } AActor* ULyraCameraMode::GetTargetActor() const { const ULyraCameraComponent* LyraCameraComponent = GetLyraCameraComponent(); return LyraCameraComponent->GetTargetActor(); } FVector ULyraCameraMode::GetPivotLocation() const { const AActor* TargetActor = GetTargetActor(); check(TargetActor); if (const APawn* TargetPawn = Cast(TargetActor)) { // Height adjustments for characters to account for crouching. if (const ACharacter* TargetCharacter = Cast(TargetPawn)) { const ACharacter* TargetCharacterCDO = TargetCharacter->GetClass()->GetDefaultObject(); check(TargetCharacterCDO); const UCapsuleComponent* CapsuleComp = TargetCharacter->GetCapsuleComponent(); check(CapsuleComp); const UCapsuleComponent* CapsuleCompCDO = TargetCharacterCDO->GetCapsuleComponent(); check(CapsuleCompCDO); const float DefaultHalfHeight = CapsuleCompCDO->GetUnscaledCapsuleHalfHeight(); const float ActualHalfHeight = CapsuleComp->GetUnscaledCapsuleHalfHeight(); const float HeightAdjustment = (DefaultHalfHeight - ActualHalfHeight) + TargetCharacterCDO->BaseEyeHeight; return TargetCharacter->GetActorLocation() + (FVector::UpVector * HeightAdjustment); } return TargetPawn->GetPawnViewLocation(); } return TargetActor->GetActorLocation(); } FRotator ULyraCameraMode::GetPivotRotation() const { const AActor* TargetActor = GetTargetActor(); check(TargetActor); if (const APawn* TargetPawn = Cast(TargetActor)) { return TargetPawn->GetViewRotation(); } return TargetActor->GetActorRotation(); } void ULyraCameraMode::UpdateCameraMode(float DeltaTime) { UpdateView(DeltaTime); UpdateBlending(DeltaTime); } void ULyraCameraMode::UpdateView(float DeltaTime) { FVector PivotLocation = GetPivotLocation(); FRotator PivotRotation = GetPivotRotation(); PivotRotation.Pitch = FMath::ClampAngle(PivotRotation.Pitch, ViewPitchMin, ViewPitchMax); View.Location = PivotLocation; View.Rotation = PivotRotation; View.ControlRotation = View.Rotation; View.FieldOfView = FieldOfView; } void ULyraCameraMode::SetBlendWeight(float Weight) { BlendWeight = FMath::Clamp(Weight, 0.0f, 1.0f); // Since we're setting the blend weight directly, we need to calculate the blend alpha to account for the blend function. const float InvExponent = (BlendExponent > 0.0f) ? (1.0f / BlendExponent) : 1.0f; switch (BlendFunction) { case ELyraCameraModeBlendFunction::Linear: BlendAlpha = BlendWeight; break; case ELyraCameraModeBlendFunction::EaseIn: BlendAlpha = FMath::InterpEaseIn(0.0f, 1.0f, BlendWeight, InvExponent); break; case ELyraCameraModeBlendFunction::EaseOut: BlendAlpha = FMath::InterpEaseOut(0.0f, 1.0f, BlendWeight, InvExponent); break; case ELyraCameraModeBlendFunction::EaseInOut: BlendAlpha = FMath::InterpEaseInOut(0.0f, 1.0f, BlendWeight, InvExponent); break; default: checkf(false, TEXT("SetBlendWeight: Invalid BlendFunction [%d]\n"), (uint8)BlendFunction); break; } } void ULyraCameraMode::UpdateBlending(float DeltaTime) { if (BlendTime > 0.0f) { BlendAlpha += (DeltaTime / BlendTime); BlendAlpha = FMath::Min(BlendAlpha, 1.0f); } else { BlendAlpha = 1.0f; } const float Exponent = (BlendExponent > 0.0f) ? BlendExponent : 1.0f; switch (BlendFunction) { case ELyraCameraModeBlendFunction::Linear: BlendWeight = BlendAlpha; break; case ELyraCameraModeBlendFunction::EaseIn: BlendWeight = FMath::InterpEaseIn(0.0f, 1.0f, BlendAlpha, Exponent); break; case ELyraCameraModeBlendFunction::EaseOut: BlendWeight = FMath::InterpEaseOut(0.0f, 1.0f, BlendAlpha, Exponent); break; case ELyraCameraModeBlendFunction::EaseInOut: BlendWeight = FMath::InterpEaseInOut(0.0f, 1.0f, BlendAlpha, Exponent); break; default: checkf(false, TEXT("UpdateBlending: Invalid BlendFunction [%d]\n"), (uint8)BlendFunction); break; } } void ULyraCameraMode::DrawDebug(UCanvas* Canvas) const { check(Canvas); FDisplayDebugManager& DisplayDebugManager = Canvas->DisplayDebugManager; DisplayDebugManager.SetDrawColor(FColor::White); DisplayDebugManager.DrawString(FString::Printf(TEXT(" LyraCameraMode: %s (%f)"), *GetName(), BlendWeight)); } ////////////////////////////////////////////////////////////////////////// // ULyraCameraModeStack ////////////////////////////////////////////////////////////////////////// ULyraCameraModeStack::ULyraCameraModeStack() { bIsActive = true; } void ULyraCameraModeStack::ActivateStack() { if (!bIsActive) { bIsActive = true; // Notify camera modes that they are being activated. for (ULyraCameraMode* CameraMode : CameraModeStack) { check(CameraMode); CameraMode->OnActivation(); } } } void ULyraCameraModeStack::DeactivateStack() { if (bIsActive) { bIsActive = false; // Notify camera modes that they are being deactivated. for (ULyraCameraMode* CameraMode : CameraModeStack) { check(CameraMode); CameraMode->OnDeactivation(); } } } void ULyraCameraModeStack::PushCameraMode(TSubclassOf CameraModeClass) { if (!CameraModeClass) { return; } ULyraCameraMode* CameraMode = GetCameraModeInstance(CameraModeClass); check(CameraMode); int32 StackSize = CameraModeStack.Num(); if ((StackSize > 0) && (CameraModeStack[0] == CameraMode)) { // Already top of stack. return; } // See if it's already in the stack and remove it. // Figure out how much it was contributing to the stack. int32 ExistingStackIndex = INDEX_NONE; float ExistingStackContribution = 1.0f; for (int32 StackIndex = 0; StackIndex < StackSize; ++StackIndex) { if (CameraModeStack[StackIndex] == CameraMode) { ExistingStackIndex = StackIndex; ExistingStackContribution *= CameraMode->GetBlendWeight(); break; } else { ExistingStackContribution *= (1.0f - CameraModeStack[StackIndex]->GetBlendWeight()); } } if (ExistingStackIndex != INDEX_NONE) { CameraModeStack.RemoveAt(ExistingStackIndex); StackSize--; } else { ExistingStackContribution = 0.0f; } // Decide what initial weight to start with. const bool bShouldBlend = ((CameraMode->GetBlendTime() > 0.0f) && (StackSize > 0)); const float BlendWeight = (bShouldBlend ? ExistingStackContribution : 1.0f); CameraMode->SetBlendWeight(BlendWeight); // Add new entry to top of stack. CameraModeStack.Insert(CameraMode, 0); // Make sure stack bottom is always weighted 100%. CameraModeStack.Last()->SetBlendWeight(1.0f); // Let the camera mode know if it's being added to the stack. if (ExistingStackIndex == INDEX_NONE) { CameraMode->OnActivation(); } } bool ULyraCameraModeStack::EvaluateStack(float DeltaTime, FLyraCameraModeView& OutCameraModeView) { if (!bIsActive) { return false; } UpdateStack(DeltaTime); BlendStack(OutCameraModeView); return true; } ULyraCameraMode* ULyraCameraModeStack::GetCameraModeInstance(TSubclassOf CameraModeClass) { check(CameraModeClass); // First see if we already created one. for (ULyraCameraMode* CameraMode : CameraModeInstances) { if ((CameraMode != nullptr) && (CameraMode->GetClass() == CameraModeClass)) { return CameraMode; } } // Not found, so we need to create it. ULyraCameraMode* NewCameraMode = NewObject(GetOuter(), CameraModeClass, NAME_None, RF_NoFlags); check(NewCameraMode); CameraModeInstances.Add(NewCameraMode); return NewCameraMode; } void ULyraCameraModeStack::UpdateStack(float DeltaTime) { const int32 StackSize = CameraModeStack.Num(); if (StackSize <= 0) { return; } int32 RemoveCount = 0; int32 RemoveIndex = INDEX_NONE; for (int32 StackIndex = 0; StackIndex < StackSize; ++StackIndex) { ULyraCameraMode* CameraMode = CameraModeStack[StackIndex]; check(CameraMode); CameraMode->UpdateCameraMode(DeltaTime); if (CameraMode->GetBlendWeight() >= 1.0f) { // Everything below this mode is now irrelevant and can be removed. RemoveIndex = (StackIndex + 1); RemoveCount = (StackSize - RemoveIndex); break; } } if (RemoveCount > 0) { // Let the camera modes know they being removed from the stack. for (int32 StackIndex = RemoveIndex; StackIndex < StackSize; ++StackIndex) { ULyraCameraMode* CameraMode = CameraModeStack[StackIndex]; check(CameraMode); CameraMode->OnDeactivation(); } CameraModeStack.RemoveAt(RemoveIndex, RemoveCount); } } void ULyraCameraModeStack::BlendStack(FLyraCameraModeView& OutCameraModeView) const { const int32 StackSize = CameraModeStack.Num(); if (StackSize <= 0) { return; } // Start at the bottom and blend up the stack const ULyraCameraMode* CameraMode = CameraModeStack[StackSize - 1]; check(CameraMode); OutCameraModeView = CameraMode->GetCameraModeView(); for (int32 StackIndex = (StackSize - 2); StackIndex >= 0; --StackIndex) { CameraMode = CameraModeStack[StackIndex]; check(CameraMode); OutCameraModeView.Blend(CameraMode->GetCameraModeView(), CameraMode->GetBlendWeight()); } } void ULyraCameraModeStack::DrawDebug(UCanvas* Canvas) const { check(Canvas); FDisplayDebugManager& DisplayDebugManager = Canvas->DisplayDebugManager; DisplayDebugManager.SetDrawColor(FColor::Green); DisplayDebugManager.DrawString(FString(TEXT(" --- Camera Modes (Begin) ---"))); for (const ULyraCameraMode* CameraMode : CameraModeStack) { check(CameraMode); CameraMode->DrawDebug(Canvas); } DisplayDebugManager.SetDrawColor(FColor::Green); DisplayDebugManager.DrawString(FString::Printf(TEXT(" --- Camera Modes (End) ---"))); } void ULyraCameraModeStack::GetBlendInfo(float& OutWeightOfTopLayer, FGameplayTag& OutTagOfTopLayer) const { if (CameraModeStack.Num() == 0) { OutWeightOfTopLayer = 1.0f; OutTagOfTopLayer = FGameplayTag(); return; } else { ULyraCameraMode* TopEntry = CameraModeStack.Last(); check(TopEntry); OutWeightOfTopLayer = TopEntry->GetBlendWeight(); OutTagOfTopLayer = TopEntry->GetCameraTypeTag(); } }