Rotate character and update floor detection functions so you can properly land on ceilings
This commit is contained in:
parent
01f88914cd
commit
94d04f2250
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.
@ -3,6 +3,7 @@
|
|||||||
#include "Character/GSCharacterMovementComponent.h"
|
#include "Character/GSCharacterMovementComponent.h"
|
||||||
|
|
||||||
// UE includes
|
// UE includes
|
||||||
|
#include "Components/CapsuleComponent.h"
|
||||||
#include "GameFramework/Character.h"
|
#include "GameFramework/Character.h"
|
||||||
|
|
||||||
// These are defined in CharacterMovementComponent.cpp and inaccessible here. Just copy and paste too make the PhysFalling work
|
// These are defined in CharacterMovementComponent.cpp and inaccessible here. Just copy and paste too make the PhysFalling work
|
||||||
@ -22,10 +23,12 @@ namespace CharacterMovementCVars
|
|||||||
ECVF_Default);
|
ECVF_Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UGSCharacterMovementComponent::UGSCharacterMovementComponent()
|
UGSCharacterMovementComponent::UGSCharacterMovementComponent()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UGSCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations)
|
void UGSCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations)
|
||||||
{
|
{
|
||||||
if (deltaTime < MIN_TICK_TIME)
|
if (deltaTime < MIN_TICK_TIME)
|
||||||
@ -348,6 +351,252 @@ void UGSCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iteration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UGSCharacterMovementComponent::ComputeFloorDist(const FVector& CapsuleLocation, float LineDistance,
|
||||||
|
float SweepDistance, FFindFloorResult& OutFloorResult, float SweepRadius,
|
||||||
|
const FHitResult* DownwardSweepResult) const
|
||||||
|
{
|
||||||
|
//UE_LOG(LogCharacterMovement, VeryVerbose, TEXT("[Role:%d] ComputeFloorDist: %s at location %s"), (int32)CharacterOwner->GetLocalRole(), *GetNameSafe(CharacterOwner), *CapsuleLocation.ToString());
|
||||||
|
OutFloorResult.Clear();
|
||||||
|
|
||||||
|
float PawnRadius, PawnHalfHeight;
|
||||||
|
CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight);
|
||||||
|
|
||||||
|
bool bSkipSweep = false;
|
||||||
|
if (DownwardSweepResult != NULL && DownwardSweepResult->IsValidBlockingHit())
|
||||||
|
{
|
||||||
|
// Only if the supplied sweep was vertical and downward.
|
||||||
|
if ((DownwardSweepResult->TraceStart.Z > DownwardSweepResult->TraceEnd.Z) &&
|
||||||
|
(DownwardSweepResult->TraceStart - DownwardSweepResult->TraceEnd).SizeSquared2D() <= UE_KINDA_SMALL_NUMBER)
|
||||||
|
{
|
||||||
|
// Reject hits that are barely on the cusp of the radius of the capsule
|
||||||
|
if (IsWithinEdgeTolerance(DownwardSweepResult->Location, DownwardSweepResult->ImpactPoint, PawnRadius))
|
||||||
|
{
|
||||||
|
// Don't try a redundant sweep, regardless of whether this sweep is usable.
|
||||||
|
bSkipSweep = true;
|
||||||
|
|
||||||
|
const bool bIsWalkable = IsWalkable(*DownwardSweepResult);
|
||||||
|
const float FloorDist = (CapsuleLocation.Z - DownwardSweepResult->Location.Z);
|
||||||
|
OutFloorResult.SetFromSweep(*DownwardSweepResult, FloorDist, bIsWalkable);
|
||||||
|
|
||||||
|
if (bIsWalkable)
|
||||||
|
{
|
||||||
|
// Use the supplied downward sweep as the floor hit result.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We require the sweep distance to be >= the line distance, otherwise the HitResult can't be interpreted as the sweep result.
|
||||||
|
if (SweepDistance < LineDistance)
|
||||||
|
{
|
||||||
|
ensure(SweepDistance >= LineDistance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bBlockingHit = false;
|
||||||
|
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(ComputeFloorDist), false, CharacterOwner);
|
||||||
|
FCollisionResponseParams ResponseParam;
|
||||||
|
InitCollisionParams(QueryParams, ResponseParam);
|
||||||
|
const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType();
|
||||||
|
|
||||||
|
// Sweep test
|
||||||
|
if (!bSkipSweep && SweepDistance > 0.f && SweepRadius > 0.f)
|
||||||
|
{
|
||||||
|
// Use a shorter height to avoid sweeps giving weird results if we start on a surface.
|
||||||
|
// This also allows us to adjust out of penetrations.
|
||||||
|
const float ShrinkScale = 0.9f;
|
||||||
|
const float ShrinkScaleOverlap = 0.1f;
|
||||||
|
float ShrinkHeight = (PawnHalfHeight - PawnRadius) * (1.f - ShrinkScale);
|
||||||
|
float TraceDist = SweepDistance + ShrinkHeight;
|
||||||
|
FCollisionShape CapsuleShape = FCollisionShape::MakeCapsule(SweepRadius, PawnHalfHeight - ShrinkHeight);
|
||||||
|
|
||||||
|
FHitResult Hit(1.f);
|
||||||
|
bBlockingHit = FloorSweepTest(Hit, CapsuleLocation, CapsuleLocation + (CharacterUpDirection * -TraceDist), CollisionChannel, CapsuleShape, QueryParams, ResponseParam);
|
||||||
|
|
||||||
|
if (bBlockingHit)
|
||||||
|
{
|
||||||
|
// Reject hits adjacent to us, we only care about hits on the bottom portion of our capsule.
|
||||||
|
// Check 2D distance to impact point, reject if within a tolerance from radius.
|
||||||
|
if (Hit.bStartPenetrating || !IsWithinEdgeTolerance(CapsuleLocation, Hit.ImpactPoint, CapsuleShape.Capsule.Radius))
|
||||||
|
{
|
||||||
|
// Use a capsule with a slightly smaller radius and shorter height to avoid the adjacent object.
|
||||||
|
// Capsule must not be nearly zero or the trace will fall back to a line trace from the start point and have the wrong length.
|
||||||
|
CapsuleShape.Capsule.Radius = FMath::Max(0.f, CapsuleShape.Capsule.Radius - SWEEP_EDGE_REJECT_DISTANCE - UE_KINDA_SMALL_NUMBER);
|
||||||
|
if (!CapsuleShape.IsNearlyZero())
|
||||||
|
{
|
||||||
|
ShrinkHeight = (PawnHalfHeight - PawnRadius) * (1.f - ShrinkScaleOverlap);
|
||||||
|
TraceDist = SweepDistance + ShrinkHeight;
|
||||||
|
CapsuleShape.Capsule.HalfHeight = FMath::Max(PawnHalfHeight - ShrinkHeight, CapsuleShape.Capsule.Radius);
|
||||||
|
Hit.Reset(1.f, false);
|
||||||
|
|
||||||
|
bBlockingHit = FloorSweepTest(Hit, CapsuleLocation, CapsuleLocation + (CharacterUpDirection * -TraceDist), CollisionChannel, CapsuleShape, QueryParams, ResponseParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce hit distance by ShrinkHeight because we shrank the capsule for the trace.
|
||||||
|
// We allow negative distances here, because this allows us to pull out of penetrations.
|
||||||
|
const float MaxPenetrationAdjust = FMath::Max(MAX_FLOOR_DIST, PawnRadius);
|
||||||
|
const float SweepResult = FMath::Max(-MaxPenetrationAdjust, Hit.Time * TraceDist - ShrinkHeight);
|
||||||
|
|
||||||
|
OutFloorResult.SetFromSweep(Hit, SweepResult, false);
|
||||||
|
if (Hit.IsValidBlockingHit() && IsWalkable(Hit))
|
||||||
|
{
|
||||||
|
if (SweepResult <= SweepDistance)
|
||||||
|
{
|
||||||
|
// Hit within test distance.
|
||||||
|
OutFloorResult.bWalkableFloor = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we require a longer sweep than line trace, we don't want to run the line trace if the sweep missed everything.
|
||||||
|
// We do however want to try a line trace if the sweep was stuck in penetration.
|
||||||
|
if (!OutFloorResult.bBlockingHit && !OutFloorResult.HitResult.bStartPenetrating)
|
||||||
|
{
|
||||||
|
OutFloorResult.FloorDist = SweepDistance;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line trace
|
||||||
|
if (LineDistance > 0.f)
|
||||||
|
{
|
||||||
|
const float ShrinkHeight = PawnHalfHeight;
|
||||||
|
const FVector LineTraceStart = CapsuleLocation;
|
||||||
|
const float TraceDist = LineDistance + ShrinkHeight;
|
||||||
|
const FVector Down = (CharacterUpDirection * -TraceDist);
|
||||||
|
QueryParams.TraceTag = SCENE_QUERY_STAT_NAME_ONLY(FloorLineTrace);
|
||||||
|
|
||||||
|
FHitResult Hit(1.f);
|
||||||
|
bBlockingHit = GetWorld()->LineTraceSingleByChannel(Hit, LineTraceStart, LineTraceStart + Down, CollisionChannel, QueryParams, ResponseParam);
|
||||||
|
|
||||||
|
if (bBlockingHit)
|
||||||
|
{
|
||||||
|
if (Hit.Time > 0.f)
|
||||||
|
{
|
||||||
|
// Reduce hit distance by ShrinkHeight because we started the trace higher than the base.
|
||||||
|
// We allow negative distances here, because this allows us to pull out of penetrations.
|
||||||
|
const float MaxPenetrationAdjust = FMath::Max(MAX_FLOOR_DIST, PawnRadius);
|
||||||
|
const float LineResult = FMath::Max(-MaxPenetrationAdjust, Hit.Time * TraceDist - ShrinkHeight);
|
||||||
|
|
||||||
|
OutFloorResult.bBlockingHit = true;
|
||||||
|
if (LineResult <= LineDistance && IsWalkable(Hit))
|
||||||
|
{
|
||||||
|
OutFloorResult.SetFromLineTrace(Hit, OutFloorResult.FloorDist, LineResult, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No hits were acceptable.
|
||||||
|
OutFloorResult.bWalkableFloor = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool UGSCharacterMovementComponent::IsValidLandingSpot(const FVector& CapsuleLocation, const FHitResult& Hit) const
|
||||||
|
{
|
||||||
|
if (!Hit.bBlockingHit)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip some checks if penetrating. Penetration will be handled by the FindFloor call (using a smaller capsule)
|
||||||
|
if (!Hit.bStartPenetrating)
|
||||||
|
{
|
||||||
|
// Reject unwalkable floor normals.
|
||||||
|
if (!IsWalkable(Hit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float PawnRadius, PawnHalfHeight;
|
||||||
|
CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight);
|
||||||
|
|
||||||
|
// TODO: I understand what this is trying to do but need to go over the math again so I can make it work for different character up orientations
|
||||||
|
/*
|
||||||
|
// Reject hits that are above our lower hemisphere (can happen when sliding down a vertical surface).
|
||||||
|
const float LowerHemisphereZ = Hit.Location.Z - PawnHalfHeight + PawnRadius;
|
||||||
|
if (Hit.ImpactPoint.Z >= LowerHemisphereZ)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Another thing I sort of understand what it is trying to do but need to go over the math again so I can make it work for different character up orientations
|
||||||
|
/*
|
||||||
|
// Reject hits that are barely on the cusp of the radius of the capsule
|
||||||
|
if (!IsWithinEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Penetrating
|
||||||
|
float NormalUp = CharacterUpDirection.Dot(Hit.Normal);
|
||||||
|
if (NormalUp < UE_KINDA_SMALL_NUMBER)
|
||||||
|
{
|
||||||
|
// Normal is nearly horizontal or downward, that's a penetration adjustment next to a vertical or overhanging wall. Don't pop to the floor.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FFindFloorResult FloorResult;
|
||||||
|
FindFloor(CapsuleLocation, FloorResult, false, &Hit);
|
||||||
|
|
||||||
|
if (!FloorResult.IsWalkableFloor())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool UGSCharacterMovementComponent::IsWalkable(const FHitResult& Hit) const
|
||||||
|
{
|
||||||
|
if (!Hit.IsValidBlockingHit())
|
||||||
|
{
|
||||||
|
// No hit, or starting in penetration
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ImpactNormalUp = CharacterUpDirection.Dot(Hit.ImpactNormal);
|
||||||
|
|
||||||
|
// Never walk up vertical surfaces.
|
||||||
|
if (ImpactNormalUp < UE_KINDA_SMALL_NUMBER)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
float TestWalkableZ = WalkableFloorZ;
|
||||||
|
|
||||||
|
// See if this component overrides the walkable floor z.
|
||||||
|
const UPrimitiveComponent* HitComponent = Hit.Component.Get();
|
||||||
|
if (HitComponent)
|
||||||
|
{
|
||||||
|
const FWalkableSlopeOverride& SlopeOverride = HitComponent->GetWalkableSlopeOverride();
|
||||||
|
TestWalkableZ = SlopeOverride.ModifyWalkableFloorZ(TestWalkableZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't walk on this surface if it is too steep.
|
||||||
|
if (Hit.ImpactNormal.Z < TestWalkableZ)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void UGSCharacterMovementComponent::SetCharacterUpDirection(FVector NewUpDirection)
|
void UGSCharacterMovementComponent::SetCharacterUpDirection(FVector NewUpDirection)
|
||||||
{
|
{
|
||||||
NewUpDirection.Normalize();
|
NewUpDirection.Normalize();
|
||||||
|
@ -19,11 +19,19 @@ class GRAVITYSTOMPGAME_API UGSCharacterMovementComponent : public UCharacterMove
|
|||||||
public:
|
public:
|
||||||
UGSCharacterMovementComponent();
|
UGSCharacterMovementComponent();
|
||||||
|
|
||||||
virtual void PhysFalling(float deltaTime, int32 Iterations) override;
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable)
|
UFUNCTION(BlueprintCallable)
|
||||||
void SetCharacterUpDirection(FVector NewUpDirection);
|
void SetCharacterUpDirection(FVector NewUpDirection);
|
||||||
|
|
||||||
|
// UCharacterMovementComponent interface
|
||||||
|
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;
|
||||||
|
|
||||||
|
virtual bool IsValidLandingSpot(const FVector& CapsuleLocation, const FHitResult& Hit) const override;
|
||||||
|
|
||||||
|
virtual bool IsWalkable(const FHitResult& Hit) const override;
|
||||||
|
// End UCharacterMovementComponent interface
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FVector CharacterUpDirection = FVector::UpVector;
|
FVector CharacterUpDirection = FVector::UpVector;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user