UPathFollowingComponent detects blockage for no reason when used with UNavMoverComponent

Hello,

We are using UNavMoverComponent and we have experienced Pawn’s movement ending with result Blocked for no apparent reason. The movement’s end seems to occur at a fixed time after the movement starts.

From my investigation this is caused by the block detection in UPathFollowingComponent, immediately after the desired number of location samples is reached. That explains why the movement ends after a fixed amount of time, as the samples are created with a predetermined frequency. This is also the moment when the function UNavPathFollowingComponent::IsBlocked actually executes the block detection for the first time since the movement’s start.

A block is detected because all of the location samples evaluate to the same value and thus they are at the same location as their center point :frowning: The location samples are results from UNavMoverComponent::GetFeetLocationBased and this function seems suspicious to me. For all of the location samples it fills the same Position (0.0, 0.0, -0.5) and same Base object (AStaticMeshActor with a label “Floor”). CachedBaseLocation is filled using the ContactLocationPosition. When such a sample is evaluated in FBasedPosition::operator* the base actor location is different from the CachedBaseLocation thus a new BaseLocation is used. However, this means that for all of the samples the same Position and Base actor (which also means the same BaseLocation) will be used, therefore resulting in the same result.

I would guess that in UNavMoverComponent::GetFeetLocationBased the ContactLocalPosition should be used to compute sample’s Position instead of CachedBaseLocation. But it is just a guess. Nevertheless the functions doesn’t seem to cooperate very well with the FBasedPosition.

Cheers,

Matej Marko

Steps to Reproduce
Create a level with a pawn using UNavMoverComponent.

Execute move with the pawn long enough for block detection in UPathFollowingComponent to execute the detection. In default setting it must collect 10 samples and sampling frequency is 0.5s, therefore 5s should be enough

Hello,

I have been thinking a little bit more about the issue and what seems strange to me is that UNavMoverComponent::GetFeetLocationBased sets the members of BasedPosition directly instead of using the Set function. I would expect something that would like this

FBasedPosition UNavMoverComponent::GetFeetLocationBased() const
{
	FBasedPosition BasedPosition(NULL, GetFeetLocation());
	
	if (MoverComponent.IsValid())
	{
		if (const UMoverBlackboard* Blackboard = MoverComponent->GetSimBlackboard())
		{
			FRelativeBaseInfo MovementBaseInfo;
			if (Blackboard->TryGet(CommonBlackboard::LastFoundDynamicMovementBase, MovementBaseInfo)) 
			{
				BasedPosition.Set(MovementBaseInfo.MovementBase->GetOwner(), MovementBaseInfo.ContactLocalPosition);
			}
		}
	}
 
	return BasedPosition;
}

Do you have any thoughts on that?

Cheers,

Matej

Hi Matej,

Sorry about the delay. I was just assigned this ticket and am looking into this, I should get back to you soon.

Best regards,

Vitor

Hello Vitor,

Thank you for the detailed explanation. I will try to answer all of the supplemental questions you have asked.

We are currently using the UE main (changelist 47892199). Using the regular releases proved difficult as cherry picking bug fixes often meant merging multiple previous changelists. Currently we are evaluating if using the UE main is a viable option or there will be too much overhead.

Setting the floor from Movable option to Static is a good workaround. The issue has been noticed only in a small test level so far, where the setting for the floor actor is the default value and hasn’t been changed after creating the level. In actual production level we use landscape for the terrain and not a static mesh actor.

We are currently using the Walking mode, are we expected to use NavWalking mode for NPCs? The reason being that NavWalking mode doesn’t perform collision checks since it can rely on using nav mesh? (Thus being more efficient for NPC navigation?}

Your suggested fix of UNavMoverComponent::GetFeetLocationBased seems to be working fine. The NPC is able to navigate and block detection only triggers when the NPC is really blocked.

Thanks,

Matej

Hello Vitor,

Thanks for the answer, that’s very useful information. We will keep that in mind.

Matej

Hi Matej,

Thank you for reporting this issue. I was able to reproduce the behavior you described on UE 5.6 and source versions (by the way, what version are you testing on?). I am now investigating possible causes for the problem, including function GetFeetLocationBased().

Meanwhile, I noticed that the odd behavior seems to happen only when using the “Walking” mode instead of “NavWalking”, and only when the actor being moved is based on a movable floor. Can you please try to switch to “NavWalking” mode and/or setting the floor to “static” to see if the issue goes away? If it does, would this be an acceptable workaround for your project?

