bool UCustomCharacterMovementComponent::TryMantle()
{
if (bCanVault) return false;
// Location of the base of the capsule
FVector BaseLocation = UpdatedComponent->GetComponentLocation() + FVector::DownVector * GetCapsuleHalfHeight();
// Forward vector
FVector Forward = UpdatedComponent->GetForwardVector().GetSafeNormal2D();
// Actors to ignore
auto Params = MainCharacter->GetIgnoreCharacterParams();
// Max height reachable by the mantle
float MaxHeight = GetCapsuleHalfHeight() * 2 + MantleReachHeight;
float CosMantleMinWallSteepnessAngle = FMath::Cos(FMath::DegreesToRadians(MantleMinWallSteepnessAngle));
float CosMantleMaxSurfaceAngle = FMath::Cos(FMath::DegreesToRadians(MantleMaxSurfaceAngle));
float CosMantleMaxAlignmentAngle = FMath::Cos(FMath::DegreesToRadians(MantleMaxAlignmentAngle));
// CHECK OBSTACLE FRONT FACE
FHitResult FrontHit;
float CheckDistance = GetCapsuleRadius() + MantleMaxDistance;
// The starting point of the line trace takes into account the step height and a customisable offset
FVector FrontStart = BaseLocation + FVector::UpVector * (MaxStepHeight - 1);
FrontStart.Z += MantleBaseStartOffset;
for (int i = 0; i < 10; i++)
{
DRAW_LINE(FrontStart, FrontStart + Forward * CheckDistance, FColor::Red)
if (GetWorld()->LineTraceSingleByProfile(FrontHit, FrontStart, FrontStart + Forward * CheckDistance,
"BlockAll", Params)) break;
FrontStart += FVector::UpVector * (2.f * GetCapsuleHalfHeight() - (MaxStepHeight - 1)) / 9;
}
if (!FrontHit.IsValidBlockingHit()) return false;
float CosWallSteepnessAngle = FrontHit.Normal | FVector::UpVector;
// Check if the front of the object is too steep
if (FMath::Abs(CosWallSteepnessAngle) > CosMantleMinWallSteepnessAngle
|| (Forward | -FrontHit.Normal) < CosMantleMaxAlignmentAngle) return false;
DRAW_POINT(FrontHit.Location, FColor::Red)
// CHECK OBSTACLE HEIGHT
TArray<FHitResult> HeightHits;
FHitResult SurfaceHit;
// Project the UP vector onto the normal vector of the object hit and normalize it
// This give us a vector that goes UP in the direction of the wall
FVector WallUpVector = FVector::VectorPlaneProject(FVector::UpVector, FrontHit.Normal).GetSafeNormal();
float WallCos = FVector::UpVector | FrontHit.Normal;
float WallSin = FMath::Sqrt(1 - WallCos * WallCos);
// The starting point for the line trace is the location of the wall + a forward vector scaled by the WALL-UP vector
// times the magnitude we want this vector (the max height we can perform the mantle), all divided by WALL-SIN
// WALL-SIN will be between 0 and 1 thus making TRACE-START bigger to account for the potential steepness of the wall
// (to ensure that TRACE-START is always of the same length)
FVector TraceStart = FrontHit.Location + Forward + WallUpVector * (MaxHeight - (MaxStepHeight - 1)) / WallSin;
DRAW_LINE(TraceStart, FrontHit.Location + Forward, FColor::Orange);
if(!GetWorld()->LineTraceMultiByProfile(HeightHits, TraceStart, FrontHit.Location + Forward, "BlockAll", Params))
{
return false;
}
for(const FHitResult& Hit : HeightHits)
{
// The tag is for the level designer to specify specific objects that should not be "mantable"
if (Hit.IsValidBlockingHit() && !Hit.GetActor()->ActorHasTag("NotMantle"))
{
SurfaceHit = Hit;
break;
}
}
// Check if the surface hit is valid OR if the surface is too steep to perform the mantle
if (!SurfaceHit.IsValidBlockingHit() || (SurfaceHit.Normal | FVector::UpVector) < CosMantleMaxSurfaceAngle) return false;
// We can consider the value of the dot product as the component of the first vector over the second one,
// thus giving us the height of the surface from the base location
float Height = (SurfaceHit.Location - BaseLocation) | FVector::UpVector;
PRINT_SCREEN(FString::Printf(TEXT("Height: %f"), Height));
DRAW_POINT(SurfaceHit.Location, FColor::Blue);
if (Height > MaxHeight) return false;
// CHECK CLEARANCE
float SurfaceCos = FVector::UpVector | SurfaceHit.Normal;
float SurfaceSin = FMath::Sqrt(1 - SurfaceCos * SurfaceCos);
// The point the capsule should mantle to, it takes into account the size of the capsule and any potential steepness of the surface
FVector ClearanceCapsuleLocation = SurfaceHit.Location + Forward * GetCapsuleRadius() +
FVector::UpVector * (GetCapsuleHalfHeight() + 1 + GetCapsuleRadius() * 2 * SurfaceSin);
FCollisionShape CapsuleShape = FCollisionShape::MakeCapsule(GetCapsuleRadius(), GetCapsuleHalfHeight());
if (GetWorld()->OverlapAnyTestByProfile(ClearanceCapsuleLocation, FQuat::Identity, "BlockAll", CapsuleShape, Params))
{
DRAW_CAPSULE(ClearanceCapsuleLocation, FColor::Red)
return false;
}
// Check if there are any obstacles between the player and the final point
if (GetWorld()->OverlapAnyTestByProfile(GetActorLocation() +
FVector::UpVector * (GetCapsuleHalfHeight() + 1 + GetCapsuleRadius() * 2 * SurfaceSin),
FQuat::Identity, "BlockAll", CapsuleShape, Params))
{
DRAW_CAPSULE(ClearanceCapsuleLocation, FColor::Black)
return false;
}
DRAW_CAPSULE(SurfaceHit.Location + Forward * GetCapsuleRadius() + FVector::UpVector * GetCapsuleHalfHeight(), FColor::Green)
MantleLocation = SurfaceHit.Location + Forward * GetCapsuleRadius() + FVector::UpVector * GetCapsuleHalfHeight();
bCanMantle = true;
bHighMantle = true;
if (Height < LowMantleCutoff) bHighMantle = false;
return true;
}