AI Controller LineOfSightTo() stops working - tips welcome

This issue cropped up and has been a serious thorn for me.

Unreal 5. Game works fine on original dev machine. Pulled the source and assets from source control onto another machine. This issue crops up.

I have isolated it down to the following. Within TickNode of a Behavior Tree Service I have the following line of code:

if (OwnerComp.GetAIOwner()->LineOfSightTo(pawn))

This is always returning false which causes all of my AI pawns to stand around doing nothing instead of following their B-Tree behaviors.

Again this is only happening on machines that pull source down, the original dev machine’s editor and code works fine.

I suspect something got b0rked pushing up to source, but am not sure what would stop an AI Controller from seeing the pawn object.

To confirm, yes the pawn object in this case is being seen and confirmed out into my logs.

Any tips or help on what property I may have missed that got zapped in the push to source control?

Hi @Auticus.Daerk !

There are at least two reasons that come to mind that could produce that:

  1. Someplace in your code the controller is unpossessing the pawn and doesn’t posses it back.
  2. The pawn could be destroyed and the controller (if it doesn’t it handle when the pawn gets destroyed) could be still in the world.

Alright, that removes number 2.

Hope this proves useful

EDIT: Just noticed you defined your own variable pawn, maybe it got garbage collected? See if it is defined as an UPROPERTY()

In this case the pawn that the AI is trying to see is the player character. Apologies… it is playerPawn as shown below - I used shorthand. This is in a B-Tree Service node component.

Now I have determined:

A) the player pawn is always found . Player pawn is set at the beginning of the tickNode method as:
APawn* playerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(),0);

A log statement under that fires if the pawn does not have a value (that log statement is never seen - the if always evaluates the playerPawn as having a valid pointer)

B) the AI Controller is always found. There is a line that states:
if (OwnerComp.GetAIOwner() == nullptr) that will then log out the ai owner was not found and then return out.

In both of these cases we always have a player pointer, and we always have an AI Owning Controller on the object in question.

However LineOfSightTo(playerPawn) always returns false even if player Pawn is standing right next to the AI pawn.

Full code here:

UMyBTService_PlayerLocationIfSeen::UMyBTService_PlayerLocationIfSeen()
{
	NodeName = "Set Current Player If Seen";
}

void UMyBTService_PlayerLocationIfSeen::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
	APawn* playerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);

	if (playerPawn == nullptr) return;
	if (OwnerComp.GetAIOwner() == nullptr) return;

	if (OwnerComp.GetAIOwner()->LineOfSightTo(playerPawn))
	{
		OwnerComp.GetBlackboardComponent()->SetValueAsObject(GetSelectedBlackboardKey(), playerPawn);
	}
	else
	{
		OwnerComp.GetBlackboardComponent()->ClearValue(GetSelectedBlackboardKey());
	}
}

The if statement checking line of sight is my issue. The false branch always fires and when I add logging around it, confirmed that LineOfSightTo is always false no matter where player controller is in the scene.

To reiterate: this code works 100% on the original dev machine.

Alright, thanks for posting your code, I don’t see a problem with what you currently have. But I have an idea that could show you where the problem is, are you by any chance using an editor which can use Breakpoints? If so, please put a breakpoint on the function it calls, that would be AAIController::LineOfSightTo() and post an image of where it is returning false.

Are you wanting a breakpoint in the B-Tree code I posted above?

I want to drill into LineOfSightTo in the engine but I only have the .h file for that.

Ahh don’t you have the source code of the engine? :thinking: This is going to be trickier

Nope. I know I can get it from the git but I don’t appear to have the source of the engine in this project. Thats something that escapes me in terms of how to do.

You could create a new class that inherits from AIController, reparent the class that you posted into the new class you created, override the LineOfSightTo function, copy the code below, and put a breakpoint there.

