496 lines
12 KiB
C++
496 lines
12 KiB
C++
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
|
|
|
|
|
#include "Camera/OLSCameraMode.h"
|
|
|
|
#include "Camera/OLSPlayerCameraManager.h"
|
|
#include "Camera/Components/OLSCameraComponent.h"
|
|
#include "Components/CapsuleComponent.h"
|
|
#include "Engine/Canvas.h"
|
|
#include "GameFramework/Character.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSCameraMode)
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FLyraCameraModeView
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
FOLSCameraModeView::FOLSCameraModeView()
|
|
: Location(ForceInit)
|
|
, Rotation(ForceInit)
|
|
, ControlRotation(ForceInit)
|
|
, FieldOfView(OLS_CAMERA_DEFAULT_FOV)
|
|
{
|
|
}
|
|
|
|
void FOLSCameraModeView::Blend(const FOLSCameraModeView& 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);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// UOLSCameraMode
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
UOLSCameraMode::UOLSCameraMode()
|
|
{
|
|
FieldOfView = OLS_CAMERA_DEFAULT_FOV;
|
|
ViewPitchMin = OLS_CAMERA_DEFAULT_PITCH_MIN;
|
|
ViewPitchMax = OLS_CAMERA_DEFAULT_PITCH_MAX;
|
|
|
|
BlendTime = 0.5f;
|
|
BlendFunction = EOLSCameraModeBlendFunction::EaseOut;
|
|
BlendExponent = 4.0f;
|
|
BlendAlpha = 1.0f;
|
|
BlendWeight = 1.0f;
|
|
}
|
|
|
|
UOLSCameraComponent* UOLSCameraMode::GetOLSCameraComponent() const
|
|
{
|
|
return CastChecked<UOLSCameraComponent>(GetOuter());
|
|
}
|
|
|
|
UWorld* UOLSCameraMode::GetWorld() const
|
|
{
|
|
return HasAnyFlags(RF_ClassDefaultObject) ? nullptr : GetOuter()->GetWorld();
|
|
}
|
|
|
|
AActor* UOLSCameraMode::GetTargetActor() const
|
|
{
|
|
const UOLSCameraComponent* cameraComponent = GetOLSCameraComponent();
|
|
|
|
return cameraComponent->GetTargetActor();
|
|
}
|
|
|
|
const FOLSCameraModeView& UOLSCameraMode::GetCameraModeView() const
|
|
{
|
|
return View;
|
|
}
|
|
|
|
void UOLSCameraMode::OnActivation()
|
|
{
|
|
}
|
|
|
|
void UOLSCameraMode::OnDeactivation()
|
|
{
|
|
}
|
|
|
|
void UOLSCameraMode::UpdateCameraMode(float deltaTime)
|
|
{
|
|
UpdateView(deltaTime);
|
|
UpdateBlending(deltaTime);
|
|
}
|
|
|
|
float UOLSCameraMode::GetBlendTime() const
|
|
{
|
|
return BlendTime;
|
|
}
|
|
|
|
float UOLSCameraMode::GetBlendWeight() const
|
|
{
|
|
return BlendWeight;
|
|
}
|
|
|
|
void UOLSCameraMode::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 EOLSCameraModeBlendFunction::Linear:
|
|
BlendAlpha = BlendWeight;
|
|
break;
|
|
|
|
case EOLSCameraModeBlendFunction::EaseIn:
|
|
BlendAlpha = FMath::InterpEaseIn(0.0f, 1.0f, BlendWeight, InvExponent);
|
|
break;
|
|
|
|
case EOLSCameraModeBlendFunction::EaseOut:
|
|
BlendAlpha = FMath::InterpEaseOut(0.0f, 1.0f, BlendWeight, InvExponent);
|
|
break;
|
|
|
|
case EOLSCameraModeBlendFunction::EaseInOut:
|
|
BlendAlpha = FMath::InterpEaseInOut(0.0f, 1.0f, BlendWeight, InvExponent);
|
|
break;
|
|
|
|
default:
|
|
checkf(false, TEXT("SetBlendWeight: Invalid BlendFunction [%d]\n"), (uint8)BlendFunction);
|
|
break;
|
|
}
|
|
}
|
|
|
|
FGameplayTag UOLSCameraMode::GetCameraTypeTag() const
|
|
{
|
|
return CameraTypeTag;
|
|
}
|
|
|
|
void UOLSCameraMode::DrawDebug(UCanvas* canvas) const
|
|
{
|
|
check(canvas);
|
|
|
|
FDisplayDebugManager& displayDebugManager = canvas->DisplayDebugManager;
|
|
|
|
displayDebugManager.SetDrawColor(FColor::White);
|
|
displayDebugManager.DrawString(FString::Printf(TEXT(" OLSCameraMode: %s (%f)"), *GetName(), BlendWeight));
|
|
}
|
|
|
|
FVector UOLSCameraMode::GetPivotLocation() const
|
|
{
|
|
const AActor* targetActor = GetTargetActor();
|
|
check(targetActor);
|
|
|
|
if (const APawn* targetPawn = Cast<APawn>(targetActor))
|
|
{
|
|
// Height adjustments for characters to account for crouching.
|
|
if (const ACharacter* targetCharacter = Cast<ACharacter>(targetPawn))
|
|
{
|
|
const ACharacter* targetCharacterCDO = targetCharacter->GetClass()->GetDefaultObject<ACharacter>();
|
|
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 UOLSCameraMode::GetPivotRotation() const
|
|
{
|
|
const AActor* targetActor = GetTargetActor();
|
|
check(targetActor);
|
|
|
|
if (const APawn* targetPawn = Cast<APawn>(targetActor))
|
|
{
|
|
return targetPawn->GetViewRotation();
|
|
}
|
|
|
|
return targetActor->GetActorRotation();
|
|
}
|
|
|
|
void UOLSCameraMode::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 UOLSCameraMode::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 EOLSCameraModeBlendFunction::Linear:
|
|
BlendWeight = BlendAlpha;
|
|
break;
|
|
|
|
case EOLSCameraModeBlendFunction::EaseIn:
|
|
BlendWeight = FMath::InterpEaseIn(0.0f, 1.0f, BlendAlpha, exponent);
|
|
break;
|
|
|
|
case EOLSCameraModeBlendFunction::EaseOut:
|
|
BlendWeight = FMath::InterpEaseOut(0.0f, 1.0f, BlendAlpha, exponent);
|
|
break;
|
|
|
|
case EOLSCameraModeBlendFunction::EaseInOut:
|
|
BlendWeight = FMath::InterpEaseInOut(0.0f, 1.0f, BlendAlpha, exponent);
|
|
break;
|
|
|
|
default:
|
|
checkf(false, TEXT("UpdateBlending: Invalid BlendFunction [%d]\n"), (uint8)BlendFunction);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// UOLSCameraModeStack
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
UOLSCameraModeStack::UOLSCameraModeStack()
|
|
{
|
|
bIsStackActive = true;
|
|
}
|
|
|
|
void UOLSCameraModeStack::ActivateStack()
|
|
{
|
|
if (!IsStackActivate())
|
|
{
|
|
bIsStackActive = true;
|
|
|
|
// Notify camera modes that they are being activated.
|
|
for (UOLSCameraMode* cameraMode : CameraModeStack)
|
|
{
|
|
check(cameraMode);
|
|
cameraMode->OnActivation();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UOLSCameraModeStack::DeactivateStack()
|
|
{
|
|
if (IsStackActivate())
|
|
{
|
|
bIsStackActive = false;
|
|
|
|
// Notify camera modes that they are being deactivated.
|
|
for (UOLSCameraMode* cameraMode : CameraModeStack)
|
|
{
|
|
check(cameraMode);
|
|
cameraMode->OnDeactivation();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UOLSCameraModeStack::IsStackActivate() const
|
|
{
|
|
return bIsStackActive;
|
|
}
|
|
|
|
void UOLSCameraModeStack::PushCameraMode(TSubclassOf<UOLSCameraMode> cameraModeClass)
|
|
{
|
|
if (!cameraModeClass)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UOLSCameraMode* 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 shouldBlend = ((cameraMode->GetBlendTime() > 0.0f) && (stackSize > 0));
|
|
const float blendWeight = (shouldBlend ? 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 UOLSCameraModeStack::EvaluateStack(float deltaTime, FOLSCameraModeView& outCameraModeView)
|
|
{
|
|
if (!IsStackActivate())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UpdateStack(deltaTime);
|
|
BlendStack(outCameraModeView);
|
|
return true;
|
|
}
|
|
|
|
void UOLSCameraModeStack::DrawDebug(UCanvas* canvas) const
|
|
{
|
|
check(canvas);
|
|
|
|
FDisplayDebugManager& displayDebugManager = canvas->DisplayDebugManager;
|
|
|
|
displayDebugManager.SetDrawColor(FColor::Green);
|
|
displayDebugManager.DrawString(FString(TEXT(" --- Camera Modes (Begin) ---")));
|
|
|
|
for (const UOLSCameraMode* cameraMode : CameraModeStack)
|
|
{
|
|
check(cameraMode);
|
|
cameraMode->DrawDebug(canvas);
|
|
}
|
|
|
|
displayDebugManager.SetDrawColor(FColor::Green);
|
|
displayDebugManager.DrawString(FString::Printf(TEXT(" --- Camera Modes (End) ---")));
|
|
}
|
|
|
|
void UOLSCameraModeStack::GetBlendInfo(float& outWeightOfTopLayer, FGameplayTag& outTagOfTopLayer) const
|
|
{
|
|
if (CameraModeStack.Num() == 0)
|
|
{
|
|
outWeightOfTopLayer = 1.0f;
|
|
outTagOfTopLayer = FGameplayTag();
|
|
return;
|
|
}
|
|
|
|
UOLSCameraMode* topEntry = CameraModeStack.Last();
|
|
check(topEntry);
|
|
outWeightOfTopLayer = topEntry->GetBlendWeight();
|
|
outTagOfTopLayer = topEntry->GetCameraTypeTag();
|
|
}
|
|
|
|
UOLSCameraMode* UOLSCameraModeStack::GetCameraModeInstance(TSubclassOf<UOLSCameraMode> cameraModeClass)
|
|
{
|
|
check(cameraModeClass);
|
|
|
|
// First see if we already created one.
|
|
for (UOLSCameraMode* cameraMode : CameraModeInstances)
|
|
{
|
|
if ((cameraMode != nullptr) && (cameraMode->GetClass() == cameraModeClass))
|
|
{
|
|
return cameraMode;
|
|
}
|
|
}
|
|
|
|
// Not found, so we need to create it.
|
|
UOLSCameraMode* newCameraMode = NewObject<UOLSCameraMode>(GetOuter(), cameraModeClass, NAME_None, RF_NoFlags);
|
|
check(newCameraMode);
|
|
|
|
CameraModeInstances.Add(newCameraMode);
|
|
|
|
return newCameraMode;
|
|
}
|
|
|
|
void UOLSCameraModeStack::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)
|
|
{
|
|
UOLSCameraMode* 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)
|
|
{
|
|
UOLSCameraMode* cameraMode = CameraModeStack[stackIndex];
|
|
check(cameraMode);
|
|
|
|
cameraMode->OnDeactivation();
|
|
}
|
|
|
|
CameraModeStack.RemoveAt(removeIndex, removeCount);
|
|
}
|
|
}
|
|
|
|
void UOLSCameraModeStack::BlendStack(FOLSCameraModeView& outCameraModeView) const
|
|
{
|
|
const int32 stackSize = CameraModeStack.Num();
|
|
if (stackSize <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Start at the bottom and blend up the stack
|
|
const UOLSCameraMode* 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());
|
|
}
|
|
}
|