450 lines
13 KiB
C++
450 lines
13 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 "Components/OLSLocomotionComponent.h"
|
|
|
|
#include "Data/OLSEnumLibrary.h"
|
|
#include "Interfaces/OLSAnimationInterface.h"
|
|
#include "Interfaces/OLSMoveableInterface.h"
|
|
#include "Kismet/KismetMathLibrary.h"
|
|
|
|
|
|
UOLSLocomotionComponent::UOLSLocomotionComponent(const FObjectInitializer& objectInitializer)
|
|
{
|
|
PrimaryComponentTick.bCanEverTick = true;
|
|
}
|
|
|
|
void UOLSLocomotionComponent::PostInitProperties()
|
|
{
|
|
Super::PostInitProperties();
|
|
|
|
Gaits.Add(EOLSGait::EWalk, 0.f);
|
|
Gaits.Add(EOLSGait::EJog, 0.f);
|
|
Gaits.Add(EOLSGait::ESprint, 0.f);
|
|
}
|
|
|
|
// Called when the game starts
|
|
void UOLSLocomotionComponent::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
// ...
|
|
|
|
if (const TObjectPtr<APawn> owningPawn = Cast<APawn>(GetOwner()))
|
|
{
|
|
OwningPawn = owningPawn;
|
|
MoveableInterface.SetObject(owningPawn);
|
|
MoveableInterface.SetInterface(Cast<IOLSMoveableInterface>(owningPawn));
|
|
|
|
if (const IOLSAnimationInterface* animInterface = Cast<IOLSAnimationInterface>(owningPawn))
|
|
{
|
|
OwningAnimInstance = animInterface->GetAnimInstance();
|
|
}
|
|
}
|
|
|
|
MaxSpeed = Gaits.FindChecked(EOLSGait::EJog);
|
|
|
|
SetDesiredGait(DesiredGait, true);
|
|
SetRotationMode(DesiredRotationMode, true);
|
|
}
|
|
|
|
// Called every frame
|
|
void UOLSLocomotionComponent::TickComponent(float deltaTime, ELevelTick tickType,
|
|
FActorComponentTickFunction* thisTickFunction)
|
|
{
|
|
Super::TickComponent(deltaTime, tickType, thisTickFunction);
|
|
|
|
// ...
|
|
|
|
UpdateEssentialValues(deltaTime);
|
|
UpdateGait(deltaTime);
|
|
UpdateGroundedRotation(deltaTime);
|
|
}
|
|
|
|
EOLSGait UOLSLocomotionComponent::GetAllowedGait() const
|
|
{
|
|
if (Stance == EOLSStance::EStanding)
|
|
{
|
|
if (RotationMode != EOLSRotationMode::EAiming)
|
|
{
|
|
if (DesiredGait == EOLSGait::ESprint)
|
|
{
|
|
return CanSprint() ? EOLSGait::ESprint : EOLSGait::EJog;
|
|
}
|
|
return DesiredGait;
|
|
}
|
|
}
|
|
|
|
if (DesiredGait == EOLSGait::ESprint)
|
|
{
|
|
return EOLSGait::EJog;
|
|
}
|
|
|
|
return DesiredGait;
|
|
}
|
|
|
|
EOLSGait UOLSLocomotionComponent::GetActualGait(const EOLSGait& allowedLocomotionState) const
|
|
{
|
|
// This should never happen.
|
|
if (Gaits.IsEmpty())
|
|
{
|
|
return DesiredGait;
|
|
}
|
|
|
|
if (allowedLocomotionState == EOLSGait::ESprint)
|
|
{
|
|
return EOLSGait::ESprint;
|
|
}
|
|
else if (allowedLocomotionState == EOLSGait::EJog && CanJog())
|
|
{
|
|
return EOLSGait::EJog;
|
|
}
|
|
|
|
return EOLSGait::EWalk;
|
|
}
|
|
|
|
void UOLSLocomotionComponent::UpdateEssentialValues(const float deltaTime)
|
|
{
|
|
if (!OwningPawn || !MoveableInterface)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Interp AimingRotation to current control rotation for smooth character rotation movement. Decrease InterpSpeed
|
|
// for slower but smoother movement.
|
|
AimingRotation = FMath::RInterpTo(AimingRotation, OwningPawn->GetControlRotation(), deltaTime, 30.f);
|
|
|
|
// These values represent how the capsule is moving as well as how it wants to move, and therefore are essential
|
|
// for any data driven animation system. They are also used throughout the system for various functions,
|
|
// so I found it is easiest to manage them all in one place.
|
|
|
|
const FVector& currentVel = GetVelocity();
|
|
|
|
// Set the amount of Acceleration.
|
|
Acceleration = MoveableInterface->GetCurrentAcceleration();
|
|
|
|
// Determine if the character is moving by getting it's speed. The Speed equals the length of the horizontal (x y)
|
|
// velocity, so it does not take vertical movement into account. If the character is moving, update the last
|
|
// velocity rotation. This value is saved because it might be useful to know the last orientation of movement
|
|
// even after the character has stopped.
|
|
Speed = currentVel.Size2D();
|
|
bIsMoving = Speed > 1.0f;
|
|
if (bIsMoving)
|
|
{
|
|
LastVelocityRotation = currentVel.ToOrientationRotator();
|
|
}
|
|
|
|
// Determine if the character has movement input by getting its movement input amount.
|
|
// The Movement Input Amount is equal to the current acceleration divided by the max acceleration so that
|
|
// it has a range of 0-1, 1 being the maximum possible amount of input, and 0 being none.
|
|
// If the character has movement input, update the Last Movement Input Rotation.
|
|
MovementInputAmount = FMath::GetMappedRangeValueClamped(FVector2D(0.f, MoveableInterface->GetMaxAcceleration()),
|
|
FVector2D(0.f, 1.f),
|
|
MoveableInterface->GetCurrentAcceleration().Size());
|
|
bHasMovementInput = !FMath::IsNearlyZero(MovementInputAmount);
|
|
if (bHasMovementInput)
|
|
{
|
|
LastMovementInputRotation = Acceleration.ToOrientationRotator();
|
|
}
|
|
}
|
|
|
|
void UOLSLocomotionComponent::UpdateGait(const float deltaTime)
|
|
{
|
|
const EOLSGait allowedLocomotionState = GetAllowedGait();
|
|
|
|
const EOLSGait actualLocomotionState = GetActualGait(allowedLocomotionState);
|
|
|
|
SetGait(actualLocomotionState);
|
|
}
|
|
|
|
void UOLSLocomotionComponent::UpdateGroundedRotation(float deltaTime)
|
|
{
|
|
const bool canUpdateMovingRot = (bHasMovementInput || bIsMoving);
|
|
if (canUpdateMovingRot)
|
|
{
|
|
if (RotationMode == EOLSRotationMode::EVelocityDirection)
|
|
{
|
|
FRotator targetRotation = LastVelocityRotation;
|
|
ProcessTurnYawCurve(targetRotation);
|
|
const float constRotationRate = CalculateConstRotationRate(500.f, 2000.f);
|
|
const float smoothRotationRate = CalculateSmoothRotationRate(5.f, 20.f);
|
|
|
|
SmoothOwningPawnRotation({0.0f, targetRotation.Yaw, 0.0f}, constRotationRate, smoothRotationRate,
|
|
deltaTime);
|
|
}
|
|
else if (RotationMode == EOLSRotationMode::ELookingDirection)
|
|
{
|
|
float yawValue = 0.0f;
|
|
if (Gait == EOLSGait::ESprint)
|
|
{
|
|
yawValue = LastVelocityRotation.Yaw;
|
|
}
|
|
else
|
|
{
|
|
yawValue = AimingRotation.Yaw;
|
|
}
|
|
|
|
const float constRotationRate = CalculateConstRotationRate(250.f, 500.f);
|
|
const float smoothRotationRate = CalculateSmoothRotationRate(5.f, 20.f);
|
|
SmoothOwningPawnRotation({0.f, yawValue, 0.f}, constRotationRate, smoothRotationRate, deltaTime);
|
|
}
|
|
else if (RotationMode == EOLSRotationMode::EAiming)
|
|
{
|
|
SmoothOwningPawnRotation({0.f, AimingRotation.Yaw, 0.f}, 1000.f, 20.f, deltaTime);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RotationMode == EOLSRotationMode::EVelocityDirection)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FRotator curTargetRot = OwningPawn->GetActorRotation();
|
|
if (RotationMode == EOLSRotationMode::EAiming)
|
|
{
|
|
LimitRotation(-100.f, 100, 20.f, deltaTime);
|
|
}
|
|
|
|
ProcessTurnYawCurve(curTargetRot);
|
|
SetOwningPawnRotation(curTargetRot);
|
|
}
|
|
}
|
|
|
|
FVector UOLSLocomotionComponent::GetVelocity() const
|
|
{
|
|
return (OwningPawn ? OwningPawn->GetVelocity() : FVector::ZeroVector);
|
|
}
|
|
|
|
float UOLSLocomotionComponent::GetAnimCurve(const FName& curveName) const
|
|
{
|
|
return (curveName.IsValid() && OwningAnimInstance ? OwningAnimInstance->GetCurveValue(curveName) : 0.f);
|
|
}
|
|
|
|
const EOLSStance& UOLSLocomotionComponent::GetStance() const
|
|
{
|
|
return Stance;
|
|
}
|
|
|
|
void UOLSLocomotionComponent::SetStance(EOLSStance newStance, bool shouldForce)
|
|
{
|
|
if (shouldForce || Stance != newStance)
|
|
{
|
|
const EOLSStance prev = Stance;
|
|
Stance = newStance;
|
|
OnStanceChanged(prev);
|
|
}
|
|
}
|
|
|
|
const EOLSGait& UOLSLocomotionComponent::GetGait() const
|
|
{
|
|
return Gait;
|
|
}
|
|
|
|
const EOLSGait& UOLSLocomotionComponent::GetDesiredGait() const
|
|
{
|
|
return DesiredGait;
|
|
}
|
|
|
|
void UOLSLocomotionComponent::SetGait(EOLSGait newGait, bool shouldForce)
|
|
{
|
|
if (shouldForce || Gait != newGait)
|
|
{
|
|
const EOLSGait prev = Gait;
|
|
Gait = newGait;
|
|
OnGaitChanged(prev);
|
|
|
|
if (MoveableInterface)
|
|
{
|
|
MoveableInterface->SetMaxWalkSpeed(Gaits.FindChecked(newGait));
|
|
}
|
|
}
|
|
}
|
|
|
|
const EOLSRotationMode& UOLSLocomotionComponent::GetRotationMode() const
|
|
{
|
|
return RotationMode;
|
|
}
|
|
|
|
void UOLSLocomotionComponent::SetRotationMode(EOLSRotationMode newRotationMode, bool shouldForce)
|
|
{
|
|
if (shouldForce || RotationMode != newRotationMode)
|
|
{
|
|
const EOLSRotationMode prevRotationMode = RotationMode;
|
|
RotationMode = newRotationMode;
|
|
OnRotationModeChanged(prevRotationMode);
|
|
}
|
|
}
|
|
|
|
void UOLSLocomotionComponent::SetDesiredGait(const EOLSGait newGait, const bool shouldForce /* = false */)
|
|
{
|
|
if (shouldForce || DesiredGait != newGait)
|
|
{
|
|
const EOLSGait& prevDesiredGait = DesiredGait;
|
|
DesiredGait = newGait;
|
|
OnDesiredGaitChanged(prevDesiredGait);
|
|
}
|
|
}
|
|
|
|
void UOLSLocomotionComponent::SetDesiredStance(const EOLSStance newStance)
|
|
{
|
|
DesiredStance = newStance;
|
|
}
|
|
|
|
void UOLSLocomotionComponent::SetDesiredRotationMode(const EOLSRotationMode newRotationMode)
|
|
{
|
|
DesiredRotationMode = newRotationMode;
|
|
}
|
|
|
|
void UOLSLocomotionComponent::OnStanceChanged(EOLSStance previousStance)
|
|
{
|
|
OnStanceChangedNativeDelegate.Broadcast(previousStance);
|
|
}
|
|
|
|
void UOLSLocomotionComponent::OnDesiredGaitChanged(EOLSGait previousDesiredGait)
|
|
{
|
|
OnDesiredGaitChangedNativeDelegate.Broadcast(previousDesiredGait);
|
|
}
|
|
|
|
void UOLSLocomotionComponent::OnGaitChanged(EOLSGait previousGait)
|
|
{
|
|
OnGaitChangedNativeDelegate.Broadcast(previousGait);
|
|
}
|
|
|
|
void UOLSLocomotionComponent::OnRotationModeChanged(EOLSRotationMode prevRotationMode)
|
|
{
|
|
OnRotationModeChangedNativeDelegate.Broadcast(prevRotationMode);
|
|
}
|
|
|
|
bool UOLSLocomotionComponent::CanSprint() const
|
|
{
|
|
if (RotationMode == EOLSRotationMode::EAiming || !MoveableInterface)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const bool hasValidInput = MovementInputAmount > 0.9f;
|
|
if (RotationMode == EOLSRotationMode::EVelocityDirection)
|
|
{
|
|
return hasValidInput;
|
|
}
|
|
|
|
if (RotationMode == EOLSRotationMode::ELookingDirection)
|
|
{
|
|
const TObjectPtr<APawn> owningPawn = Cast<APawn>(GetOwner());
|
|
if (owningPawn)
|
|
{
|
|
const FRotator velocityRot = owningPawn->GetVelocity().ToOrientationRotator();
|
|
FRotator delta = velocityRot - owningPawn->GetControlRotation();
|
|
delta.Normalize();
|
|
return (hasValidInput && FMath::Abs(delta.Yaw) < 50.f);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UOLSLocomotionComponent::CanJog() const
|
|
{
|
|
if (!MoveableInterface)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const float inputAmount = FMath::GetMappedRangeValueClamped(FVector2D(0.f, MoveableInterface->GetMaxAcceleration()),
|
|
FVector2D(0.f, 1.f),
|
|
MoveableInterface->GetCurrentAcceleration().Size());
|
|
|
|
return (inputAmount > .8f);
|
|
}
|
|
|
|
float UOLSLocomotionComponent::CalculateConstRotationRate(const float minRotationRate, const float maxRotationRate) const
|
|
{
|
|
return UKismetMathLibrary::MapRangeClamped(Speed, 0.f, MaxSpeed, minRotationRate, maxRotationRate);
|
|
}
|
|
|
|
float UOLSLocomotionComponent::CalculateSmoothRotationRate(const float minRotationRate, const float maxRotationRate) const
|
|
{
|
|
return UKismetMathLibrary::MapRangeClamped(Speed, 0.f, MaxSpeed, minRotationRate, maxRotationRate);
|
|
}
|
|
|
|
void UOLSLocomotionComponent::LimitRotation(const float aimYawMin, const float aimYawMax,
|
|
const float interpSpeed, const float deltaTime)
|
|
{
|
|
FRotator delta = AimingRotation - GetOwner()->GetActorRotation();
|
|
delta.Normalize();
|
|
const float deltaYaw = delta.Yaw;
|
|
if (deltaYaw <= -aimYawMin || deltaYaw >= aimYawMax)
|
|
{
|
|
const float controlRotYaw = AimingRotation.Yaw;
|
|
const float targetYaw = controlRotYaw + (deltaYaw > 0.f ? aimYawMin : aimYawMax);
|
|
SmoothOwningPawnRotation({0.f, targetYaw, 0.f}, 0.f, interpSpeed, deltaTime);
|
|
}
|
|
}
|
|
|
|
void UOLSLocomotionComponent::ProcessTurnYawCurve(FRotator& outRotation)
|
|
{
|
|
if (OwningAnimInstance)
|
|
{
|
|
float previousTurnYawCurveValue = TurnYawCurveValue;
|
|
const float turnYawWeightCurveValue = GetAnimCurve(TurnYawWeightCurveName);
|
|
if (FMath::IsNearlyZero(turnYawWeightCurveValue, 0.0001f))
|
|
{
|
|
TurnYawCurveValue = 0.f;
|
|
previousTurnYawCurveValue = 0.f;
|
|
return;
|
|
}
|
|
|
|
const float remainingTurnYawCurveValue = GetAnimCurve(RemainingTurnYawCurveName);
|
|
TurnYawCurveValue = UKismetMathLibrary::SafeDivide(remainingTurnYawCurveValue, turnYawWeightCurveValue);
|
|
|
|
// Avoid applying the curve delta when the curve first becomes relevant. E.g.
|
|
// When a turn animation starts, the previous curve value will be 0 and the current value will be 90,
|
|
// but no actual rotation has happened yet.
|
|
if (previousTurnYawCurveValue != 0.f)
|
|
{
|
|
// Reduce the target yaw offset by the amount of rotation from the turn animation.
|
|
outRotation.Yaw -= (TurnYawCurveValue - previousTurnYawCurveValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UOLSLocomotionComponent::SetOwningPawnRotation(const FRotator& target)
|
|
{
|
|
if (OwningPawn)
|
|
{
|
|
OwningPawn->SetActorRotation(target);
|
|
TargetRotation = OwningPawn->GetActorRotation();
|
|
}
|
|
}
|
|
|
|
void UOLSLocomotionComponent::SmoothOwningPawnRotation(const FRotator& target, const float targetInterpSpeed,
|
|
float actorInterpSpeed, float deltaTime)
|
|
{
|
|
// Interpolate the Target Rotation for extra smooth rotation behavior
|
|
TargetRotation =
|
|
FMath::RInterpConstantTo(TargetRotation, target, deltaTime, targetInterpSpeed);
|
|
SetOwningPawnRotation(
|
|
FMath::RInterpTo(GetOwner()->GetActorRotation(), TargetRotation, deltaTime, actorInterpSpeed));
|
|
}
|
|
|
|
FOLSStanceNativeDelegate& UOLSLocomotionComponent::GetOnStanceChangedNativeDelegate()
|
|
{
|
|
return OnStanceChangedNativeDelegate;
|
|
}
|
|
|
|
FOLSGaitNativeDelegate& UOLSLocomotionComponent::GetOnDesiredGaitChangedNativeDelegate()
|
|
{
|
|
return OnDesiredGaitChangedNativeDelegate;
|
|
}
|
|
|
|
FOLSGaitNativeDelegate& UOLSLocomotionComponent::GetOnGaitChangedNativeDelegate()
|
|
{
|
|
return OnGaitChangedNativeDelegate;
|
|
}
|
|
|
|
FOLSRotationModeStateNativeDelegate& UOLSLocomotionComponent::GetOnRotationModeChangedNativeDelegate()
|
|
{
|
|
return OnRotationModeChangedNativeDelegate;
|
|
}
|