// © 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 owningPawn = Cast(GetOwner())) { OwningPawn = owningPawn; MoveableInterface.SetObject(owningPawn); MoveableInterface.SetInterface(Cast(owningPawn)); if (const IOLSAnimationInterface* animInterface = Cast(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 owningPawn = Cast(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; }