Skip to content

Commit

Permalink
Merge pull request #35 from xthebat/30-add-camera-vertical-lag
Browse files Browse the repository at this point in the history
Added damper for vertical camera movement
  • Loading branch information
xthebat authored Oct 31, 2023
2 parents 92896ca + 040361a commit 240c3d9
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 20 deletions.
Binary file modified Content/Blueprints/Character/BP_Cloud9Character.uasset
Binary file not shown.
36 changes: 18 additions & 18 deletions Source/Cloud9/Character/Cloud9Character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
#include "Camera/CameraComponent.h"
#include "Cloud9/Cloud9.h"
#include "Cloud9/Game/Cloud9PlayerController.h"
#include "Cloud9/Tools/Cloud9ToolsLibrary.h"
#include "Components/DecalComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/Cloud9CharacterMovement.h"
#include "Components/Cloud9Inventory.h"
#include "Components/Cloud9SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "Materials/Material.h"
#include "Engine/World.h"
#include "Kismet/KismetMathLibrary.h"

class UCloud9SpringArmComponent;
const FName ACloud9Character::SpringArmComponentName = TEXT("CameraBoom");
const FName ACloud9Character::CameraComponentName = TEXT("TopDownCamera");
const FName ACloud9Character::DecalComponentName = TEXT("CursorToWorld");
Expand All @@ -37,7 +38,7 @@ ACloud9Character::ACloud9Character(const FObjectInitializer& ObjectInitializer)
Movement->bSnapToPlaneAtStart = true;
Movement->JumpZVelocity = 320.0f;

CameraBoom = CreateDefaultSubobject<USpringArmComponent>(SpringArmComponentName);
CameraBoom = CreateDefaultSubobject<UCloud9SpringArmComponent>(SpringArmComponentName);
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->SetUsingAbsoluteRotation(true); // Don't want arm to rotate when character does
CameraBoom->bDoCollisionTest = true; // Don't want to pull camera in when it collides with level
Expand All @@ -59,37 +60,36 @@ ACloud9Character::ACloud9Character(const FObjectInitializer& ObjectInitializer)

UCloud9CharacterMovement* ACloud9Character::GetCloud9CharacterMovement() const
{
if (const auto MyCharacterMovement = GetCharacterMovement())
{
return Cast<UCloud9CharacterMovement>(MyCharacterMovement);
}

return nullptr;
const auto Movement = GetCharacterMovement();
return IsValid(Movement) ? Cast<UCloud9CharacterMovement>(Movement) : nullptr;
}

ACloud9PlayerController* ACloud9Character::GetCloud9Controller() const
{
if (const auto MyController = GetController())
return IsValid(Controller) ? Cast<ACloud9PlayerController>(Controller) : nullptr;
}

bool ACloud9Character::CanSneak() const
{
if (const auto Movement = GetCloud9CharacterMovement(); IsValid(Movement))
{
return Cast<ACloud9PlayerController>(MyController);
return !Movement->IsCrouching();
}

return nullptr;
return false;
}

bool ACloud9Character::CanSneak() const { return !GetCloud9CharacterMovement()->IsCrouching(); }

void ACloud9Character::Sneak() const
{
if (const auto Movement = GetCloud9CharacterMovement())
if (const auto Movement = GetCloud9CharacterMovement(); IsValid(Movement))
{
Movement->Sneak();
}
}

void ACloud9Character::UnSneak() const
{
if (const auto Movement = GetCloud9CharacterMovement())
if (const auto Movement = GetCloud9CharacterMovement(); IsValid(Movement))
{
Movement->UnSneak();
}
Expand Down Expand Up @@ -207,16 +207,16 @@ void ACloud9Character::OnConstruction(const FTransform& Transform)

SetCursorIsHidden(true);

if (CursorDecal != nullptr)
if (IsValid(CursorDecal))
{
UE_LOG(LogCloud9, Display, TEXT("Setup CursorDecal = %s"), *CursorDecal->GetName());
CursorToWorld->SetDecalMaterial(CursorDecal);
CursorToWorld->DecalSize = FVector(16.0f, 32.0f, 32.0f);
}

if (GetMesh() != nullptr && !CameraTargetBoneName.IsNone())
if (const auto MyMesh = GetMesh(); IsValid(MyMesh) && !CameraTargetBoneName.IsNone())
{
const auto HeadBoneLocation = GetMesh()->GetBoneLocation(CameraTargetBoneName, EBoneSpaces::WorldSpace);
const auto HeadBoneLocation = MyMesh->GetBoneLocation(CameraTargetBoneName, EBoneSpaces::WorldSpace);
UE_LOG(LogCloud9, Display, TEXT("Setup CameraBoom = %s"), *HeadBoneLocation.ToString());
CameraBoom->SetWorldLocation(HeadBoneLocation);
}
Expand Down
2 changes: 0 additions & 2 deletions Source/Cloud9/Character/Cloud9Character.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ class CLOUD9_API UCloud9CharacterMovement
UPROPERTY(Category="Character Movement (Rotation Settings)", EditDefaultsOnly)
float RotationLag;

/** Target rotator of character*/
FRotator TargetRotator;
};
147 changes: 147 additions & 0 deletions Source/Cloud9/Character/Components/Cloud9SpringArmComponent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "Cloud9SpringArmComponent.h"

