OLS/Source/OLSAnimation/Private/Components/OLSLocomotionComponent.cpp

450 lines
13 KiB
C++
Raw Normal View History

2024-09-22 21:11:19 +00:00
// © 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);
2024-10-23 22:41:23 +00:00
// UpdateGroundedRotation(deltaTime);
2024-09-22 21:11:19 +00:00
}
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;
}