diff --git a/Source/Nakatomi/NakatomiCMC.cpp b/Source/Nakatomi/NakatomiCMC.cpp index ab8e374..93e92d8 100644 --- a/Source/Nakatomi/NakatomiCMC.cpp +++ b/Source/Nakatomi/NakatomiCMC.cpp @@ -3,13 +3,22 @@ #include "NakatomiCMC.h" +#include #include +#include "NakatomiCharacter.h" UNakatomiCMC::UNakatomiCMC(): Safe_bWantsToSprint(false) { NavAgentProps.bCanCrouch = true; } +void UNakatomiCMC::InitializeComponent() +{ + Super::InitializeComponent(); + + NakatomiCharacterOwner = Cast(GetOwner()); +} + // Checks if we can combine the NewMove with the current move to save on data. // If all the data in a saved move is identical, if true, we cam tell the server to run the existing move instead. bool UNakatomiCMC::FSavedMove_Nakatomi::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InCharacter, @@ -51,6 +60,7 @@ void UNakatomiCMC::FSavedMove_Nakatomi::SetMoveFor(ACharacter* C, float InDeltaT UNakatomiCMC* CharacterMovement = Cast(C->GetCharacterMovement()); Saved_bWantsToSprint = CharacterMovement->Safe_bWantsToSprint; + Saved_bPrevWantsToCrouch = CharacterMovement->Safe_bPrevWantsToCrouch; } void UNakatomiCMC::FSavedMove_Nakatomi::PrepMoveFor(ACharacter* C) @@ -60,6 +70,7 @@ void UNakatomiCMC::FSavedMove_Nakatomi::PrepMoveFor(ACharacter* C) UNakatomiCMC* CharacterMovement = Cast(C->GetCharacterMovement()); CharacterMovement->Safe_bWantsToSprint = Saved_bWantsToSprint; + CharacterMovement->Safe_bPrevWantsToCrouch = Saved_bPrevWantsToCrouch; } UNakatomiCMC::FNetworkPredictionData_Client_Nakatomi::FNetworkPredictionData_Client_Nakatomi( @@ -110,6 +121,51 @@ void UNakatomiCMC::OnMovementUpdated(float DeltaSeconds, const FVector& OldLocat MaxWalkSpeed = Walk_MaxWalkSpeed; } } + + Safe_bPrevWantsToCrouch = bWantsToCrouch; +} + +bool UNakatomiCMC::IsMovingOnGround() const +{ + return Super::IsMovingOnGround() || IsCustomMovementMode(CMOVE_Slide); +} + +bool UNakatomiCMC::CanCrouchInCurrentState() const +{ + return Super::CanCrouchInCurrentState() && IsMovingOnGround(); +} + +void UNakatomiCMC::UpdateCharacterStateBeforeMovement(float DeltaSeconds) +{ + if (MovementMode == MOVE_Walking && !bWantsToCrouch && Safe_bPrevWantsToCrouch) + { + FHitResult PotentialSlideSurface; + if (Velocity.SizeSquared() > pow(Slide_MinSpeed, 2) && GetSlideSurface(PotentialSlideSurface)) + { + EnterSlide(); + } + } + + if (IsCustomMovementMode(CMOVE_Slide) && !bWantsToCrouch) + { + ExitSlide(); + } + + Super::UpdateCharacterStateBeforeMovement(DeltaSeconds); +} + +void UNakatomiCMC::PhysCustom(float deltaTime, int32 Iterations) +{ + Super::PhysCustom(deltaTime, Iterations); + + switch (CustomMovementMode) + { + case CMOVE_Slide: + PhysSlide(deltaTime, Iterations); + break; + default: + UE_LOG(LogTemp, Fatal, TEXT("Invalid Movement Mode")); + } } void UNakatomiCMC::EnableSprint() @@ -131,3 +187,101 @@ void UNakatomiCMC::DisableCrouch() { bWantsToCrouch = false; } + +bool UNakatomiCMC::IsCustomMovementMode(ECustomMovementMove InCustomMovementMode) const +{ + return MovementMode == MOVE_Custom && CustomMovementMode == InCustomMovementMode; +} + +void UNakatomiCMC::EnterSlide() +{ + bWantsToCrouch = true; + Velocity += Velocity.GetSafeNormal2D() * Slide_EnterImpulse; + SetMovementMode(MOVE_Custom, CMOVE_Slide); +} + +void UNakatomiCMC::ExitSlide() +{ + bWantsToCrouch = false; + + FQuat NewRotation = FRotationMatrix::MakeFromXZ(UpdatedComponent->GetForwardVector().GetSafeNormal2D(), FVector::UpVector).ToQuat(); + FHitResult Hit; + SafeMoveUpdatedComponent(FVector::ZeroVector, NewRotation, true, Hit); + SetMovementMode(MOVE_Walking); +} + +void UNakatomiCMC::PhysSlide(float deltaTime, int32 Iterations) +{ + if (deltaTime < MIN_TICK_TIME) return; + + // This is probably not needed for Sliding but including for completeness, will likely remove later. + RestorePreAdditiveRootMotionVelocity(); + + FHitResult SurfaceHit; + if (!GetSlideSurface(SurfaceHit) || Velocity.SizeSquared() < pow(Slide_MinSpeed, 2)) + { + ExitSlide(); + StartNewPhysics(deltaTime, Iterations); + } + + // Surface Gravity + Velocity += Slide_GravityForce * FVector::DownVector * deltaTime; + + // Calculate Strafe + if (FMath::Abs(FVector::DotProduct(Acceleration.GetSafeNormal(), UpdatedComponent->GetRightVector())) > 0.5f) + { + Acceleration = Acceleration.ProjectOnTo(UpdatedComponent->GetRightVector()); + } + else + { + Acceleration = FVector::ZeroVector; + } + + // Calculate Velocity + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + CalcVelocity(deltaTime, Slide_Friction, true, GetMaxBrakingDeceleration()); + } + ApplyRootMotionToVelocity(deltaTime); + + // Perform Move + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + FQuat OldRotation = UpdatedComponent->GetComponentRotation().Quaternion(); + + FHitResult Hit(1.f); + FVector AdjustedVelocity = Velocity * deltaTime; + FVector VelocityPlaneDirection = FVector::VectorPlaneProject(Velocity, SurfaceHit.Normal).GetSafeNormal(); + FQuat NewRotation = FRotationMatrix::MakeFromXZ(VelocityPlaneDirection, SurfaceHit.Normal).ToQuat(); + SafeMoveUpdatedComponent(AdjustedVelocity, NewRotation, true, Hit); + + // Post Move Checks + if (Hit.Time < 1.f) + { + HandleImpact(Hit, deltaTime, AdjustedVelocity); + SlideAlongSurface(AdjustedVelocity, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + + FHitResult NewSurfaceHit; + if (!GetSlideSurface(NewSurfaceHit) || Velocity.SizeSquared() < pow(Slide_MinSpeed, 2)) + { + ExitSlide(); + } + + // Update outgoing Velocity and Acceleration + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / deltaTime; + } +} + +bool UNakatomiCMC::GetSlideSurface(FHitResult& Hit) const +{ + const FVector Start = UpdatedComponent->GetComponentLocation(); + const FVector End = Start + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.f * FVector::DownVector; + const FName ProfileName = TEXT("BlockAll"); + + return GetWorld()->LineTraceSingleByProfile(Hit, Start, End, ProfileName); +} diff --git a/Source/Nakatomi/NakatomiCMC.h b/Source/Nakatomi/NakatomiCMC.h index 689975e..49ac9a9 100644 --- a/Source/Nakatomi/NakatomiCMC.h +++ b/Source/Nakatomi/NakatomiCMC.h @@ -3,9 +3,18 @@ #pragma once #include "CoreMinimal.h" +#include "Nakatomi.h" #include "GameFramework/CharacterMovementComponent.h" #include "NakatomiCMC.generated.h" +UENUM(BlueprintType) +enum ECustomMovementMove +{ + CMOVE_None UMETA(Hidden), + CMOVE_Slide UMETA(DisplayName = "Slide"), + CMOVE_MAX UMETA(Hidden), +}; + /** * */ @@ -18,8 +27,11 @@ class NAKATOMI_API UNakatomiCMC : public UCharacterMovementComponent { typedef FSavedMove_Character Super; + // Flag uint8 Saved_bWantsToSprint:1; + uint8 Saved_bPrevWantsToCrouch:1; + virtual bool CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InCharacter, float MaxDelta) const override; virtual void Clear() override; virtual uint8 GetCompressedFlags() const override; @@ -37,19 +49,49 @@ class NAKATOMI_API UNakatomiCMC : public UCharacterMovementComponent virtual FSavedMovePtr AllocateNewMove() override; }; - UPROPERTY(EditDefaultsOnly) float Sprint_MaxWalkSpeed; - UPROPERTY(EditDefaultsOnly) float Walk_MaxWalkSpeed; + UPROPERTY(EditDefaultsOnly) + float Sprint_MaxWalkSpeed; + + UPROPERTY(EditDefaultsOnly) + float Walk_MaxWalkSpeed; + + UPROPERTY(EditDefaultsOnly) + float Slide_MinSpeed = 10.f; + + UPROPERTY(EditDefaultsOnly) + float Slide_EnterImpulse = 2000.f; + + UPROPERTY(EditDefaultsOnly) + float Slide_GravityForce = 5000.f; + + UPROPERTY(EditDefaultsOnly) + float Slide_Friction = 1.3f; bool Safe_bWantsToSprint; + bool Safe_bPrevWantsToCrouch; + + UPROPERTY(Transient) + ANakatomiCharacter* NakatomiCharacterOwner; public: UNakatomiCMC(); +protected: + void InitializeComponent() override; + FNetworkPredictionData_Client* GetPredictionData_Client() const override; protected: virtual void UpdateFromCompressedFlags(uint8 Flags) override; virtual void OnMovementUpdated(float DeltaSeconds, const FVector& OldLocation, const FVector& OldVelocity) override; + + virtual bool IsMovingOnGround() const override; + + virtual bool CanCrouchInCurrentState() const override; + + virtual void UpdateCharacterStateBeforeMovement(float DeltaSeconds) override; + + virtual void PhysCustom(float deltaTime, int32 Iterations) override; public: UFUNCTION(BlueprintCallable) @@ -63,4 +105,17 @@ public: UFUNCTION(BlueprintCallable) void DisableCrouch(); + + UFUNCTION() + bool IsCustomMovementMode(ECustomMovementMove InCustomMovementMode) const; + +private: + + void EnterSlide(); + + void ExitSlide(); + + void PhysSlide(float deltaTime, int32 Iterations); // Every movement mode requires a physics function to work + + bool GetSlideSurface(FHitResult& Hit) const; }; diff --git a/Source/Nakatomi/NakatomiCharacter.h b/Source/Nakatomi/NakatomiCharacter.h index 1951dc3..574f69d 100644 --- a/Source/Nakatomi/NakatomiCharacter.h +++ b/Source/Nakatomi/NakatomiCharacter.h @@ -5,7 +5,7 @@ #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "HealthComponent.h" -#include "NakatomiCMC.h" +#include "Nakatomi.h" #include "Throwable.h" #include "Weapon.h" #include "NakatomiCharacter.generated.h"