#include "Cloud9/Tools/Cloud9ToolsLibrary.h"


UCloud9SpringArmComponent::UCloud9SpringArmComponent()
{
CameraLagVector = FVector::OneVector;
}

void UCloud9SpringArmComponent::UpdateDesiredArmLocation(
bool bDoTrace,
bool bDoLocationLag,
bool bDoRotationLag,
float DeltaTime)
{
FRotator DesiredRot = GetTargetRotation();

// 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 > 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 ArmOrigin = GetComponentLocation() + TargetOffset;
// We lag the target, not the actual camera position, so rotating the camera around does not have lag
FVector DesiredLoc = ArmOrigin;
if (bDoLocationLag)
{
const auto LocationLag = CameraLagSpeed * CameraLagVector;

if (bUseCameraLagSubstepping
&& DeltaTime > CameraLagMaxTimeStep
&& LocationLag != FVector::ZeroVector)
{
const FVector ArmMovementStep = (DesiredLoc - PreviousDesiredLoc) * (1.f / DeltaTime);
auto LerpTarget = PreviousDesiredLoc;

float RemainingTime = DeltaTime;
while (RemainingTime > KINDA_SMALL_NUMBER)
{
const float LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime);
LerpTarget += ArmMovementStep * LerpAmount;
RemainingTime -= LerpAmount;

DesiredLoc = UCloud9ToolsLibrary::VInterpTo(PreviousDesiredLoc, LerpTarget, LerpAmount, LocationLag);
PreviousDesiredLoc = DesiredLoc;
}
}
else
{
DesiredLoc = UCloud9ToolsLibrary::VInterpTo(PreviousDesiredLoc, DesiredLoc, DeltaTime, LocationLag);
}

if (const auto FromOrigin = DesiredLoc - ArmOrigin;
CameraLagMaxDistance > 0.f && FromOrigin.SizeSquared() > FMath::Square(CameraLagMaxDistance))
{
DesiredLoc = ArmOrigin + FromOrigin.GetClampedToMaxSize(CameraLagMaxDistance);
}
}

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;
GetWorld()->SweepSingleByChannel(
Result,
ArmOrigin,
DesiredLoc,
FQuat::Identity,
ProbeChannel,
FCollisionShape::MakeSphere(ProbeSize),
QueryParams);

UnfixedCameraPosition = DesiredLoc;

ResultLoc = BlendLocations(DesiredLoc, Result.Location, Result.bBlockingHit, DeltaTime);

if (ResultLoc == DesiredLoc)
{
bIsCameraFixed = false;
}
}
else
{
ResultLoc = DesiredLoc;
bIsCameraFixed = false;
UnfixedCameraPosition = ResultLoc;
}

// 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();
}
32 changes: 32 additions & 0 deletions Source/Cloud9/Character/Components/Cloud9SpringArmComponent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/SpringArmComponent.h"
#include "Cloud9SpringArmComponent.generated.h"


UCLASS(ClassGroup=(Camera), meta=(BlueprintSpawnableComponent))
class CLOUD9_API UCloud9SpringArmComponent : public USpringArmComponent
{
GENERATED_BODY()

public:
// TODO: CameraLagRotator

/**
* If bEnableCameraLag is true, controls how quickly camera reaches target position by each axis.
* Values of this vector multiplies with @CameraLagSpeed.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Lag, meta=(editcondition="bEnableCameraLag"))
FVector CameraLagVector;

public:
UCloud9SpringArmComponent();

virtual void UpdateDesiredArmLocation(
bool bDoTrace,
bool bDoLocationLag,
bool bDoRotationLag,
float DeltaTime
) override;
};
29 changes: 29 additions & 0 deletions Source/Cloud9/Tools/Cloud9ToolsLibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,32 @@ FRotator UCloud9ToolsLibrary::RadiansToDegrees(const FRotator Rotator)
FMath::RadiansToDegrees(Rotator.Roll)
};
}

FVector UCloud9ToolsLibrary::VInterpTo(
const FVector Current,
const FVector Target,
float DeltaTime,
const FVector InterpSpeed)
{
const auto ClampLerp = [](auto Current, auto Dist, auto Alpha, auto Target)
{
return Alpha <= 0.0f ? Target : Current + Dist * FMath::Clamp(Alpha, 0.0f, 1.0f);
};

// Distance to reach
const auto Dist = Target - Current;

// If distance is too small, just set the desired location
if (Dist.SizeSquared() < KINDA_SMALL_NUMBER)
{
return Target;
}

const auto Alpha = DeltaTime * InterpSpeed;

return {
ClampLerp(Current.X, Dist.X, Alpha.X, Target.X),
ClampLerp(Current.Y, Dist.Y, Alpha.Y, Target.Y),
ClampLerp(Current.Z, Dist.Z, Alpha.Z, Target.Z),
};
}
6 changes: 6 additions & 0 deletions Source/Cloud9/Tools/Cloud9ToolsLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ class CLOUD9_API UCloud9ToolsLibrary : public UBlueprintFunctionLibrary
static void GetWidthHeightDepth(const FBox& Box, float& Width, float& Height, float& Depth);

static FRotator RadiansToDegrees(const FRotator Rotator);

static FVector VInterpTo(
const FVector Current,
const FVector Target,
float DeltaTime,
const FVector InterpSpeed);
};

0 comments on commit 240c3d9

Please sign in to comment.