Skip to content
Snippets Groups Projects

Draft: Improve walking implementation

Closed Jan Delember requested to merge feature/improve-walking into 4.26
All threads resolved!
3 files
+ 298
205
Compare changes
  • Side-by-side
  • Inline

Files



#include "Pawn/VRPawnMovement.h"
#include "Pawn/VRPawnMovement.h"
#include "DrawDebugHelpers.h"
#include "DrawDebugHelpers.h"
#include "Kismet/KismetSystemLibrary.h"
UVRPawnMovement::UVRPawnMovement(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
UVRPawnMovement::UVRPawnMovement(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
CapsuleColliderComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleCollider"));
{
CapsuleColliderComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
// the capsule is used to store the players size and position, e.g., for other interactions and as starting point
CapsuleColliderComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap);
// for the capsule trace (which however not use the capsule component directly)
CapsuleColliderComponent->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block);
CapsuleColliderComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleCollider"));
CapsuleColliderComponent->SetCapsuleSize(CapsuleRadius, 80.0f);
CapsuleColliderComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}
CapsuleColliderComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap);
CapsuleColliderComponent->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block);
void UVRPawnMovement::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction){
CapsuleColliderComponent->SetCapsuleSize(CapsuleRadius, 80.0f);
SetCapsuleColliderToUserSize();
//set some defaults for the UFloatingPawnMovement component, which are more reasonable for usage in VR
MaxSpeed = 300.f;
FVector PositionChange = GetPendingInputVector();
Acceleration = 800.f;
Deceleration = 2000.f;
if (NavigationMode == EVRNavigationModes::NAV_WALK)
}
{
PositionChange.Z = 0.0f;
void UVRPawnMovement::BeginPlay()
ConsumeInputVector();
{
AddInputVector(PositionChange);
Super::BeginPlay();
}
LastCapsulePosition.Reset();
LastSteeringCollisionVector = FVector(0,0,0);
if(NavigationMode == EVRNavigationModes::NAV_FLY || NavigationMode == EVRNavigationModes::NAV_WALK)
}
{
MoveByGravityOrStepUp(DeltaTime);
CheckForPhysWalkingCollision();
void UVRPawnMovement::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction){
if(CheckForVirtualMovCollision(PositionChange, DeltaTime))
SetCapsuleColliderToUserSize();
{
ConsumeInputVector();
FVector InputVector = GetPendingInputVector();
}
}
if (NavigationMode == EVRNavigationModes::NAV_WALK)
{
if(NavigationMode == EVRNavigationModes::NAV_NONE)
// you are only allowed to move horizontally in NAV_WALK
{
// everything else will be handled by stepping-up/gravity
ConsumeInputVector();
// so remove Z component for the input vector of the UFloatingPawnMovement
}
InputVector.Z = 0.0f;
ConsumeInputVector();
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
AddInputVector(InputVector);
}
LastHeadPosition = HeadComponent->GetComponentLocation();
}
const FVector CapsuleLocation = CapsuleColliderComponent->GetComponentLocation();
if(bDeactivatedWhileInCollision && !CreateCapsuleTrace(CapsuleLocation, CapsuleLocation).bBlockingHit)
bool UVRPawnMovement::CheckForVirtualMovCollision(FVector PositionChange, float DeltaTime)
{
{
bDeactivatedWhileInCollision=false;
FVector ProbePosition = PositionChange.GetSafeNormal() * GetMaxSpeed() * DeltaTime;
}
FHitResult FHitResultVR;
CapsuleColliderComponent->AddWorldOffset(ProbePosition, true, &FHitResultVR);
if(NavigationMode == EVRNavigationModes::NAV_FLY || NavigationMode == EVRNavigationModes::NAV_WALK)
if (FVector::Distance(FHitResultVR.Location, CapsuleColliderComponent->GetComponentLocation()) < CapsuleColliderComponent->GetScaledCapsuleRadius())
{
{
if(InputVector.Size() > 0.001){
return true;
const FVector SafeSteeringInput = GetCollisionSafeVirtualSteeringVec(InputVector, DeltaTime);
}
if(SafeSteeringInput != InputVector)
return false;
{
}
// if we would move into something if we apply this input (estimating distance by max speed)
// we only apply its perpendicular part (unless it is facing away from the collision)
void UVRPawnMovement::SetHeadComponent(USceneComponent* NewHeadComponent)
ConsumeInputVector();
{
AddInputVector(SafeSteeringInput);
HeadComponent = NewHeadComponent;
}
CapsuleColliderComponent->SetupAttachment(HeadComponent);
}
const float HalfHeight = 80.0f; //this is just an initial value to look good in editor
CapsuleColliderComponent->SetCapsuleSize(CapsuleRadius, HalfHeight);
// so we add stepping-up (for both walk and fly)
CapsuleColliderComponent->SetWorldLocation(FVector(0.0f, 0.0f,HalfHeight));
// and gravity for walking only
}
MoveByGravityOrStepUp(DeltaTime);
void UVRPawnMovement::SetCapsuleColliderToUserSize()
//if we physically (in the tracking space) walked into something, move the world away (by moving the pawn)
{
if(!bDeactivatedWhileInCollision) CheckForPhysWalkingCollision();
float CharachterSize = abs(UpdatedComponent->GetComponentLocation().Z - HeadComponent->GetComponentLocation().Z);
}
if (CharachterSize > MaxStepHeight)
if(NavigationMode == EVRNavigationModes::NAV_NONE)
{
{
float ColliderHeight = CharachterSize - MaxStepHeight;
//just remove whatever input is there
float ColliderHalfHeight = ColliderHeight / 2.0f;
ConsumeInputVector();
if (ColliderHalfHeight <= CapsuleRadius)
}
{//Make the collider to a Sphere
CapsuleColliderComponent->SetCapsuleSize(ColliderHalfHeight, ColliderHalfHeight);
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
}
else
{//Make the collider to a Capsule
void UVRPawnMovement::SetHeadComponent(USceneComponent* NewHeadComponent)
CapsuleColliderComponent->SetCapsuleSize(CapsuleRadius, ColliderHalfHeight);
{
}
HeadComponent = NewHeadComponent;
CapsuleColliderComponent->SetupAttachment(HeadComponent);
CapsuleColliderComponent->SetWorldLocation(HeadComponent->GetComponentLocation());
const float HalfHeight = 80.0f; //this is just an initial value to look good in editor
CapsuleColliderComponent->AddWorldOffset(FVector(0, 0, -ColliderHalfHeight));
CapsuleColliderComponent->SetCapsuleSize(CapsuleRadius, HalfHeight);
CapsuleColliderComponent->SetWorldRotation(FRotator(0, 0, 1));
CapsuleColliderComponent->SetWorldLocation(FVector(0.0f, 0.0f,HalfHeight));
}
}
else
{
void UVRPawnMovement::SetCapsuleColliderToUserSize()
CapsuleColliderComponent->SetWorldLocation(HeadComponent->GetComponentLocation());
{
CapsuleColliderComponent->SetWorldRotation(FRotator(0, 0, 1));
}
// the collider should be placed
}
// between head and floor + MaxStepHeight
// head
void UVRPawnMovement::CheckForPhysWalkingCollision()
// / \
{
// / \
FVector CurrentHeadPosition = HeadComponent->GetComponentLocation();
// | |
FVector Direction = CurrentHeadPosition - LastHeadPosition;
// | |
FHitResult FHitResultPhys;
// |collider|
CapsuleColliderComponent->AddWorldOffset(Direction, true, &FHitResultPhys);
// | |
// | |
if (FHitResultPhys.bBlockingHit)
// \ /
{
// \ __ /
UpdatedComponent->AddLocalOffset(FHitResultPhys.Normal*FHitResultPhys.PenetrationDepth);
// |
}
// MaxStepHeight
}
// |
// floor: ______________
void UVRPawnMovement::MoveByGravityOrStepUp(float DeltaSeconds)
{
const float UserSize = HeadComponent->GetComponentLocation().Z - UpdatedComponent->GetComponentLocation().Z;
FVector StartLineTraceUnderCollider = CapsuleColliderComponent->GetComponentLocation();
StartLineTraceUnderCollider.Z -= CapsuleColliderComponent->GetScaledCapsuleHalfHeight();
if (UserSize > MaxStepHeight)
FHitResult HitDetailsMultiLineTrace = CreateMultiLineTrace(FVector(0, 0, -1), StartLineTraceUnderCollider, CapsuleColliderComponent->GetScaledCapsuleRadius() / 4.0f, false);
{
float DistanceDifference = abs(MaxStepHeight - HitDetailsMultiLineTrace.Distance);
const float ColliderHeight = UserSize - MaxStepHeight;
//Going up (in Fly and Walk Mode)
const float ColliderHalfHeight = ColliderHeight / 2.0f;
if ((HitDetailsMultiLineTrace.bBlockingHit && HitDetailsMultiLineTrace.Distance < MaxStepHeight))
if (ColliderHalfHeight <= CapsuleRadius)
{
{
ShiftVertically(DistanceDifference, UpSteppingAcceleration, DeltaSeconds, 1);
//the capsule will actually be compressed to a sphere
}
CapsuleColliderComponent->SetCapsuleSize(ColliderHalfHeight, ColliderHalfHeight);
//Gravity (only in Walk Mode)
}
else if (NavigationMode==EVRNavigationModes::NAV_WALK && ((HitDetailsMultiLineTrace.bBlockingHit && HitDetailsMultiLineTrace.Distance > MaxStepHeight) || (HitDetailsMultiLineTrace.Actor == nullptr && HitDetailsMultiLineTrace.Distance != -1.0f)))
else
{
{
ShiftVertically(DistanceDifference, GravityAcceleration, DeltaSeconds, -1);
CapsuleColliderComponent->SetCapsuleSize(CapsuleRadius, ColliderHalfHeight);
}
}
}
CapsuleColliderComponent->SetWorldLocation(HeadComponent->GetComponentLocation() - FVector(0, 0, ColliderHalfHeight));
void UVRPawnMovement::ShiftVertically(float DiffernceDistance, float VerticalAcceleration, float DeltaSeconds, int Direction)
}
{
else
VerticalSpeed += VerticalAcceleration * DeltaSeconds;
{
if (VerticalSpeed*DeltaSeconds < DiffernceDistance)
CapsuleColliderComponent->SetWorldLocation(HeadComponent->GetComponentLocation());
{
}
UpdatedComponent->AddLocalOffset(FVector(0.f, 0.f, Direction * VerticalSpeed * DeltaSeconds));
}
CapsuleColliderComponent->SetWorldRotation(FRotator::ZeroRotator);
else
}
{
UpdatedComponent->AddLocalOffset(FVector(0.f, 0.f, Direction * DiffernceDistance));
void UVRPawnMovement::CheckForPhysWalkingCollision()
VerticalSpeed = 0;
{
}
if(!LastCapsulePosition.IsSet())
}
{
//not yet set, so do nothing than setting it
FHitResult UVRPawnMovement::CreateLineTrace(FVector Direction, const FVector Start, bool Visibility)
LastCapsulePosition = CapsuleColliderComponent->GetComponentLocation();
{
return;
//Re-initialize hit info
}
FHitResult HitDetails = FHitResult(ForceInit);
const FVector CapsuleLocation = CapsuleColliderComponent->GetComponentLocation();
FVector End = ((Direction * 1000.f) + Start);
const FHitResult HitResult = CreateCapsuleTrace(LastCapsulePosition.GetValue(), CapsuleLocation);
// additional trace parameters
FCollisionQueryParams TraceParams(FName(TEXT("InteractTrace")), true, NULL);
//if this was not possible move the entire pawn away to avoid the head collision
TraceParams.bTraceComplex = true; //to use complex collision on whatever we interact with to provide better precision.
if (HitResult.bBlockingHit)
TraceParams.bReturnPhysicalMaterial = true; //to provide details about the physical material, if one exists on the thing we hit, to come back in our hit result.
{
const FVector MoveOutVector = HitResult.Location-CapsuleLocation;
if (Visibility)
//move it out twice as far, to avoid getting stuck situations
DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, 1, 0, 1);
UpdatedComponent->AddWorldOffset(2*MoveOutVector);
}
if (GetWorld()->LineTraceSingleByChannel(HitDetails, Start, End, ECC_Visibility, TraceParams))
{
if (HitDetails.bBlockingHit)
//only update if not in collision
{
if(!CreateCapsuleTrace(CapsuleLocation, CapsuleLocation).bBlockingHit)
}
{
}
LastCapsulePosition = CapsuleColliderComponent->GetComponentLocation();;
return HitDetails;
}
}
else{
//we are still in collision, so deactivate collision handling until this stopped
FHitResult UVRPawnMovement::CreateMultiLineTrace(FVector Direction, const FVector Start, float Radius, bool Visibility)
bDeactivatedWhileInCollision=true;
{
LastCapsulePosition.Reset();
TArray<FVector> StartVectors;
}
TArray<FHitResult> OutHits;
}
FHitResult HitDetailsMultiLineTrace;
HitDetailsMultiLineTrace.Distance = -1.0f;//(Distance=-1) not existing, but to know if this Variable not Initialized(when all Traces not compatible)
FVector UVRPawnMovement::GetCollisionSafeVirtualSteeringVec(FVector InputVector, float DeltaTime)
{
StartVectors.Add(Start); //LineTraceCenter
StartVectors.Add(Start + FVector(0, -Radius, 0)); //LineTraceLeft
const float SafetyFactor = 3.0f; //so we detect collision a bit earlier
StartVectors.Add(Start + FVector(0, +Radius, 0)); //LineTraceRight
const FVector CapsuleLocation = CapsuleColliderComponent->GetComponentLocation();
StartVectors.Add(Start + FVector(+Radius, 0, 0)); //LineTraceFront
FVector ProbePosition = SafetyFactor * InputVector.GetSafeNormal() * GetMaxSpeed() * DeltaTime + CapsuleLocation;
StartVectors.Add(Start + FVector(-Radius, 0, 0)); //LineTraceBehind
const FHitResult TraceResult = CreateCapsuleTrace(CapsuleLocation, ProbePosition);
if (!TraceResult.bBlockingHit)
bool IsBlockingHitAndSameActor = true;
{
bool IsAllNothingHiting = true;
//everything is fine, use that vector
// loop through TArray
return InputVector;
for (FVector& Vector : StartVectors)
}
{
FHitResult OutHit = CreateLineTrace(Direction, Vector, Visibility);
//otherwise remove the component of that vector that goes towards the collision
OutHits.Add(OutHit);
FVector CollisionVector = TraceResult.Location - CapsuleLocation;
IsBlockingHitAndSameActor &= (OutHit.Actor == OutHits[0].Actor); //If all Hiting the same Object, then you are (going up/down) or (walking)
IsAllNothingHiting &= (OutHit.Actor == nullptr); //If all Hiting nothing, then you are falling
//sometimes (if by chance we already moved into collision entirely CollisionVector is 0
}
if(! CollisionVector.Normalize())
{
if (IsBlockingHitAndSameActor || IsAllNothingHiting)
//then we probably start already in collision, so we use the last one
HitDetailsMultiLineTrace = OutHits[0];
CollisionVector = LastSteeringCollisionVector;
}
return HitDetailsMultiLineTrace;
else
}
{
\ No newline at end of file
LastSteeringCollisionVector = CollisionVector;
 
}
 
 
FVector SafeInput = InputVector;
 
