diff --git a/Content/Legumix/Player/BP_Play.uasset b/Content/Legumix/Player/BP_Play.uasset index 3116513..e9e7d3e 100644 --- a/Content/Legumix/Player/BP_Play.uasset +++ b/Content/Legumix/Player/BP_Play.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6e959b5dab32920b37db91c45d18ddff4d5f33f32c291b59afa75cbe381f4af -size 470532 +oid sha256:efdd9c655acecb5f2d9e1dfbe49845d91f6347bca64bd0ecb7213dcf29e00f85 +size 478685 diff --git a/Content/Legumix/Player/BP_PlayerController.uasset b/Content/Legumix/Player/BP_PlayerController.uasset index 6ea766e..c402d84 100644 --- a/Content/Legumix/Player/BP_PlayerController.uasset +++ b/Content/Legumix/Player/BP_PlayerController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aed27e7a1ce2efa1862e330681c83f0329c73288c6003dcd827ecc83f60dbdb7 -size 21577 +oid sha256:504abdca2dc1731389651a842f3516c2512c3ef3652815c8b38fdbbe54d2b7cf +size 22113 diff --git a/Source/LegumeMix/Private/Player/LMCameraManager.cpp b/Source/LegumeMix/Private/Player/LMCameraManager.cpp new file mode 100644 index 0000000..3808df6 --- /dev/null +++ b/Source/LegumeMix/Private/Player/LMCameraManager.cpp @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Player/LMCameraManager.h" + +#include "Components/CapsuleComponent.h" +#include "Player/LMMovementComponent.h" +#include "Player/LMPlayer.h" + +ALMCameraManager::ALMCameraManager() +{ +} + +void ALMCameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime) +{ + Super::UpdateViewTarget(OutVT, DeltaTime); + + if (ALMPlayer* Player = Cast(GetOwningPlayerController()->GetPawn())) + { + ULMMovementComponent* MovementComponent = Player->GetLegumixMovementComponent(); + FVector TargetCrouchOffset = FVector(0, 0, + MovementComponent->GetCrouchedHalfHeight() - Player->GetClass()->GetDefaultObject()->GetCapsuleComponent()->GetScaledCapsuleHalfHeight()); + + FVector Offset = FMath::Lerp(FVector::ZeroVector, TargetCrouchOffset, FMath::Clamp(CrouchBlendTime / CrouchBlendDuration, 0.f, 1.f)); + + if (MovementComponent->IsCrouching()) + { + CrouchBlendTime = FMath::Clamp(CrouchBlendTime + DeltaTime, 0.f, CrouchBlendDuration); + Offset -= TargetCrouchOffset; + } + else + { + CrouchBlendTime = FMath::Clamp(CrouchBlendTime - DeltaTime, 0.f, CrouchBlendDuration); + } + + if (MovementComponent->IsMovingOnGround()) + { + OutVT.POV.Location += Offset; + } + } +} diff --git a/Source/LegumeMix/Private/Player/LMMovementComponent.cpp b/Source/LegumeMix/Private/Player/LMMovementComponent.cpp new file mode 100644 index 0000000..5fbd0d6 --- /dev/null +++ b/Source/LegumeMix/Private/Player/LMMovementComponent.cpp @@ -0,0 +1,264 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Player/LMMovementComponent.h" + +#include "Components/CapsuleComponent.h" +#include "Player/LMPlayer.h" + + +#pragma region Saved Move + +ULMMovementComponent::FSavedMove_Legumix::FSavedMove_Legumix() +{ + Saved_bPrevWantsToCrouch = 0; +} + +// Checks the current move and the new move to see if they can be combined +bool ULMMovementComponent::FSavedMove_Legumix::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InCharacter, float MaxDelta) const +{ + return FSavedMove_Character::CanCombineWith(NewMove, InCharacter, MaxDelta); +} + +void ULMMovementComponent::FSavedMove_Legumix::Clear() +{ + FSavedMove_Character::Clear(); +} + +uint8 ULMMovementComponent::FSavedMove_Legumix::GetCompressedFlags() const +{ + return FSavedMove_Character::GetCompressedFlags(); +} + +void ULMMovementComponent::FSavedMove_Legumix::SetMoveFor(ACharacter* C, float InDeltaTime, FVector const& NewAccel, FNetworkPredictionData_Client_Character& ClientData) +{ + FSavedMove_Character::SetMoveFor(C, InDeltaTime, NewAccel, ClientData); + const ULMMovementComponent* CharacterMovement = Cast(C->GetCharacterMovement()); + + Saved_bPrevWantsToCrouch = CharacterMovement->Safe_bPrevWantsToCrouch; +} + +void ULMMovementComponent::FSavedMove_Legumix::PrepMoveFor(ACharacter* C) +{ + FSavedMove_Character::PrepMoveFor(C); + ULMMovementComponent* CharacterMovement = Cast(C->GetCharacterMovement()); + CharacterMovement->Safe_bPrevWantsToCrouch = Saved_bPrevWantsToCrouch; +} + +#pragma endregion + +#pragma region Client Network Prediction Data + +ULMMovementComponent::FNetworkPredictionData_Client_Legumix::FNetworkPredictionData_Client_Legumix(const UCharacterMovementComponent& ClientMovement) +: Super(ClientMovement) +{ +} + +FSavedMovePtr ULMMovementComponent::FNetworkPredictionData_Client_Legumix::AllocateNewMove() +{ + return FSavedMovePtr(new FSavedMove_Legumix()); +} + +#pragma endregion + +#pragma region CMC + +ULMMovementComponent::ULMMovementComponent() +{ + PrimaryComponentTick.bCanEverTick = true; + NavAgentProps.bCanCrouch = true; +} + +FNetworkPredictionData_Client* ULMMovementComponent::GetPredictionData_Client() const +{ + check(PawnOwner != nullptr); + + if (ClientPredictionData == nullptr) + { + ULMMovementComponent* MutableThis = const_cast(this); + + MutableThis->ClientPredictionData = new FNetworkPredictionData_Client_Legumix(*this); + MutableThis->ClientPredictionData->MaxSmoothNetUpdateDist = 92.f; + MutableThis->ClientPredictionData->MaxSmoothNetUpdateDist = 140.f; + } + return ClientPredictionData; +} + + +void ULMMovementComponent::InitializeComponent() +{ + Super::InitializeComponent(); + CharacterOwner = Cast(GetOwner()); +} + +void ULMMovementComponent::UpdateFromCompressedFlags(uint8 Flags) +{ + Super::UpdateFromCompressedFlags(Flags); +} + + +void ULMMovementComponent::OnMovementUpdated(float DeltaSeconds, const FVector& OldLocation, const FVector& OldVelocity) +{ + Super::OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity); + + Safe_bPrevWantsToCrouch = bWantsToCrouch; +} + +bool ULMMovementComponent::IsMovingOnGround() const +{ + return Super::IsMovingOnGround() || IsCustomMovementMode(ECustomMovementModes::ECMM_Sliding); +} + +bool ULMMovementComponent::CanCrouchInCurrentState() const +{ + return Super::CanCrouchInCurrentState() && IsMovingOnGround(); +} + +void ULMMovementComponent::UpdateCharacterStateBeforeMovement(float DeltaSeconds) +{ + UE_LOG(LogTemp, Log, TEXT("Velocity %f vs %f"), Velocity.SizeSquared(), pow(SlideMinSpeed, 2)) + if (MovementMode == EMovementMode::MOVE_Walking && Safe_bPrevWantsToCrouch) + { + FHitResult PotentialSlideSurface; + UE_LOG(LogTemp, Display, TEXT("PotentialSlideSurface")); + if (Velocity.SizeSquared() > pow(SlideMinSpeed, 2) && GetSlideSurface(PotentialSlideSurface)) + { + EnterSlide(); + } + } + + if (IsCustomMovementMode(ECustomMovementModes::ECMM_Sliding) && !bWantsToCrouch) + { + ExitSlide(); + } + + Super::UpdateCharacterStateBeforeMovement(DeltaSeconds); +} + +void ULMMovementComponent::PhysCustom(float DeltaTime, int32 Iterations) +{ + Super::PhysCustom(DeltaTime, Iterations); + + switch (CustomMovementMode) + { + case ECustomMovementModes::ECMM_Sliding: + PhysSlide(DeltaTime, Iterations); + break; + default: + UE_LOG(LogTemp, Fatal, TEXT("Invalid Movement Mode")) + } +} + +bool ULMMovementComponent::IsCustomMovementMode(ECustomMovementModes InCustomMovementMode) const +{ + return MovementMode == EMovementMode::MOVE_Custom && CustomMovementMode == InCustomMovementMode; +} + +void ULMMovementComponent::EnterSlide() +{ + UE_LOG(LogTemp, Error, TEXT("Entering Slide")); + bWantsToCrouch = true; + Velocity += Velocity.GetSafeNormal2D() * SlideEnterImpulse; + SetMovementMode(EMovementMode::MOVE_Custom, ECustomMovementModes::ECMM_Sliding); +} + +void ULMMovementComponent::ExitSlide() +{ + UE_LOG(LogTemp, Error, TEXT("Exiting Slide")); + bWantsToCrouch = false; + + FQuat NewRotation = FRotationMatrix::MakeFromXZ(UpdatedComponent->GetForwardVector().GetSafeNormal2D(), FVector::UpVector).ToQuat(); + FHitResult Hit; + SafeMoveUpdatedComponent(FVector::ZeroVector, NewRotation, true, Hit); + SetMovementMode(EMovementMode::MOVE_Walking); +} + +void ULMMovementComponent::PhysSlide(float DeltaTime, int32 Iterations) +{ + if (DeltaTime < MIN_TICK_TIME) + { + return; + } + + // RestorePreAdditiveRootMotionVelocity(); + + FHitResult SurfaceHit; + if (!GetSlideSurface(SurfaceHit) || Velocity.SizeSquared() < pow(SlideMinSpeed, 2)) + { + ExitSlide(); + StartNewPhysics(DeltaTime, Iterations); + return; + } + + // Surface Gravity + Velocity += SlideGravityForce * FVector::DownVector * DeltaTime; + + // Strafe + if (FMath::Abs(FVector::DotProduct(Acceleration.GetSafeNormal(), UpdatedComponent->GetRightVector())) > .5f) + { + Acceleration = Acceleration.ProjectOnTo(UpdatedComponent->GetRightVector()); + } + else + { + Acceleration = FVector::ZeroVector; + } + + // Calc Velocity + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + // bFluid == true because friction isn't applied if we are not in a fluid + CalcVelocity(DeltaTime, SlideFriction, true, GetMaxBrakingDeceleration()); + } + // ApplyRootMotionToVelocity(DeltaTime); + + // Perform Move + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + FQuat OldRotation = UpdatedComponent->GetComponentRotation().Quaternion(); + FHitResult Hit(1.f); + FVector Adjusted = Velocity * DeltaTime; + FVector VelPlaneDir = FVector::VectorPlaneProject(Velocity, SurfaceHit.Normal).GetSafeNormal(); + FQuat NewRotation = FRotationMatrix::MakeFromXZ(VelPlaneDir, SurfaceHit.Normal).ToQuat(); + + // The most important method to move the character + SafeMoveUpdatedComponent(Adjusted, NewRotation, true, Hit); + + if (Hit.Time < 1.f) + { + HandleImpact(Hit, DeltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + + FHitResult NewSurfaceHit; + if (!GetSlideSurface(NewSurfaceHit) || Velocity.SizeSquared() < pow(SlideMinSpeed, 2)) + { + ExitSlide(); + } + + // Update Outgoing Velocity & Acceleration + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / DeltaTime; + } +} + +bool ULMMovementComponent::GetSlideSurface(FHitResult& Hit) const +{ + FVector Start = UpdatedComponent->GetComponentLocation(); + FVector End = Start + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.f * FVector::DownVector; + FName ProfileName = TEXT("BlockAll"); + return GetWorld()->LineTraceSingleByProfile(Hit, Start, End, ProfileName , PlayerCharacterOwner->GetIgnoreCharacterParams()); +} + +#pragma endregion + +#pragma region Input +void ULMMovementComponent::CrouchPressed() +{ + bWantsToCrouch = !bWantsToCrouch; +} + +#pragma endregion + diff --git a/Source/LegumeMix/Private/Player/LMPlayer.cpp b/Source/LegumeMix/Private/Player/LMPlayer.cpp index 1d96f14..1973219 100644 --- a/Source/LegumeMix/Private/Player/LMPlayer.cpp +++ b/Source/LegumeMix/Private/Player/LMPlayer.cpp @@ -11,11 +11,15 @@ #include "Player/LMBulletInfo.h" #include "Player/LMHealthComponent.h" #include "Player/LMHitBox.h" +#include "Player/LMMovementComponent.h" #include "Weapon/LMWeaponManager.h" -ALMPlayer::ALMPlayer() +ALMPlayer::ALMPlayer(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass(CharacterMovementComponentName)) { + LegumixMovementComponent = Cast(GetCharacterMovement()); + PrimaryActorTick.bCanEverTick = true; SpreadStream = FRandomStream(FMath::Rand()); @@ -217,4 +221,15 @@ void ALMPlayer::SetPlayerViewOcclusionPercent() NextPlayerViewOcclusionPercent = CumulatedDamagesOnCurrentHitPeriod / HealthAtTheCurrentHitPeriodBeginning; } +FCollisionQueryParams ALMPlayer::GetIgnoreCharacterParams() +{ + FCollisionQueryParams Params; + + TArray ChildrenActors; + GetAllChildActors(ChildrenActors); + Params.AddIgnoredActors(ChildrenActors); + Params.AddIgnoredActor(this); + + return Params; +} diff --git a/Source/LegumeMix/Public/LMCustomMovementModes.h b/Source/LegumeMix/Public/LMCustomMovementModes.h new file mode 100644 index 0000000..95056dc --- /dev/null +++ b/Source/LegumeMix/Public/LMCustomMovementModes.h @@ -0,0 +1,9 @@ +#pragma once + +UENUM(BlueprintType) +enum ECustomMovementModes +{ + ECMM_None UMETA(Hidden), + ECMM_Sliding UMETA(DisplayName = "Sliding"), + ECMM_Max UMETA(Hidden), +}; diff --git a/Source/LegumeMix/Public/Player/LMCameraManager.h b/Source/LegumeMix/Public/Player/LMCameraManager.h new file mode 100644 index 0000000..f362663 --- /dev/null +++ b/Source/LegumeMix/Public/Player/LMCameraManager.h @@ -0,0 +1,26 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Camera/PlayerCameraManager.h" +#include "LMCameraManager.generated.h" + +/** + * + */ +UCLASS() +class LEGUMEMIX_API ALMCameraManager : public APlayerCameraManager +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Legumix, meta=(AllowPrivateAccess=true)) + float CrouchBlendDuration = 0.5f; + float CrouchBlendTime = 0.f; + +public: + ALMCameraManager(); + + virtual void UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime) override; + +}; \ No newline at end of file diff --git a/Source/LegumeMix/Public/Player/LMMovementComponent.h b/Source/LegumeMix/Public/Player/LMMovementComponent.h new file mode 100644 index 0000000..5382b96 --- /dev/null +++ b/Source/LegumeMix/Public/Player/LMMovementComponent.h @@ -0,0 +1,94 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "LMCustomMovementModes.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "LMMovementComponent.generated.h" + + +class ALMPlayer; + +// See https://www.youtube.com/playlist?list=PLXJlkahwiwPmeABEhjwIALvxRSZkzoQpk +UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) +class LEGUMEMIX_API ULMMovementComponent : public UCharacterMovementComponent +{ + GENERATED_BODY() + + class FSavedMove_Legumix : public FSavedMove_Character + { + public: + uint8 Saved_bPrevWantsToCrouch:1; + + FSavedMove_Legumix(); + + virtual bool CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InCharacter, float MaxDelta) const override; + virtual void Clear() override; + virtual uint8 GetCompressedFlags() const override; + virtual void SetMoveFor(ACharacter* C, float InDeltaTime, FVector const& NewAccel, class FNetworkPredictionData_Client_Character& ClientData) override; + virtual void PrepMoveFor(ACharacter* C) override; + }; + + class FNetworkPredictionData_Client_Legumix : public FNetworkPredictionData_Client_Character + { + public: + FNetworkPredictionData_Client_Legumix(const UCharacterMovementComponent& ClientMovement); + typedef FNetworkPredictionData_Client_Character Super; + virtual FSavedMovePtr AllocateNewMove() override; + }; + + bool Safe_bPrevWantsToCrouch = false; + +public: + ULMMovementComponent(); + +protected: + virtual void InitializeComponent() override; + +public: + virtual FNetworkPredictionData_Client* GetPredictionData_Client() const override; + virtual bool IsMovingOnGround() const override; + virtual bool CanCrouchInCurrentState() const override; + +protected: + virtual void UpdateFromCompressedFlags(uint8 Flags) override; + +public: + virtual void UpdateCharacterStateBeforeMovement(float DeltaSeconds) override; + +protected: + virtual void OnMovementUpdated(float DeltaSeconds, const FVector& OldLocation, const FVector& OldVelocity) override; + virtual void PhysCustom(float DeltaTime, int32 Iterations) override; + + +public: + UFUNCTION(BlueprintPure) + bool IsCustomMovementMode(ECustomMovementModes InCustomMovementMode) const; + +public: + UPROPERTY(Transient) + TObjectPtr PlayerCharacterOwner; + + // Minimum speed require to slide + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Legumix|Slide") + float SlideMinSpeed = 350.f; + // Boost in velocity when entering a slide + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Legumix|Slide") + float SlideEnterImpulse = 500.f; + // Amount of force applied to the player to force it on the ground, also affects the speed of a slide on a slope. + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Legumix|Slide") + float SlideGravityForce = 5000.f; + // How fast you loose velocity as you slide. + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Legumix|Slide") + float SlideFriction = 1.3f; + +private: + void EnterSlide(); + void ExitSlide(); + void PhysSlide(float DeltaTime, int32 Iterations); + bool GetSlideSurface(FHitResult& Hit) const; + + UFUNCTION(BlueprintCallable, Category="Legumix|Slide") + void CrouchPressed(); +}; diff --git a/Source/LegumeMix/Public/Player/LMPlayer.h b/Source/LegumeMix/Public/Player/LMPlayer.h index 875e418..deaf8e3 100644 --- a/Source/LegumeMix/Public/Player/LMPlayer.h +++ b/Source/LegumeMix/Public/Player/LMPlayer.h @@ -9,6 +9,7 @@ #include "GameFramework/Character.h" #include "LMPlayer.generated.h" +class ULMMovementComponent; class UCameraComponent; class ULMWeaponManager; class ULMHealthComponent; @@ -22,9 +23,15 @@ class LEGUMEMIX_API ALMPlayer : public ACharacter { GENERATED_BODY() +protected: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Movement) + TObjectPtr LegumixMovementComponent; + public: // Sets default values for this character's properties - ALMPlayer(); + ALMPlayer(const FObjectInitializer& ObjectInitializer); + + ULMMovementComponent* GetLegumixMovementComponent() { return LegumixMovementComponent; } UFUNCTION(BlueprintCallable) bool PickUpAmmo(ALMAmmo* Ammo); @@ -92,6 +99,8 @@ public: UFUNCTION(BlueprintNativeEvent, BlueprintCallable) void OnPause(bool Paused); + + FCollisionQueryParams GetIgnoreCharacterParams(); public: UFUNCTION(BlueprintImplementableEvent) @@ -189,8 +198,6 @@ private: UFUNCTION(BlueprintCallable, Category=PostProcess) void SetPlayerViewOcclusionPercent(); - - private: #if WITH_EDITORONLY_DATA /** If set, bullet debug will be drawn. */