Semi-functional gravity direction changing
This commit is contained in:
parent
f7a263d2c2
commit
01f88914cd
BIN
GravityStomp/Content/Characters/BP_GSCharacter.uasset
(Stored with Git LFS)
BIN
GravityStomp/Content/Characters/BP_GSCharacter.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
GravityStomp/Content/Input/Actions/IA_ChangeGravity.uasset
(Stored with Git LFS)
Normal file
BIN
GravityStomp/Content/Input/Actions/IA_ChangeGravity.uasset
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
GravityStomp/Content/Input/IMC_Default.uasset
(Stored with Git LFS)
BIN
GravityStomp/Content/Input/IMC_Default.uasset
(Stored with Git LFS)
Binary file not shown.
@ -7,7 +7,10 @@
|
|||||||
{
|
{
|
||||||
"Name": "GravityStompGame",
|
"Name": "GravityStompGame",
|
||||||
"Type": "Runtime",
|
"Type": "Runtime",
|
||||||
"LoadingPhase": "Default"
|
"LoadingPhase": "Default",
|
||||||
|
"AdditionalDependencies": [
|
||||||
|
"Engine"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name": "GravityStompEditor",
|
"Name": "GravityStompEditor",
|
||||||
|
@ -9,13 +9,15 @@
|
|||||||
#include "GameFramework/SpringArmComponent.h"
|
#include "GameFramework/SpringArmComponent.h"
|
||||||
#include "EnhancedInputComponent.h"
|
#include "EnhancedInputComponent.h"
|
||||||
#include "EnhancedInputSubsystems.h"
|
#include "EnhancedInputSubsystems.h"
|
||||||
|
#include "GSCharacterMovementComponent.h"
|
||||||
#include "Kismet/KismetMathLibrary.h"
|
#include "Kismet/KismetMathLibrary.h"
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// AGravityStompCharacter
|
// AGravityStompCharacter
|
||||||
|
|
||||||
AGSCharacter::AGSCharacter()
|
AGSCharacter::AGSCharacter(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer.SetDefaultSubobjectClass<UGSCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
|
||||||
{
|
{
|
||||||
// Set size for collision capsule
|
// Set size for collision capsule
|
||||||
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
|
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
|
||||||
@ -80,6 +82,7 @@ void AGSCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputC
|
|||||||
{
|
{
|
||||||
//Moving
|
//Moving
|
||||||
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AGSCharacter::Move);
|
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AGSCharacter::Move);
|
||||||
|
EnhancedInputComponent->BindAction(ChangeGravityAction, ETriggerEvent::Triggered, this, &AGSCharacter::ChangeGravityDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -94,3 +97,11 @@ void AGSCharacter::Move(const FInputActionValue& Value)
|
|||||||
AddMovementInput(FVector::UpVector, MovementVector.Y);
|
AddMovementInput(FVector::UpVector, MovementVector.Y);
|
||||||
AddMovementInput(FVector::RightVector, MovementVector.X);
|
AddMovementInput(FVector::RightVector, MovementVector.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AGSCharacter::ChangeGravityDirection(const FInputActionValue& Value)
|
||||||
|
{
|
||||||
|
FVector2D GravityDirection = Value.Get<FVector2D>();
|
||||||
|
FVector NewCharacterUpDirection(0.0f, -GravityDirection.X, -GravityDirection.Y);
|
||||||
|
|
||||||
|
GetCharacterMovement<UGSCharacterMovementComponent>()->SetCharacterUpDirection(NewCharacterUpDirection);
|
||||||
|
}
|
||||||
|
@ -27,16 +27,22 @@ class AGSCharacter : public ACharacter
|
|||||||
|
|
||||||
/** Move Input Action */
|
/** Move Input Action */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
|
||||||
class UInputAction* MoveAction;;
|
class UInputAction* MoveAction;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
|
||||||
|
class UInputAction* ChangeGravityAction;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AGSCharacter();
|
AGSCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/** Called for movement input */
|
/** Called for movement input */
|
||||||
void Move(const FInputActionValue& Value);
|
void Move(const FInputActionValue& Value);
|
||||||
|
|
||||||
|
/** Called when the player changes the gravity direction */
|
||||||
|
void ChangeGravityDirection(const FInputActionValue& Value);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// APawn interface
|
// APawn interface
|
||||||
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||||
|
@ -0,0 +1,355 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#include "Character/GSCharacterMovementComponent.h"
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
|
||||||
|
// These are defined in CharacterMovementComponent.cpp and inaccessible here. Just copy and paste too make the PhysFalling work
|
||||||
|
namespace CharacterMovementConstants
|
||||||
|
{
|
||||||
|
// MAGIC NUMBERS
|
||||||
|
const float VERTICAL_SLOPE_NORMAL_Z = 0.001f; // Slope is vertical if Abs(Normal.Z) <= this threshold. Accounts for precision problems that sometimes angle normals slightly off horizontal for vertical surface.
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace CharacterMovementCVars
|
||||||
|
{
|
||||||
|
int32 ForceJumpPeakSubstep = 1;
|
||||||
|
FAutoConsoleVariableRef CVarForceJumpPeakSubstep(
|
||||||
|
TEXT("gs.ForceJumpPeakSubstep"),
|
||||||
|
ForceJumpPeakSubstep,
|
||||||
|
TEXT("If 1, force a jump substep to always reach the peak position of a jump, which can often be cut off as framerate lowers."),
|
||||||
|
ECVF_Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
UGSCharacterMovementComponent::UGSCharacterMovementComponent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGSCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations)
|
||||||
|
{
|
||||||
|
if (deltaTime < MIN_TICK_TIME)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector FallAcceleration = GetFallingLateralAcceleration(deltaTime);
|
||||||
|
FallAcceleration.Z = 0.f;
|
||||||
|
const bool bHasLimitedAirControl = ShouldLimitAirControl(deltaTime, FallAcceleration);
|
||||||
|
|
||||||
|
float remainingTime = deltaTime;
|
||||||
|
while( (remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) )
|
||||||
|
{
|
||||||
|
Iterations++;
|
||||||
|
float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
|
||||||
|
remainingTime -= timeTick;
|
||||||
|
|
||||||
|
const FVector OldLocation = UpdatedComponent->GetComponentLocation();
|
||||||
|
const FQuat PawnRotation = UpdatedComponent->GetComponentQuat();
|
||||||
|
bJustTeleported = false;
|
||||||
|
|
||||||
|
const FVector OldVelocityWithRootMotion = Velocity;
|
||||||
|
|
||||||
|
RestorePreAdditiveRootMotionVelocity();
|
||||||
|
|
||||||
|
const FVector OldVelocity = Velocity;
|
||||||
|
|
||||||
|
// Apply input
|
||||||
|
const float MaxDecel = GetMaxBrakingDeceleration();
|
||||||
|
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
|
||||||
|
{
|
||||||
|
// Compute Velocity
|
||||||
|
{
|
||||||
|
// Acceleration = FallAcceleration for CalcVelocity(), but we restore it after using it.
|
||||||
|
TGuardValue<FVector> RestoreAcceleration(Acceleration, FallAcceleration);
|
||||||
|
Velocity.Z = 0.f;
|
||||||
|
CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel);
|
||||||
|
Velocity.Z = OldVelocity.Z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute current gravity
|
||||||
|
const FVector Gravity = CharacterUpDirection * GetGravityZ();
|
||||||
|
float GravityTime = timeTick;
|
||||||
|
|
||||||
|
// If jump is providing force, gravity may be affected.
|
||||||
|
bool bEndingJumpForce = false;
|
||||||
|
if (CharacterOwner->JumpForceTimeRemaining > 0.0f)
|
||||||
|
{
|
||||||
|
// Consume some of the force time. Only the remaining time (if any) is affected by gravity when bApplyGravityWhileJumping=false.
|
||||||
|
const float JumpForceTime = FMath::Min(CharacterOwner->JumpForceTimeRemaining, timeTick);
|
||||||
|
GravityTime = bApplyGravityWhileJumping ? timeTick : FMath::Max(0.0f, timeTick - JumpForceTime);
|
||||||
|
|
||||||
|
// Update Character state
|
||||||
|
CharacterOwner->JumpForceTimeRemaining -= JumpForceTime;
|
||||||
|
if (CharacterOwner->JumpForceTimeRemaining <= 0.0f)
|
||||||
|
{
|
||||||
|
CharacterOwner->ResetJumpState();
|
||||||
|
bEndingJumpForce = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply gravity
|
||||||
|
Velocity = NewFallVelocity(Velocity, Gravity, GravityTime);
|
||||||
|
|
||||||
|
//UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) OldVelocityWithRootMotion=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *OldVelocityWithRootMotion.ToString(), *Velocity.ToString());
|
||||||
|
ApplyRootMotionToVelocity(timeTick);
|
||||||
|
DecayFormerBaseVelocity(timeTick);
|
||||||
|
|
||||||
|
// See if we need to sub-step to exactly reach the apex. This is important for avoiding "cutting off the top" of the trajectory as framerate varies.
|
||||||
|
if (CharacterMovementCVars::ForceJumpPeakSubstep && OldVelocityWithRootMotion.Z > 0.f && Velocity.Z <= 0.f && NumJumpApexAttempts < MaxJumpApexAttemptsPerSimulation)
|
||||||
|
{
|
||||||
|
const FVector DerivedAccel = (Velocity - OldVelocityWithRootMotion) / timeTick;
|
||||||
|
if (!FMath::IsNearlyZero(DerivedAccel.Z))
|
||||||
|
{
|
||||||
|
const float TimeToApex = -OldVelocityWithRootMotion.Z / DerivedAccel.Z;
|
||||||
|
|
||||||
|
// The time-to-apex calculation should be precise, and we want to avoid adding a substep when we are basically already at the apex from the previous iteration's work.
|
||||||
|
const float ApexTimeMinimum = 0.0001f;
|
||||||
|
if (TimeToApex >= ApexTimeMinimum && TimeToApex < timeTick)
|
||||||
|
{
|
||||||
|
const FVector ApexVelocity = OldVelocityWithRootMotion + (DerivedAccel * TimeToApex);
|
||||||
|
Velocity = ApexVelocity;
|
||||||
|
Velocity.Z = 0.f; // Should be nearly zero anyway, but this makes apex notifications consistent.
|
||||||
|
|
||||||
|
// We only want to move the amount of time it takes to reach the apex, and refund the unused time for next iteration.
|
||||||
|
const float TimeToRefund = (timeTick - TimeToApex);
|
||||||
|
|
||||||
|
remainingTime += TimeToRefund;
|
||||||
|
timeTick = TimeToApex;
|
||||||
|
Iterations--;
|
||||||
|
NumJumpApexAttempts++;
|
||||||
|
|
||||||
|
// Refund time to any active Root Motion Sources as well
|
||||||
|
for (TSharedPtr<FRootMotionSource> RootMotionSource : CurrentRootMotion.RootMotionSources)
|
||||||
|
{
|
||||||
|
const float RewoundRMSTime = FMath::Max(0.0f, RootMotionSource->GetTime() - TimeToRefund);
|
||||||
|
RootMotionSource->SetTime(RewoundRMSTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bNotifyApex && (Velocity.Z < 0.f))
|
||||||
|
{
|
||||||
|
// Just passed jump apex since now going down
|
||||||
|
bNotifyApex = false;
|
||||||
|
NotifyJumpApex();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute change in position (using midpoint integration method).
|
||||||
|
FVector Adjusted = 0.5f * (OldVelocityWithRootMotion + Velocity) * timeTick;
|
||||||
|
|
||||||
|
// Special handling if ending the jump force where we didn't apply gravity during the jump.
|
||||||
|
if (bEndingJumpForce && !bApplyGravityWhileJumping)
|
||||||
|
{
|
||||||
|
// We had a portion of the time at constant speed then a portion with acceleration due to gravity.
|
||||||
|
// Account for that here with a more correct change in position.
|
||||||
|
const float NonGravityTime = FMath::Max(0.f, timeTick - GravityTime);
|
||||||
|
Adjusted = (OldVelocityWithRootMotion * NonGravityTime) + (0.5f*(OldVelocityWithRootMotion + Velocity) * GravityTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move
|
||||||
|
FHitResult Hit(1.f);
|
||||||
|
SafeMoveUpdatedComponent( Adjusted, PawnRotation, true, Hit);
|
||||||
|
|
||||||
|
if (!HasValidData())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LastMoveTimeSlice = timeTick;
|
||||||
|
float subTimeTickRemaining = timeTick * (1.f - Hit.Time);
|
||||||
|
|
||||||
|
if ( IsSwimming() ) //just entered water
|
||||||
|
{
|
||||||
|
remainingTime += subTimeTickRemaining;
|
||||||
|
StartSwimming(OldLocation, OldVelocity, timeTick, remainingTime, Iterations);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ( Hit.bBlockingHit )
|
||||||
|
{
|
||||||
|
if (IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit))
|
||||||
|
{
|
||||||
|
remainingTime += subTimeTickRemaining;
|
||||||
|
ProcessLanded(Hit, remainingTime, Iterations);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Compute impact deflection based on final velocity, not integration step.
|
||||||
|
// This allows us to compute a new velocity from the deflected vector, and ensures the full gravity effect is included in the slide result.
|
||||||
|
Adjusted = Velocity * timeTick;
|
||||||
|
|
||||||
|
// See if we can convert a normally invalid landing spot (based on the hit result) to a usable one.
|
||||||
|
if (!Hit.bStartPenetrating && ShouldCheckForValidLandingSpot(timeTick, Adjusted, Hit))
|
||||||
|
{
|
||||||
|
const FVector PawnLocation = UpdatedComponent->GetComponentLocation();
|
||||||
|
FFindFloorResult FloorResult;
|
||||||
|
FindFloor(PawnLocation, FloorResult, false);
|
||||||
|
if (FloorResult.IsWalkableFloor() && IsValidLandingSpot(PawnLocation, FloorResult.HitResult))
|
||||||
|
{
|
||||||
|
remainingTime += subTimeTickRemaining;
|
||||||
|
ProcessLanded(FloorResult.HitResult, remainingTime, Iterations);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleImpact(Hit, LastMoveTimeSlice, Adjusted);
|
||||||
|
|
||||||
|
// If we've changed physics mode, abort.
|
||||||
|
if (!HasValidData() || !IsFalling())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit air control based on what we hit.
|
||||||
|
// We moved to the impact point using air control, but may want to deflect from there based on a limited air control acceleration.
|
||||||
|
FVector VelocityNoAirControl = OldVelocity;
|
||||||
|
FVector AirControlAccel = Acceleration;
|
||||||
|
if (bHasLimitedAirControl)
|
||||||
|
{
|
||||||
|
// Compute VelocityNoAirControl
|
||||||
|
{
|
||||||
|
// Find velocity *without* acceleration.
|
||||||
|
TGuardValue<FVector> RestoreAcceleration(Acceleration, FVector::ZeroVector);
|
||||||
|
TGuardValue<FVector> RestoreVelocity(Velocity, OldVelocity);
|
||||||
|
Velocity.Z = 0.f;
|
||||||
|
CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel);
|
||||||
|
VelocityNoAirControl = FVector(Velocity.X, Velocity.Y, OldVelocity.Z);
|
||||||
|
VelocityNoAirControl = NewFallVelocity(VelocityNoAirControl, Gravity, GravityTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool bCheckLandingSpot = false; // we already checked above.
|
||||||
|
AirControlAccel = (Velocity - VelocityNoAirControl) / timeTick;
|
||||||
|
const FVector AirControlDeltaV = LimitAirControl(LastMoveTimeSlice, AirControlAccel, Hit, bCheckLandingSpot) * LastMoveTimeSlice;
|
||||||
|
Adjusted = (VelocityNoAirControl + AirControlDeltaV) * LastMoveTimeSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector OldHitNormal = Hit.Normal;
|
||||||
|
const FVector OldHitImpactNormal = Hit.ImpactNormal;
|
||||||
|
FVector Delta = ComputeSlideVector(Adjusted, 1.f - Hit.Time, OldHitNormal, Hit);
|
||||||
|
|
||||||
|
// Compute velocity after deflection (only gravity component for RootMotion)
|
||||||
|
const UPrimitiveComponent* HitComponent = Hit.GetComponent();
|
||||||
|
if (/*CharacterMovementCVars::UseTargetVelocityOnImpact &&*/ !Velocity.IsNearlyZero() && MovementBaseUtility::IsSimulatedBase(HitComponent))
|
||||||
|
{
|
||||||
|
const FVector ContactVelocity = MovementBaseUtility::GetMovementBaseVelocity(HitComponent, NAME_None) + MovementBaseUtility::GetMovementBaseTangentialVelocity(HitComponent, NAME_None, Hit.ImpactPoint);
|
||||||
|
const FVector NewVelocity = Velocity - Hit.ImpactNormal * FVector::DotProduct(Velocity - ContactVelocity, Hit.ImpactNormal);
|
||||||
|
Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity;
|
||||||
|
}
|
||||||
|
else if (subTimeTickRemaining > UE_KINDA_SMALL_NUMBER && !bJustTeleported)
|
||||||
|
{
|
||||||
|
const FVector NewVelocity = (Delta / subTimeTickRemaining);
|
||||||
|
Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subTimeTickRemaining > UE_KINDA_SMALL_NUMBER && (Delta | Adjusted) > 0.f)
|
||||||
|
{
|
||||||
|
// Move in deflected direction.
|
||||||
|
SafeMoveUpdatedComponent( Delta, PawnRotation, true, Hit);
|
||||||
|
|
||||||
|
if (Hit.bBlockingHit)
|
||||||
|
{
|
||||||
|
// hit second wall
|
||||||
|
LastMoveTimeSlice = subTimeTickRemaining;
|
||||||
|
subTimeTickRemaining = subTimeTickRemaining * (1.f - Hit.Time);
|
||||||
|
|
||||||
|
if (IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit))
|
||||||
|
{
|
||||||
|
remainingTime += subTimeTickRemaining;
|
||||||
|
ProcessLanded(Hit, remainingTime, Iterations);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleImpact(Hit, LastMoveTimeSlice, Delta);
|
||||||
|
|
||||||
|
// If we've changed physics mode, abort.
|
||||||
|
if (!HasValidData() || !IsFalling())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act as if there was no air control on the last move when computing new deflection.
|
||||||
|
if (bHasLimitedAirControl && Hit.Normal.Z > CharacterMovementConstants::VERTICAL_SLOPE_NORMAL_Z)
|
||||||
|
{
|
||||||
|
const FVector LastMoveNoAirControl = VelocityNoAirControl * LastMoveTimeSlice;
|
||||||
|
Delta = ComputeSlideVector(LastMoveNoAirControl, 1.f, OldHitNormal, Hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector PreTwoWallDelta = Delta;
|
||||||
|
TwoWallAdjust(Delta, Hit, OldHitNormal);
|
||||||
|
|
||||||
|
// Limit air control, but allow a slide along the second wall.
|
||||||
|
if (bHasLimitedAirControl)
|
||||||
|
{
|
||||||
|
const bool bCheckLandingSpot = false; // we already checked above.
|
||||||
|
const FVector AirControlDeltaV = LimitAirControl(subTimeTickRemaining, AirControlAccel, Hit, bCheckLandingSpot) * subTimeTickRemaining;
|
||||||
|
|
||||||
|
// Only allow if not back in to first wall
|
||||||
|
if (FVector::DotProduct(AirControlDeltaV, OldHitNormal) > 0.f)
|
||||||
|
{
|
||||||
|
Delta += (AirControlDeltaV * subTimeTickRemaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute velocity after deflection (only gravity component for RootMotion)
|
||||||
|
if (subTimeTickRemaining > UE_KINDA_SMALL_NUMBER && !bJustTeleported)
|
||||||
|
{
|
||||||
|
const FVector NewVelocity = (Delta / subTimeTickRemaining);
|
||||||
|
Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bDitch=true means that pawn is straddling two slopes, neither of which it can stand on
|
||||||
|
bool bDitch = ( (OldHitImpactNormal.Z > 0.f) && (Hit.ImpactNormal.Z > 0.f) && (FMath::Abs(Delta.Z) <= UE_KINDA_SMALL_NUMBER) && ((Hit.ImpactNormal | OldHitImpactNormal) < 0.f) );
|
||||||
|
SafeMoveUpdatedComponent( Delta, PawnRotation, true, Hit);
|
||||||
|
if ( Hit.Time == 0.f )
|
||||||
|
{
|
||||||
|
// if we are stuck then try to side step
|
||||||
|
FVector SideDelta = (OldHitNormal + Hit.ImpactNormal).GetSafeNormal2D();
|
||||||
|
if ( SideDelta.IsNearlyZero() )
|
||||||
|
{
|
||||||
|
SideDelta = FVector(OldHitNormal.Y, -OldHitNormal.X, 0).GetSafeNormal();
|
||||||
|
}
|
||||||
|
SafeMoveUpdatedComponent( SideDelta, PawnRotation, true, Hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( bDitch || IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit) || Hit.Time == 0.f )
|
||||||
|
{
|
||||||
|
remainingTime = 0.f;
|
||||||
|
ProcessLanded(Hit, remainingTime, Iterations);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (GetPerchRadiusThreshold() > 0.f && Hit.Time == 1.f && OldHitImpactNormal.Z >= GetWalkableFloorZ())
|
||||||
|
{
|
||||||
|
// We might be in a virtual 'ditch' within our perch radius. This is rare.
|
||||||
|
const FVector PawnLocation = UpdatedComponent->GetComponentLocation();
|
||||||
|
const float ZMovedDist = FMath::Abs(PawnLocation.Z - OldLocation.Z);
|
||||||
|
const float MovedDist2DSq = (PawnLocation - OldLocation).SizeSquared2D();
|
||||||
|
if (ZMovedDist <= 0.2f * timeTick && MovedDist2DSq <= 4.f * timeTick)
|
||||||
|
{
|
||||||
|
Velocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f);
|
||||||
|
Velocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f);
|
||||||
|
Velocity.Z = FMath::Max<float>(JumpZVelocity * 0.25f, 1.f);
|
||||||
|
Delta = Velocity * timeTick;
|
||||||
|
SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Velocity.SizeSquared2D() <= UE_KINDA_SMALL_NUMBER * 10.f)
|
||||||
|
{
|
||||||
|
Velocity.X = 0.f;
|
||||||
|
Velocity.Y = 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGSCharacterMovementComponent::SetCharacterUpDirection(FVector NewUpDirection)
|
||||||
|
{
|
||||||
|
NewUpDirection.Normalize();
|
||||||
|
CharacterUpDirection = NewUpDirection;
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
|
|
||||||
|
#include "GSCharacterMovementComponent.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclass of CharacterMovementComponent which allows for a custom gravity direction to be applied to the character while falling
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class GRAVITYSTOMPGAME_API UGSCharacterMovementComponent : public UCharacterMovementComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UGSCharacterMovementComponent();
|
||||||
|
|
||||||
|
virtual void PhysFalling(float deltaTime, int32 Iterations) override;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
void SetCharacterUpDirection(FVector NewUpDirection);
|
||||||
|
|
||||||
|
private:
|
||||||
|
FVector CharacterUpDirection = FVector::UpVector;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user