const float DotProduct = FVector::DotProduct(InputVector, CollisionVector);
 
if(DotProduct>0.0f)
 
{
 
// only keep perpendicular part of the input vector (remove anything towards hit)
 
SafeInput -= DotProduct * CollisionVector;
 
}
 
return SafeInput;
 
}
 
 
void UVRPawnMovement::MoveByGravityOrStepUp(float DeltaSeconds)
 
{
 
const FVector DownTraceStart = CapsuleColliderComponent->GetComponentLocation();
 
const float DownTraceDist = MaxFallingDepth < 0.0f ? 1000.0f : MaxFallingDepth;
 
const FVector DownTraceDir = FVector(0,0,-1);
 
const FVector DownTraceEnd = DownTraceStart + DownTraceDist * DownTraceDir;
 
 
const FHitResult DownTraceHitResult = CreateCapsuleTrace(DownTraceStart, DownTraceEnd);
 
float HeightDifference = 0.0f;
 
 
if(DownTraceHitResult.bBlockingHit)
 
{
 
HeightDifference = DownTraceHitResult.ImpactPoint.Z - UpdatedComponent->GetComponentLocation().Z;
 
//so for HeightDifference>0, we have to move the pawn up; for HeightDifference<0 we have to move it down
 
}
 
 
//Going up (in Fly and Walk Mode)
 
if (HeightDifference>0.0f && HeightDifference<=MaxStepHeight)
 
{
 
ShiftVertically(HeightDifference, UpSteppingAcceleration, DeltaSeconds);
 
}
 
 
if(NavigationMode!=EVRNavigationModes::NAV_WALK)
 
{
 
return;
 
}
 
 
if(!DownTraceHitResult.bBlockingHit && MaxFallingDepth<0.0f)
 
{
 
HeightDifference = -1000.0f; //just fall
 
}
 
 
//Gravity (only in Walk Mode)
 
if (HeightDifference<0.0f)
 
{
 
ShiftVertically(HeightDifference, GravityAcceleration, DeltaSeconds);
 
}
 
}
 
 
void UVRPawnMovement::ShiftVertically(float Distance, float VerticalAcceleration, float DeltaSeconds)
 