bool AMyNewAIController::LineOfSightTo(const AActor* Other, FVector ViewPoint, bool bAlternateChecks) const
{
	if (Other == nullptr)
	{
		return false;
	}

	if (ViewPoint.IsZero())
	{
		FRotator ViewRotation;
		GetActorEyesViewPoint(ViewPoint, ViewRotation);

		// if we still don't have a view point we simply fail
		if (ViewPoint.IsZero())
		{
			return false;
		}
	}

	FVector TargetLocation = Other->GetTargetLocation(GetPawn());

	FCollisionQueryParams CollisionParams(SCENE_QUERY_STAT(LineOfSight), true, this->GetPawn());
	CollisionParams.AddIgnoredActor(Other);

	bool bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, TargetLocation, ECC_Visibility, CollisionParams);
	if (!bHit)
	{
		return true;
	}

	// if other isn't using a cylinder for collision and isn't a Pawn (which already requires an accurate cylinder for AI)
	// then don't go any further as it likely will not be tracing to the correct location
	const APawn * OtherPawn = Cast<const APawn>(Other);
	if (!OtherPawn && Cast<UCapsuleComponent>(Other->GetRootComponent()) == NULL)
	{
		return false;
	}

	const FVector OtherActorLocation = Other->GetActorLocation();
	const float DistSq = (OtherActorLocation - ViewPoint).SizeSquared();
	if (DistSq > FARSIGHTTHRESHOLDSQUARED)
	{
		return false;
	}

	if (!OtherPawn && (DistSq > NEARSIGHTTHRESHOLDSQUARED))
	{
		return false;
	}

	float OtherRadius, OtherHeight;
	Other->GetSimpleCollisionCylinder(OtherRadius, OtherHeight);

	if (!bAlternateChecks || !bLOSflag)
	{
		//try viewpoint to head
		bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, OtherActorLocation + FVector(0.f, 0.f, OtherHeight), ECC_Visibility, CollisionParams);
		if (!bHit)
		{
			return true;
		}
	}

	if (!bSkipExtraLOSChecks && (!bAlternateChecks || bLOSflag))
	{
		// only check sides if width of other is significant compared to distance
		if (OtherRadius * OtherRadius / (OtherActorLocation - ViewPoint).SizeSquared() < 0.0001f)
		{
			return false;
		}
		//try checking sides - look at dist to four side points, and cull furthest and closest
		FVector Points[4];
		Points[0] = OtherActorLocation - FVector(OtherRadius, -1 * OtherRadius, 0);
		Points[1] = OtherActorLocation + FVector(OtherRadius, OtherRadius, 0);
		Points[2] = OtherActorLocation - FVector(OtherRadius, OtherRadius, 0);
		Points[3] = OtherActorLocation + FVector(OtherRadius, -1 * OtherRadius, 0);
		int32 IndexMin = 0;
		int32 IndexMax = 0;
		float CurrentMax = (Points[0] - ViewPoint).SizeSquared();
		float CurrentMin = CurrentMax;
		for (int32 PointIndex = 1; PointIndex<4; PointIndex++)
		{
			const float NextSize = (Points[PointIndex] - ViewPoint).SizeSquared();
			if (NextSize > CurrentMin)
			{
				CurrentMin = NextSize;
				IndexMax = PointIndex;
			}
			else if (NextSize < CurrentMax)
			{
				CurrentMax = NextSize;
				IndexMin = PointIndex;
			}
		}

		for (int32 PointIndex = 0; PointIndex<4; PointIndex++)
		{
			if ((PointIndex != IndexMin) && (PointIndex != IndexMax))
			{
				bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, Points[PointIndex], ECC_Visibility, CollisionParams);
				if (!bHit)
				{
					return true;
				}
			}
		}
	}
	return false;
}
1 Like

I’m not near the work station that has this, but I will check that out on return next week. Thank you for posting that :slight_smile:

Alright, hope it helps, tell me how it goes!

I’m going to guess that this means there are differences in what the original machine has, versus what the source control depot has.

If you don’t have engine debugging capability (because you’re not installed from source), it mioght be difficult to debug into it to see what’s failing.

