// © 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 every frame void UOLSLocomotionComponent::TickComponent(float deltaTime, ELevelTick tickType, FActorComponentTickFunction* thisTickFunction) { Super::TickComponent(deltaTime, tickType, thisTickFunction); // ... UpdateEssentialValues(deltaTime); UpdateGait(deltaTime); } void UOLSLocomotionComponent::Initialize() { 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); } 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; } // 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(); // 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()); } void UOLSLocomotionComponent::UpdateGait(const float deltaTime) { const EOLSGait allowedLocomotionState = GetAllowedGait(); const EOLSGait actualLocomotionState = GetActualGait(allowedLocomotionState); SetGait(actualLocomotionState); } 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); } } float UOLSLocomotionComponent::GetMaxSpeedByGait(const EOLSGait gait) const { return Gaits.FindChecked(gait); } const float& UOLSLocomotionComponent::GetMovementInputAmount() const { return MovementInputAmount; } 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 bool isCrouching = (GetStance() == EOLSStance::EStanding); const bool canJog = (MovementInputAmount > .8f && isCrouching); return canJog; } FOLSStanceNativeDelegate& UOLSLocomotionComponent::GetOnStanceChangedNativeDelegate() { return OnStanceChangedNativeDelegate; } FOLSGaitNativeDelegate& UOLSLocomotionComponent::GetOnDesiredGaitChangedNativeDelegate() { return OnDesiredGaitChangedNativeDelegate; } FOLSGaitNativeDelegate& UOLSLocomotionComponent::GetOnGaitChangedNativeDelegate() { return OnGaitChangedNativeDelegate; } FOLSRotationModeStateNativeDelegate& UOLSLocomotionComponent::GetOnRotationModeChangedNativeDelegate() { return OnRotationModeChangedNativeDelegate; }