Fix sliding issue when standing on "vertical" walls
This commit is contained in:
parent
2bda0ce351
commit
cf85b7f916
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/Maps/Debug/Test.umap
(Stored with Git LFS)
BIN
GravityStomp/Content/Maps/Debug/Test.umap
(Stored with Git LFS)
Binary file not shown.
@ -22,6 +22,13 @@ namespace CharacterMovementCVars
|
||||
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);
|
||||
|
||||
static bool bAddFormerBaseVelocityToRootMotionOverrideWhenFalling = true;
|
||||
FAutoConsoleVariableRef CVarAddFormerBaseVelocityToRootMotionOverrideWhenFalling(
|
||||
TEXT("gs.AddFormerBaseVelocityToRootMotionOverrideWhenFalling"),
|
||||
bAddFormerBaseVelocityToRootMotionOverrideWhenFalling,
|
||||
TEXT("To avoid sudden velocity changes when a root motion source moves the pawn from a moving base to free fall, this CVar will enable the FormerBaseVelocityDecayHalfLife property on CharacterMovementComponent."),
|
||||
ECVF_Default);
|
||||
}
|
||||
|
||||
|
||||
@ -631,9 +638,619 @@ bool UGSCharacterMovementComponent::FloorSweepTest(FHitResult& OutHit, const FVe
|
||||
return bBlockingHit;
|
||||
}
|
||||
|
||||
bool UGSCharacterMovementComponent::IsCharacterUpAlignedToWorldUp() const
|
||||
{
|
||||
return FMath::Abs(CharacterUpDirection.Dot(FVector::UpVector)) > UE_KINDA_SMALL_NUMBER;
|
||||
}
|
||||
|
||||
|
||||
void UGSCharacterMovementComponent::SetCharacterUpDirection(FVector NewUpDirection)
|
||||
{
|
||||
NewUpDirection.Normalize();
|
||||
CharacterUpDirection = NewUpDirection;
|
||||
}
|
||||
|
||||
void UGSCharacterMovementComponent::OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode)
|
||||
{
|
||||
if (!HasValidData())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update collision settings if needed
|
||||
if (MovementMode == MOVE_NavWalking)
|
||||
{
|
||||
// Reset cached nav location used by NavWalking
|
||||
CachedNavLocation = FNavLocation();
|
||||
|
||||
SetGroundMovementMode(MovementMode);
|
||||
// Walking is either only X or Z depending on the character's up direction
|
||||
if(IsCharacterUpAlignedToWorldUp())
|
||||
{
|
||||
Velocity.Z = 0.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Velocity.Y = 0.f;
|
||||
}
|
||||
SetNavWalkingPhysics(true);
|
||||
}
|
||||
else if (PreviousMovementMode == MOVE_NavWalking)
|
||||
{
|
||||
if (MovementMode == DefaultLandMovementMode || IsWalking())
|
||||
{
|
||||
const bool bSucceeded = TryToLeaveNavWalking();
|
||||
if (!bSucceeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetNavWalkingPhysics(false);
|
||||
}
|
||||
}
|
||||
|
||||
// React to changes in the movement mode.
|
||||
if (MovementMode == MOVE_Walking)
|
||||
{
|
||||
// Walking is either only X or Z depending on the character's up direction
|
||||
if(IsCharacterUpAlignedToWorldUp())
|
||||
{
|
||||
Velocity.Z = 0.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Velocity.Y = 0.f;
|
||||
}
|
||||
bCrouchMaintainsBaseLocation = true;
|
||||
SetGroundMovementMode(MovementMode);
|
||||
|
||||
// make sure we update our new floor/base on initial entry of the walking physics
|
||||
FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false);
|
||||
AdjustFloorHeight();
|
||||
SetBaseFromFloor(CurrentFloor);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentFloor.Clear();
|
||||
bCrouchMaintainsBaseLocation = false;
|
||||
|
||||
if (MovementMode == MOVE_Falling)
|
||||
{
|
||||
DecayingFormerBaseVelocity = GetImpartedMovementBaseVelocity();
|
||||
Velocity += DecayingFormerBaseVelocity;
|
||||
if (bMovementInProgress && CurrentRootMotion.HasAdditiveVelocity())
|
||||
{
|
||||
// If we leave a base during movement and we have additive root motion, we need to add the imparted velocity so that it retains it next tick
|
||||
CurrentRootMotion.LastPreAdditiveVelocity += DecayingFormerBaseVelocity;
|
||||
}
|
||||
if (!CharacterMovementCVars::bAddFormerBaseVelocityToRootMotionOverrideWhenFalling || FormerBaseVelocityDecayHalfLife == 0.f)
|
||||
{
|
||||
DecayingFormerBaseVelocity = FVector::ZeroVector;
|
||||
}
|
||||
CharacterOwner->Falling();
|
||||
}
|
||||
|
||||
SetBase(NULL);
|
||||
|
||||
if (MovementMode == MOVE_None)
|
||||
{
|
||||
// Kill velocity and clear queued up events
|
||||
StopMovementKeepPathing();
|
||||
CharacterOwner->ResetJumpState();
|
||||
ClearAccumulatedForces();
|
||||
}
|
||||
}
|
||||
|
||||
if (MovementMode == MOVE_Falling && PreviousMovementMode != MOVE_Falling)
|
||||
{
|
||||
IPathFollowingAgentInterface* PFAgent = GetPathFollowingAgent();
|
||||
if (PFAgent)
|
||||
{
|
||||
PFAgent->OnStartedFalling();
|
||||
}
|
||||
}
|
||||
|
||||
CharacterOwner->OnMovementModeChanged(PreviousMovementMode, PreviousCustomMode);
|
||||
ensureMsgf(GetGroundMovementMode() == MOVE_Walking || GetGroundMovementMode() == MOVE_NavWalking, TEXT("Invalid GroundMovementMode %d. MovementMode: %d, PreviousMovementMode: %d"), GetGroundMovementMode(), MovementMode.GetValue(), PreviousMovementMode);
|
||||
}
|
||||
|
||||
void UGSCharacterMovementComponent::PhysWalking(float deltaTime, int32 Iterations)
|
||||
{
|
||||
if (deltaTime < MIN_TICK_TIME)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CharacterOwner || (!CharacterOwner->Controller && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy)))
|
||||
{
|
||||
Acceleration = FVector::ZeroVector;
|
||||
Velocity = FVector::ZeroVector;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UpdatedComponent->IsQueryCollisionEnabled())
|
||||
{
|
||||
SetMovementMode(MOVE_Walking);
|
||||
return;
|
||||
}
|
||||
|
||||
bJustTeleported = false;
|
||||
bool bCheckedFall = false;
|
||||
bool bTriedLedgeMove = false;
|
||||
float remainingTime = deltaTime;
|
||||
|
||||
// Perform the move
|
||||
while ( (remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller || bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy)) )
|
||||
{
|
||||
Iterations++;
|
||||
bJustTeleported = false;
|
||||
const float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
|
||||
remainingTime -= timeTick;
|
||||
|
||||
// Save current values
|
||||
UPrimitiveComponent * const OldBase = GetMovementBase();
|
||||
const FVector PreviousBaseLocation = (OldBase != NULL) ? OldBase->GetComponentLocation() : FVector::ZeroVector;
|
||||
const FVector OldLocation = UpdatedComponent->GetComponentLocation();
|
||||
const FFindFloorResult OldFloor = CurrentFloor;
|
||||
|
||||
RestorePreAdditiveRootMotionVelocity();
|
||||
|
||||
// Ensure velocity is horizontal.
|
||||
MaintainHorizontalGroundVelocity();
|
||||
const FVector OldVelocity = Velocity;
|
||||
|
||||
if(IsCharacterUpAlignedToWorldUp())
|
||||
{
|
||||
Acceleration.Z = 0.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Acceleration.Y = 0.f;
|
||||
}
|
||||
|
||||
// Apply acceleration
|
||||
if( !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() )
|
||||
{
|
||||
CalcVelocity(timeTick, GroundFriction, false, GetMaxBrakingDeceleration());
|
||||
}
|
||||
|
||||
ApplyRootMotionToVelocity(timeTick);
|
||||
|
||||
if( IsFalling() )
|
||||
{
|
||||
// Root motion could have put us into Falling.
|
||||
// No movement has taken place this movement tick so we pass on full time/past iteration count
|
||||
StartNewPhysics(remainingTime+timeTick, Iterations-1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute move parameters
|
||||
const FVector MoveVelocity = Velocity;
|
||||
const FVector Delta = timeTick * MoveVelocity;
|
||||
const bool bZeroDelta = Delta.IsNearlyZero();
|
||||
FStepDownResult StepDownResult;
|
||||
|
||||
if ( bZeroDelta )
|
||||
{
|
||||
remainingTime = 0.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// try to move forward
|
||||
MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult);
|
||||
|
||||
if ( IsFalling() )
|
||||
{
|
||||
// pawn decided to jump up
|
||||
const float DesiredDist = Delta.Size();
|
||||
if (DesiredDist > UE_KINDA_SMALL_NUMBER)
|
||||
{
|
||||
const float ActualDist = (UpdatedComponent->GetComponentLocation() - OldLocation).Size2D();
|
||||
remainingTime += timeTick * (1.f - FMath::Min(1.f,ActualDist/DesiredDist));
|
||||
}
|
||||
StartNewPhysics(remainingTime,Iterations);
|
||||
return;
|
||||
}
|
||||
else if ( IsSwimming() ) //just entered water
|
||||
{
|
||||
StartSwimming(OldLocation, OldVelocity, timeTick, remainingTime, Iterations);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update floor.
|
||||
// StepUp might have already done it for us.
|
||||
if (StepDownResult.bComputedFloor)
|
||||
{
|
||||
CurrentFloor = StepDownResult.FloorResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL);
|
||||
}
|
||||
|
||||
// check for ledges here
|
||||
const bool bCheckLedges = !CanWalkOffLedges();
|
||||
if ( bCheckLedges && !CurrentFloor.IsWalkableFloor() )
|
||||
{
|
||||
// calculate possible alternate movement
|
||||
const FVector GravDir = FVector(0.f,0.f,-1.f);
|
||||
const FVector NewDelta = bTriedLedgeMove ? FVector::ZeroVector : GetLedgeMove(OldLocation, Delta, GravDir);
|
||||
if ( !NewDelta.IsZero() )
|
||||
{
|
||||
// first revert this move
|
||||
RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, false);
|
||||
|
||||
// avoid repeated ledge moves if the first one fails
|
||||
bTriedLedgeMove = true;
|
||||
|
||||
// Try new movement direction
|
||||
Velocity = NewDelta/timeTick;
|
||||
remainingTime += timeTick;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// see if it is OK to jump
|
||||
// @todo collision : only thing that can be problem is that oldbase has world collision on
|
||||
bool bMustJump = bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase)));
|
||||
if ( (bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
bCheckedFall = true;
|
||||
|
||||
// revert this move
|
||||
RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, true);
|
||||
remainingTime = 0.f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Validate the floor check
|
||||
if (CurrentFloor.IsWalkableFloor())
|
||||
{
|
||||
if (ShouldCatchAir(OldFloor, CurrentFloor))
|
||||
{
|
||||
HandleWalkingOffLedge(OldFloor.HitResult.ImpactNormal, OldFloor.HitResult.Normal, OldLocation, timeTick);
|
||||
if (IsMovingOnGround())
|
||||
{
|
||||
// If still walking, then fall. If not, assume the user set a different mode they want to keep.
|
||||
StartFalling(Iterations, remainingTime, timeTick, Delta, OldLocation);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AdjustFloorHeight();
|
||||
SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName);
|
||||
}
|
||||
else if (CurrentFloor.HitResult.bStartPenetrating && remainingTime <= 0.f)
|
||||
{
|
||||
// The floor check failed because it started in penetration
|
||||
// We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor.
|
||||
FHitResult Hit(CurrentFloor.HitResult);
|
||||
Hit.TraceEnd = Hit.TraceStart + CharacterUpDirection * MAX_FLOOR_DIST;
|
||||
const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit);
|
||||
ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat());
|
||||
bForceNextFloorCheck = true;
|
||||
}
|
||||
|
||||
// check if just entered water
|
||||
if ( IsSwimming() )
|
||||
{
|
||||
StartSwimming(OldLocation, Velocity, timeTick, remainingTime, Iterations);
|
||||
return;
|
||||
}
|
||||
|
||||
// See if we need to start falling.
|
||||
if (!CurrentFloor.IsWalkableFloor() && !CurrentFloor.HitResult.bStartPenetrating)
|
||||
{
|
||||
const bool bMustJump = bJustTeleported || bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase)));
|
||||
if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
bCheckedFall = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Allow overlap events and such to change physics state and velocity
|
||||
if (IsMovingOnGround())
|
||||
{
|
||||
// Make velocity reflect actual move
|
||||
if( !bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && timeTick >= MIN_TICK_TIME)
|
||||
{
|
||||
// TODO-RootMotionSource: Allow this to happen during partial override Velocity, but only set allowed axes?
|
||||
Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick;
|
||||
MaintainHorizontalGroundVelocity();
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't move at all this iteration then abort (since future iterations will also be stuck).
|
||||
if (UpdatedComponent->GetComponentLocation() == OldLocation)
|
||||
{
|
||||
remainingTime = 0.f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsMovingOnGround())
|
||||
{
|
||||
MaintainHorizontalGroundVelocity();
|
||||
}
|
||||
}
|
||||
|
||||
void UGSCharacterMovementComponent::MoveAlongFloor(const FVector& InVelocity, float DeltaSeconds,
|
||||
FStepDownResult* OutStepDownResult)
|
||||
{
|
||||
if (!CurrentFloor.IsWalkableFloor())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Move along the current floor
|
||||
FVector MovementAxis = CharacterUpDirection.Cross(FVector::ForwardVector).GetAbs();
|
||||
FString DebugAxisMsg = FString::Printf(TEXT("Movement Axis: %s"), *MovementAxis.ToString());
|
||||
GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Cyan, DebugAxisMsg);
|
||||
const FVector Delta = (MovementAxis * InVelocity) * DeltaSeconds;
|
||||
|
||||
FString DebugVelMsg = FString::Printf(TEXT("Move Along Floor Velocity: %s"), *(MovementAxis * InVelocity).ToString());
|
||||
GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Cyan, DebugVelMsg);
|
||||
|
||||
FHitResult Hit(1.f);
|
||||
FVector RampVector = ComputeGroundMovementDelta(Delta, CurrentFloor.HitResult, CurrentFloor.bLineTrace);
|
||||
SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit);
|
||||
float LastMoveTimeSlice = DeltaSeconds;
|
||||
|
||||
if (Hit.bStartPenetrating)
|
||||
{
|
||||
// Allow this hit to be used as an impact we can deflect off, otherwise we do nothing the rest of the update and appear to hitch.
|
||||
HandleImpact(Hit);
|
||||
SlideAlongSurface(Delta, 1.f, Hit.Normal, Hit, true);
|
||||
|
||||
if (Hit.bStartPenetrating)
|
||||
{
|
||||
OnCharacterStuckInGeometry(&Hit);
|
||||
}
|
||||
}
|
||||
else if (Hit.IsValidBlockingHit())
|
||||
{
|
||||
// We impacted something (most likely another ramp, but possibly a barrier).
|
||||
float PercentTimeApplied = Hit.Time;
|
||||
if ((Hit.Time > 0.f) && (IsCharacterUpAlignedToWorldUp() ? Hit.Normal.Z: Hit.Normal.Y > UE_KINDA_SMALL_NUMBER) && IsWalkable(Hit))
|
||||
{
|
||||
// Another walkable ramp.
|
||||
const float InitialPercentRemaining = 1.f - PercentTimeApplied;
|
||||
RampVector = ComputeGroundMovementDelta(Delta * InitialPercentRemaining, Hit, false);
|
||||
LastMoveTimeSlice = InitialPercentRemaining * LastMoveTimeSlice;
|
||||
SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit);
|
||||
|
||||
const float SecondHitPercent = Hit.Time * InitialPercentRemaining;
|
||||
PercentTimeApplied = FMath::Clamp(PercentTimeApplied + SecondHitPercent, 0.f, 1.f);
|
||||
}
|
||||
|
||||
if (Hit.IsValidBlockingHit())
|
||||
{
|
||||
if (CanStepUp(Hit) || (CharacterOwner->GetMovementBase() != nullptr && Hit.HitObjectHandle == CharacterOwner->GetMovementBase()->GetOwner()))
|
||||
{
|
||||
// hit a barrier, try to step up
|
||||
const FVector PreStepUpLocation = UpdatedComponent->GetComponentLocation();
|
||||
const FVector GravDir = -CharacterUpDirection;
|
||||
if (!StepUp(GravDir, Delta * (1.f - PercentTimeApplied), Hit, OutStepDownResult))
|
||||
{
|
||||
//UE_LOG(LogCharacterMovement, Verbose, TEXT("- StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString());
|
||||
HandleImpact(Hit, LastMoveTimeSlice, RampVector);
|
||||
SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//UE_LOG(LogCharacterMovement, Verbose, TEXT("+ StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString());
|
||||
if (!bMaintainHorizontalGroundVelocity)
|
||||
{
|
||||
// Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. Only consider horizontal movement.
|
||||
bJustTeleported = true;
|
||||
const float StepUpTimeSlice = (1.f - PercentTimeApplied) * DeltaSeconds;
|
||||
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && StepUpTimeSlice >= UE_KINDA_SMALL_NUMBER)
|
||||
{
|
||||
Velocity = (UpdatedComponent->GetComponentLocation() - PreStepUpLocation) / StepUpTimeSlice;
|
||||
if(IsCharacterUpAlignedToWorldUp())
|
||||
{
|
||||
Velocity.Z = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Velocity.Y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( Hit.Component.IsValid() && !Hit.Component.Get()->CanCharacterStepUp(CharacterOwner) )
|
||||
{
|
||||
HandleImpact(Hit, LastMoveTimeSlice, RampVector);
|
||||
SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FVector UGSCharacterMovementComponent::ComputeGroundMovementDelta(const FVector& Delta, const FHitResult& RampHit,
|
||||
const bool bHitFromLineTrace) const
|
||||
{
|
||||
/*
|
||||
const FVector FloorNormal = RampHit.ImpactNormal;
|
||||
const FVector ContactNormal = RampHit.Normal;
|
||||
|
||||
if (FloorNormal.Z < (1.f - UE_KINDA_SMALL_NUMBER) && FloorNormal.Z > UE_KINDA_SMALL_NUMBER && ContactNormal.Z > UE_KINDA_SMALL_NUMBER && !bHitFromLineTrace && IsWalkable(RampHit))
|
||||
{
|
||||
// Compute a vector that moves parallel to the surface, by projecting the horizontal movement direction onto the ramp.
|
||||
const float FloorDotDelta = (FloorNormal | Delta);
|
||||
FVector RampMovement(Delta.X, Delta.Y, -FloorDotDelta / FloorNormal.Z);
|
||||
|
||||
if (bMaintainHorizontalGroundVelocity)
|
||||
{
|
||||
return RampMovement;
|
||||
}
|
||||
else
|
||||
{
|
||||
return RampMovement.GetSafeNormal() * Delta.Size();
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
return Delta;
|
||||
}
|
||||
|
||||
|
||||
void UGSCharacterMovementComponent::MaintainHorizontalGroundVelocity()
|
||||
{
|
||||
if (IsCharacterUpAlignedToWorldUp())
|
||||
{
|
||||
if(Velocity.Z != 0.f)
|
||||
{
|
||||
if (bMaintainHorizontalGroundVelocity)
|
||||
{
|
||||
// Ramp movement already maintained the velocity, so we just want to remove the vertical component.
|
||||
Velocity.Z = 0.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rescale velocity to be horizontal but maintain magnitude of last update.
|
||||
Velocity = Velocity.GetSafeNormal2D() * Velocity.Size();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(Velocity.Y != 0.f)
|
||||
{
|
||||
if (bMaintainHorizontalGroundVelocity)
|
||||
{
|
||||
// Ramp movement already maintained the velocity, so we just want to remove the vertical component.
|
||||
Velocity.Y = 0.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
FVector NewVelocity = Velocity.GetSafeNormal();
|
||||
Velocity = FVector(0.0f, NewVelocity.Y, NewVelocity.Z) * Velocity.Size();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float UGSCharacterMovementComponent::SlideAlongSurface(const FVector& Delta, float Time, const FVector& InNormal,
|
||||
FHitResult& Hit, bool bHandleImpact)
|
||||
{
|
||||
if (!Hit.bBlockingHit)
|
||||
{
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
bool IsUp = IsCharacterUpAlignedToWorldUp();
|
||||
|
||||
FVector Normal(InNormal);
|
||||
float ZComp = Normal.Dot(CharacterUpDirection);
|
||||
if (IsMovingOnGround())
|
||||
{
|
||||
// We don't want to be pushed up an unwalkable surface.
|
||||
if (ZComp > 0.f)
|
||||
{
|
||||
if (!IsWalkable(Hit))
|
||||
{
|
||||
FVector NewNormal = Normal.GetSafeNormal();
|
||||
Normal = IsUp ? FVector(NewNormal.X, NewNormal.Y, 0.0f) : FVector(0.0f, NewNormal.Y, NewNormal.Z);
|
||||
}
|
||||
}
|
||||
else if (ZComp < -UE_KINDA_SMALL_NUMBER)
|
||||
{
|
||||
// Don't push down into the floor when the impact is on the upper portion of the capsule.
|
||||
if (CurrentFloor.FloorDist < MIN_FLOOR_DIST && CurrentFloor.bBlockingHit)
|
||||
{
|
||||
const FVector FloorNormal = CurrentFloor.HitResult.Normal;
|
||||
float FloorZComp = FloorNormal.Dot(CharacterUpDirection);
|
||||
const bool bFloorOpposedToMovement = (Delta | FloorNormal) < 0.f && (FloorZComp < 1.f - UE_DELTA);
|
||||
if (bFloorOpposedToMovement)
|
||||
{
|
||||
Normal = FloorNormal;
|
||||
}
|
||||
|
||||
FVector NewNormal = Normal.GetSafeNormal();
|
||||
Normal = IsUp ? FVector(NewNormal.X, NewNormal.Y, 0.0f) : FVector(0.0f, NewNormal.Y, NewNormal.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Super::SlideAlongSurface(Delta, Time, Normal, Hit, bHandleImpact);
|
||||
}
|
||||
|
||||
void UGSCharacterMovementComponent::AdjustFloorHeight()
|
||||
{
|
||||
bool IsUp = IsCharacterUpAlignedToWorldUp();
|
||||
|
||||
// If we have a floor check that hasn't hit anything, don't adjust height.
|
||||
if (!CurrentFloor.IsWalkableFloor())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float OldFloorDist = CurrentFloor.FloorDist;
|
||||
if (CurrentFloor.bLineTrace)
|
||||
{
|
||||
// This would cause us to scale unwalkable walls
|
||||
if (OldFloorDist < MIN_FLOOR_DIST && CurrentFloor.LineDist >= MIN_FLOOR_DIST)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Falling back to a line trace means the sweep was unwalkable (or in penetration). Use the line distance for the vertical adjustment.
|
||||
OldFloorDist = CurrentFloor.LineDist;
|
||||
}
|
||||
}
|
||||
|
||||
// Move up or down to maintain floor height.
|
||||
if (OldFloorDist < MIN_FLOOR_DIST || OldFloorDist > MAX_FLOOR_DIST)
|
||||
{
|
||||
FHitResult AdjustHit(1.f);
|
||||
const float InitialZ = IsUp ? UpdatedComponent->GetComponentLocation().Z : UpdatedComponent->GetComponentLocation().Y;
|
||||
const float AvgFloorDist = (MIN_FLOOR_DIST + MAX_FLOOR_DIST) * 0.5f;
|
||||
const float MoveDist = AvgFloorDist - OldFloorDist;
|
||||
SafeMoveUpdatedComponent(CharacterUpDirection * MoveDist, UpdatedComponent->GetComponentQuat(), true, AdjustHit );
|
||||
|
||||
if (!AdjustHit.IsValidBlockingHit())
|
||||
{
|
||||
CurrentFloor.FloorDist += MoveDist;
|
||||
}
|
||||
else if (MoveDist > 0.f)
|
||||
{
|
||||
const float CurrentZ = IsUp ? UpdatedComponent->GetComponentLocation().Z : UpdatedComponent->GetComponentLocation().Y;
|
||||
CurrentFloor.FloorDist += CurrentZ - InitialZ;
|
||||
}
|
||||
else
|
||||
{
|
||||
checkSlow(MoveDist < 0.f);
|
||||
const float CurrentZ = IsUp ? UpdatedComponent->GetComponentLocation().Z : UpdatedComponent->GetComponentLocation().Y;
|
||||
CurrentFloor.FloorDist = CurrentZ - IsUp ? AdjustHit.Location.Z : AdjustHit.Location.Y;
|
||||
if (IsWalkable(AdjustHit))
|
||||
{
|
||||
CurrentFloor.SetFromSweep(AdjustHit, CurrentFloor.FloorDist, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't recalculate velocity based on this height adjustment, if considering vertical adjustments.
|
||||
// Also avoid it if we moved out of penetration
|
||||
bJustTeleported |= !bMaintainHorizontalGroundVelocity || (OldFloorDist < 0.f);
|
||||
|
||||
// If something caused us to adjust our height (especially a depentration) we should ensure another check next frame or we will keep a stale result.
|
||||
if (CharacterOwner && CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy)
|
||||
{
|
||||
bForceNextFloorCheck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,23 @@ public:
|
||||
void SetCharacterUpDirection(FVector NewUpDirection);
|
||||
|
||||
// UCharacterMovementComponent interface
|
||||
|
||||
virtual void OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) override;
|
||||
|
||||
// walking
|
||||
virtual void PhysWalking(float deltaTime, int32 Iterations) override;
|
||||
|
||||
virtual void MoveAlongFloor(const FVector& InVelocity, float DeltaSeconds, FStepDownResult* OutStepDownResult) override;
|
||||
|
||||
virtual FVector ComputeGroundMovementDelta(const FVector& Delta, const FHitResult& RampHit, const bool bHitFromLineTrace) const override;
|
||||
|
||||
virtual void MaintainHorizontalGroundVelocity() override;
|
||||
|
||||
virtual float SlideAlongSurface(const FVector& Delta, float Time, const FVector& InNormal, FHitResult& Hit, bool bHandleImpact) override;
|
||||
|
||||
virtual void AdjustFloorHeight() override;
|
||||
|
||||
// falling
|
||||
virtual void PhysFalling(float deltaTime, int32 Iterations) override;
|
||||
|
||||
virtual void ComputeFloorDist(const FVector& CapsuleLocation, float LineDistance, float SweepDistance, FFindFloorResult& OutFloorResult, float SweepRadius, const FHitResult* DownwardSweepResult) const override;
|
||||
@ -36,4 +53,6 @@ public:
|
||||
|
||||
private:
|
||||
FVector CharacterUpDirection = FVector::UpVector;
|
||||
|
||||
FORCEINLINE bool IsCharacterUpAlignedToWorldUp() const;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user