FORCE AI to move towards target even if it's off NavMesh?

I want my AI to move towards it’s target, using regular Pathing if it’s on NavMesh, and just by applying input if it isn’t. REALLY simple right?

I cannot for the life of me figure out how to get this working, and I’ve been jumping between AI Tasks, BP Tasks, AI Controllers, Path Following Components and Movement Components for hours now. The movement system is so insanely complex and trying to find the point at which it says “Oh I can’t find a path, do this instead” is inducing nightmares. I want this to happen ALL the time, so I don’t want to be doing this in behaviour trees (not that I think it’s possible anyway)

All I want is a pawn that hovers and jumps, and every single step of the way I’ve been fighting against the AI system… about ready to have a mental breakdown.

This works but feels horrible. I’d rather make a custom MoveTo node and just have a flag that says “Move Directly if off NavMesh” to avoid generating loads of Move Requests :confused:


I ended up creating my own MoveTo task.

Are you doing this via C++ or BP?

That’s what I’ll likely do eventually, but the only issue I’m facing is that because my pawns hover, they are often off the NavMesh. It looks like the default engine pathing actually completely cancels pathing if the agent moves off of navmesh.

C++.

Yeah, I had that at rooftops too I think. Where I didn’t have a navmesh.

Would steering back in the direction of the navmesh bounds be sufficent?

That would work fine for me, the pawns can’t escape the level and there are no high walls at the edges, so it would do the job.

After a bit of sifting through engine source, it turns out that ‘Move Directly Toward’ doesn’t really do anything different other than sets ‘bUsePathfinding’ in the FAIMoveRequest. In AIController.cpp, the controller picks up on this and chooses to use the ‘Abstract Nav Data’ if this bool is false, resulting in the Navigation system returning a path with only two path points (start and end).

This is what I did in the end. I can now use just the one Move To node. Does the job for me!

MyGame_AIController.cpp



void AECGame_AIController::FindPathForMoveRequest(const FAIMoveRequest& MoveRequest, FPathFindingQuery& Query, FNavPathSharedPtr& OutPath) const
{
	UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld());
	if (NavSys)
	{
		FPathFindingResult PathResult = NavSys->FindPathSync(Query);
		if (PathResult.Result != ENavigationQueryResult::Error)
		{
			if (PathResult.IsSuccessful() && PathResult.Path.IsValid())
			{
				if (MoveRequest.IsMoveToActorRequest())
				{
					PathResult.Path->SetGoalActorObservation(*MoveRequest.GetGoalActor(), 100.0f);
				}

				PathResult.Path->EnableRecalculationOnInvalidation(true);
				OutPath = PathResult.Path;
				GEngine->AddOnScreenDebugMessage(-1, 0.5f, FColor::Red, TEXT("COMPLEX PATHING"));
			}
		}
		else if (MoveRequest.IsUsingPathfinding())
		{
			// Try to find path directly to the target
			// TODO: Since this is called quite often, maybe we should navigate to closest reachable polygon instead?
			const bool bValidQ2 = BuildBackupPathfindingQuery(MoveRequest, Query);
			if (bValidQ2)
			{
				PathResult = NavSys->FindPathSync(Query);
				if (PathResult.Result != ENavigationQueryResult::Error)
				{
					if (PathResult.IsSuccessful() && PathResult.Path.IsValid())
					{
						if (MoveRequest.IsMoveToActorRequest())
						{
							PathResult.Path->SetGoalActorObservation(*MoveRequest.GetGoalActor(), 100.0f);
						}

						PathResult.Path->EnableRecalculationOnInvalidation(true);
						OutPath = PathResult.Path;
						GEngine->AddOnScreenDebugMessage(-1, 0.5f, FColor::Red, TEXT("SIMPLE PATHING"));
					}
				}
			}
		}
// 		else
// 		{
// 			UE_VLOG(this, LogAIECNavigation, Error, TEXT("Trying to find path to %s resulted in Error")
// 				, MoveRequest.IsMoveToActorRequest() ? *GetNameSafe(MoveRequest.GetGoalActor()) : *MoveRequest.GetGoalLocation().ToString());
// 			UE_VLOG_SEGMENT(this, LogAIECNavigation, Error, GetPawn() ? GetPawn()->GetActorLocation() : FAISystem::InvalidLocation
// 				, MoveRequest.GetGoalLocation(), FColor::Red, TEXT("Failed move to %s"), *GetNameSafe(MoveRequest.GetGoalActor()));
// 		}
	}
}

bool AECGame_AIController::BuildBackupPathfindingQuery(const FAIMoveRequest& MoveRequest, FPathFindingQuery& OutQuery) const
{
	bool bResult = false;

	UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld());
	const ANavigationData* NavData = (NavSys == nullptr) ? nullptr : NavSys->GetAbstractNavData();

	if (NavData)
	{
		FVector GoalLocation = MoveRequest.GetGoalLocation();
		if (MoveRequest.IsMoveToActorRequest())
		{
			const INavAgentInterface* NavGoal = Cast<const INavAgentInterface>(MoveRequest.GetGoalActor());
			if (NavGoal)
			{
				const FVector Offset = NavGoal->GetMoveGoalOffset(this);
				GoalLocation = FQuatRotationTranslationMatrix(MoveRequest.GetGoalActor()->GetActorQuat(), NavGoal->GetNavAgentLocation()).TransformPosition(Offset);
			}
			else
			{
				GoalLocation = MoveRequest.GetGoalActor()->GetActorLocation();
			}
		}

		FSharedConstNavQueryFilter NavFilter = UNavigationQueryFilter::GetQueryFilter(*NavData, this, MoveRequest.GetNavigationFilter());
		OutQuery = FPathFindingQuery(*this, *NavData, GetNavAgentLocation(), GoalLocation, NavFilter);
		OutQuery.SetAllowPartialPaths(MoveRequest.IsUsingPartialPaths());

		if (GetPathFollowingComponent())
		{
			GetPathFollowingComponent()->OnPathfindingQuery(OutQuery);
		}

		bResult = true;
	}
// 	else
// 	{
// 		UE_VLOG(this, LogAIECNavigation, Warning, TEXT("Unable to find NavigationData instance while calling AAIController::BuildBackupPathfindingQuery"));
// 	}

	return bResult;
}


Sorry for being so late and being very new to this, but does this mean that if I don’t want to use a navmesh, because I don’t need obstacle avoidance at all, I can use move directly toward or just uncheck move to actor?

Rereading it, my understanding is that move directly towards changes nothing in terms of needing a navmesh. Is that right?