// © 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 "AnimInstances/OLSBaseLayerAnimInstance.h" #include "KismetAnimationLibrary.h" #include "Components/OLSLocomotionComponent.h" #include "Libraries/OLSLocomotionBPLibrary.h" #include "GameFramework/PawnMovementComponent.h" #include "Interfaces/OLSMoveableInterface.h" #include "Kismet/KismetSystemLibrary.h" #if WITH_EDITOR #include "Misc/DataValidation.h" #endif #if ENABLE_LOCOMOTION_DEBUG static TAutoConsoleVariable CVarAnimInstanceLocomotionDebug(TEXT("a.AnimInstance.Locomotion.Debug"), false, TEXT("Turn on visualization debugging for Locomotion")); #endif void UOLSBaseLayerAnimInstance::NativeInitializeAnimation() { // Super::NativeInitializeAnimation(); // Don't call Super since it is empty. if (const TObjectPtr owningPawn = TryGetPawnOwner()) { OwningPawn = owningPawn; MovementComponent = owningPawn->GetMovementComponent(); MoveableInterface.SetObject(owningPawn); MoveableInterface.SetInterface(Cast(owningPawn)); if (MoveableInterface) { LocomotionComponent = MoveableInterface->GetLocomotionComponent(); } } } void UOLSBaseLayerAnimInstance::NativeUninitializeAnimation() { // Super::NativeUninitializeAnimation(); // Don't call Super since it is empty. if (LocomotionComponent) { LocomotionComponent->GetOnStanceChangedNativeDelegate().RemoveAll(this); LocomotionComponent->GetOnGaitChangedNativeDelegate().RemoveAll(this); LocomotionComponent->GetOnRotationModeChangedNativeDelegate().RemoveAll(this); } } void UOLSBaseLayerAnimInstance::NativeBeginPlay() { Super::NativeBeginPlay(); if (LocomotionComponent) { LocomotionComponent->GetOnStanceChangedNativeDelegate().AddWeakLambda( this, [this](const EOLSStance& prevStance) { if (LocomotionComponent) { Stance = LocomotionComponent->GetStance(); } }); LocomotionComponent->GetOnDesiredGaitChangedNativeDelegate().AddWeakLambda( this, [this](const EOLSGait& prevDesiredGait) { if (LocomotionComponent) { DesiredGait = LocomotionComponent->GetDesiredGait(); } }); LocomotionComponent->GetOnGaitChangedNativeDelegate().AddWeakLambda( this, [this](const EOLSGait& prevGait) { if (LocomotionComponent) { PrevGait = prevGait; Gait = LocomotionComponent->GetGait(); bHasGaitChanged = true; } }); LocomotionComponent->GetOnRotationModeChangedNativeDelegate().AddWeakLambda( this, [this](const EOLSRotationMode& prevRotationMode) { if (LocomotionComponent) { RotationMode = LocomotionComponent->GetRotationMode(); bHasRotationModeChanged = true; } }); } } void UOLSBaseLayerAnimInstance::NativeUpdateAnimation(float deltaSeconds) { // Super::NativeUpdateAnimation(deltaSeconds); // Don't call Super since it is empty. if (OwningPawn && MoveableInterface && MovementComponent) { NativeUpdateLocationData(OwningPawn, deltaSeconds); NativeUpdateRotationData(OwningPawn, deltaSeconds); NativeUpdateVelocityData(MoveableInterface, OwningPawn, deltaSeconds); NativeUpdateAccelerationData(OwningPawn, deltaSeconds); NativeUpdateCharacterStateData(MoveableInterface, MovementComponent, deltaSeconds); NativeUpdateAimingData(OwningPawn, deltaSeconds); } } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateAnimation(float deltaSeconds) { // Super::NativeThreadSafeUpdateAnimation(deltaSeconds); // Don't call Super since it is empty. NativeThreadSafeUpdateLocationData(deltaSeconds); NativeThreadSafeUpdateRotationData(deltaSeconds); NativeThreadSafeUpdateVelocityData(deltaSeconds); NativeThreadSafeUpdateAccelerationData(deltaSeconds); NativeThreadSafeUpdateWallDetectionHeuristic(deltaSeconds); NativeThreadSafeUpdateCharacterStateData(deltaSeconds); NativeThreadSafeUpdateAimingData(deltaSeconds); NativeThreadSafeUpdateRootYawOffset(deltaSeconds); bIsFirstUpdate = false; } void UOLSBaseLayerAnimInstance::NativePostEvaluateAnimation() { // Don't call Super since it's empty. #if WITH_EDITOR const TObjectPtr owningActor = GetOwningActor(); if (owningActor) { const FVector& actorLocation = owningActor->GetActorLocation(); const FVector& actorForwardDir = owningActor->GetActorForwardVector() * 100.f; UKismetSystemLibrary::DrawDebugArrow(this, actorLocation, actorLocation + actorForwardDir, 0.f, FLinearColor::Black, 0.f, 1.f); const TObjectPtr owningComponent = GetOwningComponent(); if (owningComponent) { const FVector& rightDir = UKismetMathLibrary::GetRightVector(owningComponent->GetSocketRotation(SKEL_RootBoneName)) * 100.f; UKismetSystemLibrary::DrawDebugArrow(this, actorLocation, actorLocation + rightDir, 0.f, FLinearColor::Red, 0.f, 1.f); } } #endif } void UOLSBaseLayerAnimInstance::NativeUpdateRotationData(APawn* owningPawn, const float deltaSeconds) { OwningPawnRotation = owningPawn->GetActorRotation(); } void UOLSBaseLayerAnimInstance::NativeUpdateLocationData(APawn* owningPawn, const float deltaSeconds) { OwningPawnLocation = owningPawn->GetActorLocation(); } void UOLSBaseLayerAnimInstance::NativeUpdateVelocityData(const TScriptInterface& moveableInterface, APawn* owningPawn, const float deltaSeconds) { OwningPawnVelocity = owningPawn->GetVelocity(); } void UOLSBaseLayerAnimInstance::NativeUpdateAccelerationData(APawn* owningPawn, const float deltaSeconds) { OwningPawnAcceleration = MoveableInterface->GetCurrentAcceleration(); } void UOLSBaseLayerAnimInstance::NativeUpdateCharacterStateData( const TScriptInterface& moveableInterface, UPawnMovementComponent* movementComponent, const float deltaSeconds) { OwningPawnMovementMode = moveableInterface->GetMovementMode(); bIsOwningPawnOnGround = movementComponent->IsMovingOnGround(); bIsOwningPawnCrouching = movementComponent->IsCrouching(); bIsOwningOrientRotationToMovement = moveableInterface->IsOrientRotationToMovement(); OwningPawnGravityZ = movementComponent->GetGravityZ(); OwningPawnMaxSpeed = movementComponent->GetMaxSpeed(); } void UOLSBaseLayerAnimInstance::NativeUpdateAimingData(APawn* owningPawn, const float deltaSeconds) { OwningPawnAimRotation = owningPawn->GetBaseAimRotation(); } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateRotationData(const float deltaSeconds) { if (bIsFirstUpdate) { YawDeltaSinceLastUpdate = 0.f; YawDeltaSpeed = 0.f; return; } YawDeltaSinceLastUpdate = OwningPawnRotation.Yaw - WorldRotation.Yaw; YawDeltaSpeed = UKismetMathLibrary::SafeDivide(YawDeltaSinceLastUpdate, deltaSeconds); AdditiveLeanAngle = YawDeltaSpeed * ((bIsOwningPawnCrouching) ? .025f : .0375f); WorldRotation = OwningPawnRotation; // Call custom logic on blueprint. BlueprintThreadSafeUpdateRotationData(deltaSeconds); } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateLocationData(const float deltaSeconds) { if (bIsFirstUpdate) { DisplacementSinceLastUpdate = 0.f; DisplacementSpeed = 0.f; return; } DisplacementSinceLastUpdate = (OwningPawnLocation - WorldLocation).Size2D(); WorldLocation = OwningPawnLocation; DisplacementSpeed = UKismetMathLibrary::SafeDivide(DisplacementSinceLastUpdate, deltaSeconds); // Call custom logic on blueprint. BlueprintThreadSafeUpdateLocationData(deltaSeconds); } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateVelocityData(const float deltaSeconds) { const bool wasMovingLastUpdate = !LocalVelocity2D.IsNearlyZero(1.e-4f); if (wasMovingLastUpdate) { StopLocalVelocityCardinalDirection = LocalVelocityDirection; } WorldVelocity = OwningPawnVelocity; const FVector worldVelocity2D = WorldVelocity * FVector(1.f, 1.f, 0.f); LocalVelocity2D = UKismetMathLibrary::LessLess_VectorRotator(worldVelocity2D, WorldRotation); LocalVelocityDirectionAngle = UKismetAnimationLibrary::CalculateDirection(worldVelocity2D, WorldRotation); LocalVelocityDirectionAngleWithOffset = LocalVelocityDirectionAngle - RootYawOffset; LocalVelocityDirection = UOLSLocomotionBPLibrary::SelectCardinalDirectionFromAngle(LocalVelocityDirectionAngleWithOffset, CardinalDirectionDeadZone, LocalVelocityDirection, wasMovingLastUpdate); LocalVelocityDirectionNoOffset = UOLSLocomotionBPLibrary::SelectCardinalDirectionFromAngle( LocalVelocityDirectionAngle, CardinalDirectionDeadZone, LocalVelocityDirection, wasMovingLastUpdate); bHasVelocity = !FMath::IsNearlyZero(LocalVelocity2D.SizeSquared2D()); // Call custom logic on blueprint. BlueprintThreadSafeUpdateVelocityData(deltaSeconds); } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateAccelerationData(const float deltaSeconds) { const FVector worldAcceleration2D = OwningPawnAcceleration * FVector(1.f, 1.f, 0.f); LocalAcceleration2D = UKismetMathLibrary::LessLess_VectorRotator(worldAcceleration2D, WorldRotation); bHasAcceleration = (!FMath::IsNearlyZero(LocalAcceleration2D.SizeSquared2D(), .000001f)); PivotDirection2D = UKismetMathLibrary::Normal(UKismetMathLibrary::VLerp(PivotDirection2D, UKismetMathLibrary::Normal(worldAcceleration2D), .5f)); CardinalDirectionFromAcceleration = (IsLookingOrAimingDirection() ? UOLSLocomotionBPLibrary::GetOppositeCardinalDirectional( UOLSLocomotionBPLibrary::SelectCardinalDirectionFromAngle( UKismetAnimationLibrary::CalculateDirection(PivotDirection2D, WorldRotation), CardinalDirectionDeadZone, EOLSCardinalDirection::EForward)) : EOLSCardinalDirection::EBackward); // Call custom logic on blueprint. BlueprintThreadSafeUpdateAccelerationData(deltaSeconds); } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateWallDetectionHeuristic(const float deltaSeconds) { const bool hasAccelerationPassedThreshold = (LocalAcceleration2D.Size2D() > .1f); const bool hasVelocityBelowThreshold = (LocalVelocity2D.Size2D() < 200.f); const FVector normalLocalAcceleration2D = UKismetMathLibrary::Normal(LocalAcceleration2D); const FVector normalLocalVelocity2D = UKismetMathLibrary::Normal(LocalVelocity2D); const bool isDotProductInRange = UKismetMathLibrary::InRange_FloatFloat( UKismetMathLibrary::Dot_VectorVector( normalLocalAcceleration2D, normalLocalVelocity2D), -.6f, .6f); bIsRunningIntoWall = hasAccelerationPassedThreshold && hasVelocityBelowThreshold && isDotProductInRange; // Call custom logic on blueprint. BlueprintThreadSafeUpdateWallDetectionHeuristic(deltaSeconds); } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateCharacterStateData(const float deltaSeconds) { // Locomotion Component Data. { bHasGaitChanged = (Gait != LastGait); LastGait = Gait; bHasRotationModeChanged = (RotationMode != LastRotationMode); LastRotationMode = RotationMode; } // Crouch state. { const bool wasCrouchingLastUpdate = bIsCrouching; bIsCrouching = bIsOwningPawnCrouching; bHasCrouchStateChanged = (bIsCrouching != wasCrouchingLastUpdate); } // ADS state. { bHasADSStateChanged = (bGameplayTag_IsADS != bWasADSLastUpdate); bWasADSLastUpdate = bGameplayTag_IsADS; } // Weapon fired state. { TimeSinceFiredWeapon = (bGameplayTag_IsFiring) ? 0.f : TimeSinceFiredWeapon + deltaSeconds; } // In air state. { bIsJumping = false; bIsFalling = false; if (OwningPawnMovementMode == MOVE_Falling) { if (WorldVelocity.Z > 0.f) { bIsJumping = true; } else { bIsFalling = true; } // Update Jump Fall Data. { if (bIsJumping) { TimeToJumpApex = UKismetMathLibrary::SafeDivide(0.f - WorldVelocity.Z, OwningPawnGravityZ); } else { TimeToJumpApex = 0.f; } } } } // Call custom logic on blueprint. BlueprintThreadSafeUpdateCharacterData(deltaSeconds); } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateAimingData(const float deltaSeconds) { AimPitch = UKismetMathLibrary::NormalizeAxis(OwningPawnRotation.Pitch); // Call custom logic on blueprint. BlueprintThreadSafeUpdateAimingData(deltaSeconds); } void UOLSBaseLayerAnimInstance::NativeThreadSafeUpdateRootYawOffset(const float deltaSeconds) { if (RootYawOffsetMode == EOLSRootYawOffsetMode::EAccumulate) { SetRootYawOffset(RootYawOffset - YawDeltaSinceLastUpdate); } if (RootYawOffsetMode == EOLSRootYawOffsetMode::EBlendOut) { SetRootYawOffset( UKismetMathLibrary::FloatSpringInterp(RootYawOffset, 0.f, RootYawOffsetSpringState, 80.f, 1.f, deltaSeconds, 1.f, .5f)); } RootYawOffsetMode = EOLSRootYawOffsetMode::EBlendOut; } UPawnMovementComponent* UOLSBaseLayerAnimInstance::GetMovementComponent() const { return MovementComponent; } const FVector& UOLSBaseLayerAnimInstance::GetWorldVelocity() const { return WorldVelocity; } const float& UOLSBaseLayerAnimInstance::GetLocalVelocityDirectionAngle() const { return LocalVelocityDirectionAngle; } const float& UOLSBaseLayerAnimInstance::GetDisplacementSpeed() const { return DisplacementSpeed; } const FVector& UOLSBaseLayerAnimInstance::GetLocalAcceleration2D() const { return LocalAcceleration2D; } bool UOLSBaseLayerAnimInstance::IsLookingOrAimingDirection() const { return (IsLookingDirection() || IsAimingDirection()); } bool UOLSBaseLayerAnimInstance::IsVelocityOrLookingDirection() const { return (IsVelocityDirection() || IsLookingDirection()); } bool UOLSBaseLayerAnimInstance::IsVelocityOrAimingDirection() const { return (IsVelocityDirection() || IsAimingDirection()); } bool UOLSBaseLayerAnimInstance::IsAimingDirection() const { return (RotationMode == EOLSRotationMode::EAiming); } bool UOLSBaseLayerAnimInstance::IsLookingDirection() const { return (RotationMode == EOLSRotationMode::ELookingDirection); } bool UOLSBaseLayerAnimInstance::IsVelocityDirection() const { return (RotationMode == EOLSRotationMode::EVelocityDirection); } bool UOLSBaseLayerAnimInstance::IsCrouching() const { return bIsCrouching; } bool UOLSBaseLayerAnimInstance::HasVelocity() const { return bHasVelocity; } bool UOLSBaseLayerAnimInstance::HasAcceleration() const { return bHasAcceleration; } bool UOLSBaseLayerAnimInstance::ShouldDistanceMatchStop() const { return HasVelocity() && !HasAcceleration(); } bool UOLSBaseLayerAnimInstance::IsMovingPerpendicularToInitialPivot() const { const bool isPivotFwdOrBwd = (PivotInitialDirection == EOLSCardinalDirection::EForward || PivotInitialDirection == EOLSCardinalDirection::EBackward); const bool isVelocityFwdOrBwd = (LocalVelocityDirection == EOLSCardinalDirection::EForward || LocalVelocityDirection == EOLSCardinalDirection::EBackward); const bool isPivotLeftOrRight = (PivotInitialDirection == EOLSCardinalDirection::ELeft || PivotInitialDirection == EOLSCardinalDirection::ERight); const bool isVelocityLeftOrRight = (LocalVelocityDirection == EOLSCardinalDirection::ELeft || LocalVelocityDirection == EOLSCardinalDirection::ERight); return (isPivotFwdOrBwd && !isVelocityFwdOrBwd) || (isPivotLeftOrRight && !isVelocityLeftOrRight); } const EOLSRotationMode& UOLSBaseLayerAnimInstance::GetRotationMode() const { return RotationMode; } const float& UOLSBaseLayerAnimInstance::GetOwningPawnMaxSpeed() const { return OwningPawnMaxSpeed; } void UOLSBaseLayerAnimInstance::SetRootYawOffset(const float rootYawOffset) { if (!bEnableRootYawOffset) { RootYawOffset = 0.f; AimYaw = 0.f; return; } const float normalRootYawOffset = UKismetMathLibrary::NormalizeAxis(rootYawOffset); RootYawOffset = (RootYawOffsetAngleClamp.X == RootYawOffsetAngleClamp.Y) ? normalRootYawOffset : FMath::Clamp(normalRootYawOffset, RootYawOffsetAngleClamp.X, RootYawOffsetAngleClamp.Y); AimYaw = RootYawOffset * -1.f; } void UOLSBaseLayerAnimInstance::ProcessTurnYawCurve() { float previousTurnYawCurveValue = TurnYawCurveValue; const float turnYawWeightCurveValue = GetCurveValue(TurnYawWeightCurveName); if (FMath::IsNearlyZero(turnYawWeightCurveValue, 0.0001f)) { TurnYawCurveValue = 0.f; previousTurnYawCurveValue = 0.f; return; } const float remainingTurnYawCurveValue = GetCurveValue(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. SetRootYawOffset(RootYawOffset - (TurnYawCurveValue - previousTurnYawCurveValue)); } } void UOLSBaseLayerAnimInstance::ProcessTurnYawCurve_ForwardFacing() { float previousTurnYawCurveValue = TurnYawCurveValue; const float turnYawWeightCurveValue = GetCurveValue(TurnYawWeightCurveName); if (FMath::IsNearlyZero(turnYawWeightCurveValue, 0.0001f)) { TurnYawCurveValue = 0.f; previousTurnYawCurveValue = 0.f; // When the animation blends in, we could have near-zero weights. // When this value is goes up, and then down to 0, we know. if (MaxTurnYawValue != 0.f) { bHasReachedEndTurn = true; } } else { const float remainingTurnYawCurveValue = GetCurveValue(RemainingTurnYawCurveName); TurnYawCurveValue = UKismetMathLibrary::SafeDivide(remainingTurnYawCurveValue, turnYawWeightCurveValue); if (FMath::Abs(TurnYawCurveValue) > FMath::Abs(MaxTurnYawValue)) { MaxTurnYawValue = TurnYawCurveValue; } // 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) { const float actualAngle = UKismetMathLibrary::SafeDivide(LocalVelocityDirectionAngleWithOffset, TurnYawCurveValue) * (TurnYawCurveValue - previousTurnYawCurveValue); const float minAngle = FMath::Abs(LocalVelocityDirectionAngleWithOffset) * -1.f; const float maxAngle = FMath::Abs(LocalVelocityDirectionAngleWithOffset); // Reduce the target yaw offset by the amount of rotation from the turn animation. SetRootYawOffset(RootYawOffset - FMath::Clamp(actualAngle, minAngle, maxAngle)); } } } float UOLSBaseLayerAnimInstance::CalculateMeshSpaceBlendWeight(const FName localSpaceLayerCurveName) const { // If this condition fails so MeshSpace will be prioritized. if (localSpaceLayerCurveName.IsNone()) { return 1.f; } const int32 localSpaceCurveValue = FMath::FloorToInt(GetCurveValue(localSpaceLayerCurveName)); return 1 - localSpaceCurveValue; } UOLSLocomotionComponent* UOLSBaseLayerAnimInstance::ThreadSafeGetLocomotionComponent() const { return LocomotionComponent; }