Nav Link Proxy SmartLinkReached event caught by wrong Nav Link

When using NavLinkProxy actors and the Event Received Smart Link Reached, I’ve run into an issue where the wrong NavLinkProxy actor in the world will catch the event. The actor that catches the event is not near the pawn and in fact doesn’t have to be on a nav mesh at all.

Has anyone else seen this?

The PathPt0 is correct, but its CustomLinkId is for the wrong nav link

// handle moving through custom nav links
if (PathPt0.CustomLinkId)
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld());
INavLinkCustomInterface* CustomNavLink = NavSys->GetCustomLink(PathPt0.CustomLinkId);
StartUsingCustomLink(CustomNavLink, SegmentEnd);

INavLinkCustomInterface* UNavigationSystemV1::GetCustomLink(uint32 UniqueLinkId) const
const FNavigationSystem::FCustomLinkOwnerInfo* LinkInfo = CustomLinksMap.Find(UniqueLinkId);
return (LinkInfo && LinkInfo->IsValid()) ? LinkInfo->LinkInterface : nullptr;

Hi . I’ve got the same issue.

It is driving me crazy. I’ve created several different NavLinkProxy classes (in c++ and also in bp) and from them I call specific methods on may AI character to trigger different behaviour.

After 3 days of trying around and debugging I decided to give the reference of the NavLinkProxy to my character and found out, that these are completely wrong.

It is like the character chooses NavLinkProxies which are even not nearby and are even from a completely other subclass of NavLinkProxies.

Have you found any solution for that?

I’ve created some example for my current case.

I have several different NavLinkProxies for different vaulting scenarios in my game.
In this case I positioned the following NavLinkProxy actors in the level


which all directly inherit from AIModule.NavLinkProxy

I furthermore used also blueprint implementations but they seem to have the same issue.

So I placed always 3 of these NavLinkProxies next to each other (by copying them with ALT + drag in the editor).

In some cases the AI uses different ones when running to me, because of a little bit different positioning, so I can try around.

Here is the implementation of the VaultOverObstacle_050_NavLink.

It simply calls a method on my character and it also gives a reference of the current NavLinkProxy to this character. After some testing I saw that this reference is the wrong one!

So I added a print string into the NavLinkProxy…

and even there the NavLinkProxy has sometimes the completely wrong reference.

In my last run the first NavLinkProxy should have been of class VaultOverObstacle_050_NavLink but it was the reference of the last left NavLinkProxy from the first screenshot VaultOverObstacle_200_NavLink3.

This is really a major issue because in this way the NavLinkProxy system is not usable for me.

Sorry, I should have posted the solution we went with. This is in NavigationSystem.cpp. Look for the following line in the RegisterCustomLink function.

// Fix for link id overwriting an existing link id in the CustomLinksMap
while (CustomLinksMap.Contains(LinkId))
LinkId = INavLinkCustomInterface::GetUniqueId();

void UNavigationSystemV1::RegisterCustomLink(INavLinkCustomInterface& CustomLink)
	ensureMsgf(CustomLink.GetLinkOwner() == nullptr || GetWorld() == CustomLink.GetLinkOwner()->GetWorld(),
		TEXT("Registering a link from a world different than the navigation system world should not happen."));

	uint32 LinkId = CustomLink.GetLinkId();

	// if there's already a link with that Id registered, assign new Id and mark dirty area
	// this won't fix baked data in static navmesh (in game), but every other case will regenerate affected tiles 
	if (CustomLinksMap.Contains(LinkId))
		//  Fix for link id overwriting an existing link id in the CustomLinksMap
		while (CustomLinksMap.Contains(LinkId))
			LinkId = INavLinkCustomInterface::GetUniqueId();

		UE_LOG(LogNavLink, VeryVerbose, TEXT("%s new navlink id %u."), ANSI_TO_TCHAR(__FUNCTION__), LinkId);

		UObject* CustomLinkOb = CustomLink.GetLinkOwner();
		UActorComponent* OwnerComp = Cast<UActorComponent>(CustomLinkOb);
		AActor* OwnerActor = OwnerComp ? OwnerComp->GetOwner() : Cast<AActor>(CustomLinkOb);

		if (OwnerActor)
			ENavLinkDirection::Type DummyDir = ENavLinkDirection::BothWays;
			FVector RelativePtA, RelativePtB;
			CustomLink.GetLinkData(RelativePtA, RelativePtB, DummyDir);

			const FTransform OwnerActorTM = OwnerActor->GetTransform();
			const FVector WorldPtA = OwnerActorTM.TransformPosition(RelativePtA);
			const FVector WorldPtB = OwnerActorTM.TransformPosition(RelativePtB);

			FBox LinkBounds(ForceInitToZero);
			LinkBounds += WorldPtA;
			LinkBounds += WorldPtB;

			AddDirtyArea(LinkBounds, FNavigationOctreeController::OctreeUpdate_Modifiers);

	CustomLinksMap.Add(LinkId, FNavigationSystem::FCustomLinkOwnerInfo(&CustomLink));
1 Like

Oh nice. I’ll check this out. Thx :slight_smile: