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"
|
||||
|
||||
// UE includes
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
UGSCharacterMovementComponent::UGSCharacterMovementComponent()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void UGSCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations)
|
||||
{
|
||||
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)
|
||||
{
|
||||
NewUpDirection.Normalize();
|
||||
|
@ -19,11 +19,19 @@ class GRAVITYSTOMPGAME_API UGSCharacterMovementComponent : public UCharacterMove
|
||||
public:
|
||||
UGSCharacterMovementComponent();
|
||||
|
||||
virtual void PhysFalling(float deltaTime, int32 Iterations) override;
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
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:
|
||||
FVector CharacterUpDirection = FVector::UpVector;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user