However, here’s the code for LOSTo, and maybe you can glean something from there

bool AAIController::LineOfSightTo(const AActor* Other, FVector ViewPoint, bool bAlternateChecks) const
{
	if (Other == nullptr)
	{
		return false;
	}

	if (ViewPoint.IsZero())
	{
		FRotator ViewRotation;
		GetActorEyesViewPoint(ViewPoint, ViewRotation);

		// if we still don't have a view point we simply fail
		if (ViewPoint.IsZero())
		{
			return false;
		}
	}

	FVector TargetLocation = Other->GetTargetLocation(GetPawn());

	FCollisionQueryParams CollisionParams(SCENE_QUERY_STAT(LineOfSight), true, this->GetPawn());
	CollisionParams.AddIgnoredActor(Other);

	bool bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, TargetLocation, ECC_Visibility, CollisionParams);
	if (!bHit)
	{
		return true;
	}

	// if other isn't using a cylinder for collision and isn't a Pawn (which already requires an accurate cylinder for AI)
	// then don't go any further as it likely will not be tracing to the correct location
	const APawn * OtherPawn = Cast<const APawn>(Other);
	if (!OtherPawn && Cast<UCapsuleComponent>(Other->GetRootComponent()) == NULL)
	{
		return false;
	}

	const FVector OtherActorLocation = Other->GetActorLocation();
	const float DistSq = (OtherActorLocation - ViewPoint).SizeSquared();
	if (DistSq > FARSIGHTTHRESHOLDSQUARED)
	{
		return false;
	}

	if (!OtherPawn && (DistSq > NEARSIGHTTHRESHOLDSQUARED))
	{
		return false;
	}

	float OtherRadius, OtherHeight;
	Other->GetSimpleCollisionCylinder(OtherRadius, OtherHeight);

	if (!bAlternateChecks || !bLOSflag)
	{
		//try viewpoint to head
		bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, OtherActorLocation + FVector(0.f, 0.f, OtherHeight), ECC_Visibility, CollisionParams);
		if (!bHit)
		{
			return true;
		}
	}

	if (!bSkipExtraLOSChecks && (!bAlternateChecks || bLOSflag))
	{
		// only check sides if width of other is significant compared to distance
		if (OtherRadius * OtherRadius / (OtherActorLocation - ViewPoint).SizeSquared() < 0.0001f)
		{
			return false;
		}
		//try checking sides - look at dist to four side points, and cull furthest and closest
		FVector Points[4];
		Points[0] = OtherActorLocation - FVector(OtherRadius, -1 * OtherRadius, 0);
		Points[1] = OtherActorLocation + FVector(OtherRadius, OtherRadius, 0);
		Points[2] = OtherActorLocation - FVector(OtherRadius, OtherRadius, 0);
		Points[3] = OtherActorLocation + FVector(OtherRadius, -1 * OtherRadius, 0);
		int32 IndexMin = 0;
		int32 IndexMax = 0;
		float CurrentMax = (Points[0] - ViewPoint).SizeSquared();
		float CurrentMin = CurrentMax;
		for (int32 PointIndex = 1; PointIndex<4; PointIndex++)
		{
			const float NextSize = (Points[PointIndex] - ViewPoint).SizeSquared();
			if (NextSize > CurrentMin)
			{
				CurrentMin = NextSize;
				IndexMax = PointIndex;
			}
			else if (NextSize < CurrentMax)
			{
				CurrentMax = NextSize;
				IndexMin = PointIndex;
			}
		}

		for (int32 PointIndex = 0; PointIndex<4; PointIndex++)
		{
			if ((PointIndex != IndexMin) && (PointIndex != IndexMax))
			{
				bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, Points[PointIndex], ECC_Visibility, CollisionParams);
				if (!bHit)
				{
					return true;
				}
			}
		}
	}
	return false;
}
  • edit: i completely failed to notice someone else already supplied that before i hit reply :smiley: