Mover - StanceModifier incorrectly offsetting Capsule when both Capsule and SkeletalMesh have collisions

Heya, I’ve been trying to integrate recent GASP examples into our project and noticed a peculiar thing happening when a character crouches. I was able to reproduce this occurrence inside the GASP project as well.

I have attached 3 videos; in all of them, the Root `CapsuleComponent` has Pawn collisions enabled (the default value from GASP). But each has a different setting for the child `SkeletalMeshComponent`.

  • 1st video - No Collision (NoCollision.mp4 attachment)
    • [Image Removed]
  • 2nd video - Pawn preset (Pawn.mp4 attachment)
    • [Image Removed]
  • 3rd video - Pawn preset, but Query only (QueryOnly.mp4 attachment)
    • [Image Removed]

When the SkeletalMesh has no collision whatsoever, the Capsule correctly stays above the terrain. But when the SkeletalMesh has collisions (even query/probe), the Capsule dips under the terrain and then jumps back up upon uncrouching.

[Image Removed]

Interestingly, it only happens when standing still and works correctly while moving.

In our project, we need the SkeletalMesh to have collision because of the combat system, so the NoCollision solution isn’t possible.

How should I go above fixing this issue to support SkeletalMesh having collisions while using your StanceModifier? Thanks!

[Attachment Removed]

Steps to Reproduce[Attachment Removed]

Hello! Thanks for providing the videos. They were very helpful in reproducing the problem. I see that the issue occurs only during Crouch and Uncrouch transitions.

The problem occurs because:

  • during the uncrouch transition, FStanceModifier enqueues an instant teleportation effect to move the actor root upward to adjust for the change in capsule size
  • but that teleportation effect fails and has no effect due to the skeletal mesh having some intersection with the world.

Step by step, here is what happens. FStanceModifier::AdjustCapsule enqueues the teleport effect on crouch and uncrouch.

if (!bExpanding || MoverComp->GetVelocity().Length() <= 0)
{
	bApplyAdjustment = true;
	TSharedPtr<FTeleportEffect> TeleportEffect = MakeShared<FTeleportEffect>();
	TeleportEffect->TargetLocation = MoverComp->GetUpdatedComponentTransform().GetLocation() + (CapsuleOffset);
	MoverComp->QueueInstantMovementEffect(TeleportEffect);
}

FTeleportEffect::ApplyMovementEffect calls the frankly, quite old school AActor::TeleportTo method. Note that the reasoning to use AActor::TeleportTo is because the DefaultMovementSet is meant to match CMC as closely as possible.

if (OwnerActor->TeleportTo(TargetLocation, FinalTargetRotation))This calls UWorld::EncroachingBlockingGeometry, which will test whether the actor would intersect anything at the new desired location. Here is where Mover will deviate from CMC:

  • If the actor has an UMovementComponent, which UMoverComponent is not, it will only check the primitive root
  • Since UMoverComponent is not, we fallback to checking collision for all components, including the skeletal mesh

EncroachingBlockingGeometry checks each component by testing whether anything in the world at the new location collides with the component’s Object Type. See: ComponentEncroachesBlockingGeometry_WithAdjustment.

I’ll raise this with the Mover devs to discuss whether we should (a) avoid calling AActor::TeleportTo to execute the teleport, or (b) update EncroachingBlockingGeometry to have a similar exception for Mover like in MovementComponent but that might be tricky/hacky seeing as Mover is an optional plugin. In the meantime, you can use the following workaround

  • Introduce a new object channel like (PawnTraceable, default: ignored) in your project settings
  • Assign the skeletal mesh the PawnTraceable. Since the new object channel is default ignored, existing world geo will ignore the skeletal mesh.
  • Update your game’s combat logic to trace/overlap/block the new object channel instead of the Pawn channel

I hope that’s helpful!

[Attachment Removed]

For reference, here is the callstack for the adjusting teleport failing on Uncrouch:

>	[Inline Frame] UnrealEditor-Engine.dll!ComponentEncroachesBlockingGeometry_WithAdjustment::__l14::<lambda_3>::operator()() Line 1355	C++
 	UnrealEditor-Engine.dll!ComponentEncroachesBlockingGeometry_WithAdjustment(const UWorld * World, const AActor * TestActor, const UPrimitiveComponent * PrimComp, const UE::Math::TTransform<double> & TestWorldTransform, UE::Math::TVector<double> & OutProposedAdjustment, const TArray<AActor *,TSizedDefaultAllocator<32>> & IgnoreActors) Line 1355	C++
 	[Inline Frame] UnrealEditor-Engine.dll!ComponentEncroachesBlockingGeometry(const UWorld *) Line 1440	C++
 	UnrealEditor-Engine.dll!UWorld::EncroachingBlockingGeometry(const AActor * TestActor, UE::Math::TVector<double> TestLocation, UE::Math::TRotator<double> TestRotation, UE::Math::TVector<double> * ProposedAdjustment) Line 1546	C++
 	UnrealEditor-Engine.dll!UWorld::FindTeleportSpot(const AActor * TestActor, UE::Math::TVector<double> & TestLocation, UE::Math::TRotator<double> TestRotation) Line 1135	C++
 	UnrealEditor-Engine.dll!AActor::TeleportTo(const UE::Math::TVector<double> & DestLocation, const UE::Math::TRotator<double> & DestRotation, bool bIsATest, bool bNoCheck) Line 803	C++
 	UnrealEditor-Mover.dll!FTeleportEffect::ApplyMovementEffect(FApplyMovementEffectParams & ApplyEffectParams, FMoverSyncState & OutputState) Line 47	C++
 	UnrealEditor-Mover.dll!UMovementModeStateMachine::ApplyInstantEffects(FApplyMovementEffectParams & ApplyEffectParams, FMoverSyncState & OutputState) Line 936	C++
 	UnrealEditor-Mover.dll!UMovementModeStateMachine::OnSimulationTick(USceneComponent * UpdatedComponent, UPrimitiveComponent * UpdatedPrimitive, UMoverBlackboard * SimBlackboard, const FMoverTickStartData & StartState, const FMoverTimeStep & TimeStep, FMoverTickEndData & OutputState) Line 247	C++
 	UnrealEditor-Mover.dll!UMoverComponent::SimulationTick(const FMoverTimeStep & InTimeStep, const FMoverTickStartData & SimInput, FMoverTickEndData & SimOutput) Line 621	C++
 	UnrealEditor-Mover.dll!UMoverNetworkPredictionLiaisonComponent::SimulationTick(const FNetSimTimeStep & TimeStep, const TNetworkPredictionState<TNetworkPredictionStateTypes<FMoverInputCmdContext,FMoverSyncState,FMoverAuxStateContext>> & SimInput, const TNetSimOutput<TNetworkPredictionStateTypes<FMoverInputCmdContext,FMoverSyncState,FMoverAuxStateContext>> & SimOutput) Line 159	C++
 	UnrealEditor-Mover.dll!TTickUtil<FMoverActorModelDef>::DoTick<UMoverNetworkPredictionLiaisonComponent>(TInstanceData<FMoverActorModelDef> & Instance, TInstanceFrameState<FMoverActorModelDef>::FFrame & InputFrameData, TInstanceFrameState<FMoverActorModelDef>::FFrame & OutputFrameData, const FNetSimTimeStep & Step, const int CueTimeMS, ESimulationTickContext TickContext) Line 42	C++
 	UnrealEditor-Mover.dll!TLocalTickServiceBase<FMoverActorModelDef>::Tick_Internal<0>(const FNetSimTimeStep & Step, const FServiceTimeStep & ServiceStep) Line 123	C++
 	UnrealEditor-NetworkPrediction.dll!UNetworkPredictionWorldManager::BeginNewSimulationFrame_Internal(float DeltaTimeSeconds) Line 400	C++
 	UnrealEditor-NetworkPrediction.dll!UNetworkPredictionWorldManager::BeginNewSimulationFrame(UWorld * InWorld, ELevelTick InLevelTick, float DeltaTimeSeconds) Line 287	C++

[Attachment Removed]

Looking forward to hear the outcome of trying out the separate object channel!

After chatting internally with Mover devs, it seems the problem was already known as UE-308058. However, I filed a new bug UE-363516 with yours and my latest info. They will be addressed together. For Mover, we do want the option that some child components are tested for collision during teleports (including when crouching/uncrouching), but not for it to be the default.

[Attachment Removed]

Happy to help! I’ll close this question. If you periodically check up on the state of UE-363516, you’ll find out when we have addressed the bug internally.

Although, I suspect in general that the separate object channel will remain the cleanest way that ensures the skel mesh doesn’t affect anything that it shouldn’t.

[Attachment Removed]

Thanks a lot for such a detailed answer! I will definitely take a look and try your PawnTraceable solution.

[Attachment Removed]

The new object type helped, and we will be using it until this issue has been resolved on your side. Thanks again!

[Attachment Removed]