A project using this tutorial series is tracked on GitHub. The revision after this part is 137f023.
Hej!
In this part we will look at basic collision handling related to movement and make sure that our character doesn't go through walls anymore.
It is actually fairly easy to implement as Unreal does all the heavy lifting of doing low-level collision checking, however we need to make sure that "colliding" means to prevent walking through walls, and also try to slide along them instead. Luckily there is a function built into UMovementComponent
called SlideALongSurface
that helps with that.
The steps we need to take are:
Set character collision profile to Pawn
.
Change MoveUpdatedComponent
to SafeMoveUpdatedComponent.
Check for any valid hits after moving.
Call HandleImpact and SlideAlongSurface accordingly.
First things first we need to set the collision profile of our character to Pawn
(or something else that might be appropriate for your project). By default it is set to OverlapAllDynamic
which won't trigger any blocking hits. In your equivalent of BP_CactusPlayerPawn
, on the capsule component set the collision preset:
First we are going to change MoveUpdatedComponent
to SafeMoveUpdatedComponent
. This is strictly not necessary for our code to work, however the difference is that the safe version automatically resolves initial penetrations, if we somehow start or end-up inside a wall. To facilitate this the safe version requires an additional FHitResult
parameter (optional on the non-safe variant).
Replace our call to MoveUpdatedComponent(...)
with:
FHitResult Hit;
SafeMoveUpdatedComponent(Velocity, Rotation, true, Hit);
Notice that we also set the bSweep
parameter to true
. This is what enables collision checking internally.
If you hit play now and try moving around you will notice that we won't go through walls anymore. However, unless we are moving directly away from a wall we instead stick to it!
This is because we have yet to handle what happens when we collide. SafeMoveUpdatedComponent
automatically stops the movement but we need to slide along the surface as well. Luckily it's very simple; we only need to add the following snippet directly after the movement call:
if (Hit.IsValidBlockingHit())
{
HandleImpact(Hit, DeltaTime, Velocity);
SlideAlongSurface(Velocity, 1.0f - Hit.Time, Hit.Normal, Hit, true);
}
HandleImpact
is a helper to catch and propagate an impact throughout the movement component, however the base implementation is empty so in our case it is not strictly needed, but it's still good practice to include.
SlideAlongSurface
is the main part and it simply computes and moves the component along a projected vector based on the normal (angle) and other parameters we give it. Internally it calls SafeMoveUpdatedComponent
and HandleImpact
like we've done.
We should now correctly slide along any walls:
Interestingly, since SlideAlongSurface
is not restricted to the horizontal plane we actually slide up slopes as well:
However we don't slide down as no collision is happening and we aren't applying gravity (which is what we will take a look at in the next part!).
Our full TickComponent
function now looks like this:
void USimpleMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (ShouldSkipUpdate(DeltaTime))
{
return;
}
if (!PawnOwner || !UpdatedComponent)
{
return;
}
// Reset velocity
Velocity.X = 0;
Velocity.Y = 0;
// Calculate force
const FVector& Input = ConsumeInputVector().GetClampedToMaxSize2D(1.0f);
const FVector DesiredInputForce = Input * MoveSpeed;
Velocity += DesiredInputForce;
const FVector MovementDelta = Velocity * DeltaTime;
// Move
const FRotator& Rotation = UpdatedComponent->GetComponentRotation();
FHitResult Hit;
SafeMoveUpdatedComponent(MovementDelta, Rotation, true, Hit);
if (Hit.IsValidBlockingHit())
{
HandleImpact(Hit, DeltaTime, MovementDelta);
SlideAlongSurface(MovementDelta, 1.0f - Hit.Time, Hit.Normal, Hit, true);
}
UpdateComponentVelocity();
}