Best regards,

Vitor

Hi Matej,

Ok, here’s what I found out:

Function UNavMoverComponent::GetFeetLocationBased() gets its “MovementBaseInfo” data from key “CommonBlackboard::LastFoundDynamicMovementBase” on its simulation blackboard (member TObjectPtr<UMoverBlackboard> SimBlackboard). This blackboard key is only set by WalkingMode and FallingMode (not by NavWalkingMode), and only if the controlled actor is based on a floor marked as Movable (not Static). Here’s the relevant call chain:

UWalkingMode::SimulationTick_Implementation()
  UFloorQueryUtils::FindFloor()
  UWalkingMode::CaptureFinalState()
    UWalkingMode::UpdateFloorAndBaseInfo()
      FRelativeBaseInfo::SetFromFloorResult()

On the call chain above, UFloorQueryUtils::FindFloor() looks for the floor below the controlled actor by sweeping a collision shape downward (using UWorld::SweepSingleByChannel()). Its results are passed on to FRelativeBaseInfo::SetFromFloorResult(), which fills out the “ContactLocalPosition” member by converting the floor result’s “HitResult.ImpactPoint” from world space to a space relative to “HitResult.Component” (using UBasedMovementUtils::TransformWorldLocationToBased()).

Now, when UNavMoverComponent::GetFeetLocationBased() attempts to convert an FRelativeBaseInfo to an FBasedPosition, it seems to be mixing things up. It sets CachedBaseLocation (which should be the world space position of the base actor) to ContactLocalPosition (the controlled actor’s position relative to a component in the base actor). It also sets Position (which should be the controlled actor’s position relative to the base actor) to “Location” (the world space position of a component in the base actor). As a result, FBasedPosition’s operator* will recalculate the world-space position of the controlled actor, and will do it using the wrong base location. The most useful information, ContactLocalPosition, is lost, resulting in the behavior you described well inside UPathFollowingComponent.

Here’s how I believe UNavMoverComponent::GetFeetLocationBased() should have been implemented:

FBasedPosition UNavMoverComponent::GetFeetLocationBased() const
{
	FBasedPosition BasedPosition(NULL, GetFeetLocation());
	
	if (MoverComponent.IsValid())
	{
		if (const UMoverBlackboard* Blackboard = MoverComponent->GetSimBlackboard())
		{
			FRelativeBaseInfo MovementBaseInfo;
			if (Blackboard->TryGet(CommonBlackboard::LastFoundDynamicMovementBase, MovementBaseInfo)) 
			{
				AActor* Base = MovementBaseInfo.MovementBase->GetOwner();
				FVector WorldPosition;
				UBasedMovementUtils::TransformBasedLocationToWorld(
					MovementBaseInfo.MovementBase.Get(),
					MovementBaseInfo.BoneName,
					MovementBaseInfo.ContactLocalPosition,
					WorldPosition);
 
				BasedPosition.Set(Base, WorldPosition);
			}
		}
	}
 
	return BasedPosition;
}

In the implementation above, I transformed MovementBaseInfo.ContactLocalPosition from its base-component-relative space back to world space, and allowed FBasedPosition::Set() to re-convert that world space position to base-actor-relative space, as well as update all internal cached variables. This not only “un-swaps” Position and CachedBaseLocation, but it also converts the base from a primitive component (as used in FRelativeBaseInfo) to its owning actor (as used in FBasedPosition). This back-and-forth sequence of conversions does not feel very polished, but it seems to resolve the issue in my tests, so please let me know if it also works for you.

I will now file an internal bug report with all this information for the devs responsible for the Mover plugin, and then provide you a tracking number for it. Note that, since this is still an experimental plugin, it is possible that it still undergoes significant changes instead of merely getting this fixed.

Best regards,

Vitor

Here’s the bug tracking number: UE-353449. This link should become accessible for tracking once the engine devs mark the bug report as public.

Let me know if there is anything else I can assist you with.

All the best,

Vitor

Yes, you should generally prefer using the NavWalking mode together with the NavMover component and AI-driven movement requests. I can’t say about performance, but NavWalking was made specifically for integration with NavMesh-related features, and the Mover examples appear to prefer NavWalking when using navmeshes, so I expect it to integrate better (now and in the future) with them. For example, I believe WalkingMode might attempt to cut corners without fully respecting the navmesh when moving between movement segments generated by the path following component.

Best,

Vitor