{
 
VerticalSpeed += VerticalAcceleration * DeltaSeconds;
 
if (abs(VerticalSpeed*DeltaSeconds) < abs(Distance))
 
{
 
UpdatedComponent->AddWorldOffset(FVector(0.f, 0.f, VerticalSpeed * DeltaSeconds));
 
}
 
else
 
{
 
UpdatedComponent->AddWorldOffset(FVector(0.f, 0.f, Distance));
 
VerticalSpeed = 0;
 
}
 
}
 
 
FHitResult UVRPawnMovement::CreateCapsuleTrace(const FVector Start, FVector End, bool DrawDebug)
 
{
 
const EDrawDebugTrace::Type DrawType = DrawDebug ? EDrawDebugTrace::Type::ForDuration : EDrawDebugTrace::Type::None;
 
if(ActorsToIgnore.Num()==0){
 
ActorsToIgnore.Add(GetOwner());
 
}
 
 
//UE_LOG(LogTemp, Warning, TEXT("Capsule from %s to %s"), *Start.ToString(), *End.ToString())
 
 
FHitResult Hit;
 
UKismetSystemLibrary::CapsuleTraceSingle(GetWorld(), Start, End, CapsuleColliderComponent->GetScaledCapsuleRadius(), CapsuleColliderComponent->GetScaledCapsuleHalfHeight(), UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_Visibility), true, ActorsToIgnore, DrawType, Hit, true);
 
return Hit;
 
}
Loading