In this post, Unreal Engine Developer course student Carlos shares his awesome detective work (and code) on getting AI sight perception to custom points…

On the way to creating a metal gear solid clone game, I was following the testing grounds videos. I already had guards with Sight sense to detect me.

At a certain point I put some barriers, what should then happen is that NPC shouldn’t see me standing up. But what I got was that the NPC was detecting me, which is wrong. I thought maybe the AI Sight perception only sees the actor location (only one point).

I started to search for the answer, I asked on gamedev.tv and the Unreal engine forum. Someone suggested I add a sphere collision to the head, add this sphere into a collision channel. It didn’t work, so I continued searching. I googled “Unreal engine 4 head perception” and found this link in this page someone said that I should implement IAISightTargetInterface and override the CanBeSeenFrom method.

So, i tried:

First include and implements IAISightTargetInterface in the .h file of the character class:

#pragma once
#include "Perception/AISightTargetInterface.h"
...

UCLASS(config=Game)
class ASnuffCharacter : public ACharacter, public IAISightTargetInterface

Then on the public part of my class add the definition of the method:

public:

	virtual bool CanBeSeenFrom(
		const FVector& ObserverLocation,
		FVector& OutSeenLocation,
		int32& NumberOfLoSChecksPerformed,
		float& OutSightStrength,
		const AActor* IgnoreActor = NULL
	) const;

And then finally the implementation of the method in the .cpp file:

bool ASnuffCharacter::CanBeSeenFrom(const FVector& ObserverLocation, FVector& OutSeenLocation, int32& NumberOfLoSChecksPerformed, float& OutSightStrength, const AActor* IgnoreActor) const
{
	static const FName NAME_AILineOfSight = FName(TEXT("TestPawnLineOfSight"));

	FHitResult HitResult;

	const bool bHit = GetWorld()->LineTraceSingleByObjectType(HitResult, ObserverLocation, GetActorLocation()
		, FCollisionObjectQueryParams(ECC_TO_BITFIELD(ECC_WorldStatic) | ECC_TO_BITFIELD(ECC_WorldDynamic)) // << Changed this line
		, FCollisionQueryParams(NAME_AILineOfSight, true, IgnoreActor));

	NumberOfLoSChecksPerformed++;

	if (bHit == false || (HitResult.Actor.IsValid() && HitResult.Actor->IsOwnedBy(this)))
	{
		UE_LOG(LogTemp, Error, TEXT("true"));
		OutSeenLocation = GetActorLocation();
		OutSightStrength = 1;

		return true;
	}

	UE_LOG(LogTemp, Error, TEXT("false"));
	OutSightStrength = 0;
	return false;
}

I then had some link errors on the compiling process, so I went onto the gamedev discord server and PocketNerd and PartlyAtomic helped me a lot (thank you so much guys!). The solution was adding “AIModule” on the dependency modules string array on the build.cs file.

Once the code was compiled I tested the behaviour of the code and what I got was the true log when I was inside the AI Sight range and he can see the the character’s location point, and false also inside the range but with the visibility blocked. Out the Sight range the method wasn’t called.

The only thing that I needed to do then was to get all the sockets name, and iterate trough the sockets array to check if the AI was able to percept some of them, and that’s the code of the method:

bool ASnuffCharacter::CanBeSeenFrom(const FVector& ObserverLocation, FVector& OutSeenLocation, int32& NumberOfLoSChecksPerformed, float& OutSightStrength, const AActor* IgnoreActor) const
{
	static const FName NAME_AILineOfSight = FName(TEXT("TestPawnLineOfSight"));

	FHitResult HitResult;

	auto sockets = GetMesh()->GetAllSocketNames();

	for (int i = 0; i < sockets.Num(); i++)
	{
		FVector socketLocation = GetMesh()->GetSocketLocation(sockets[i]);

		const bool bHitSocket = GetWorld()->LineTraceSingleByObjectType(HitResult, ObserverLocation, socketLocation
			, FCollisionObjectQueryParams(ECC_TO_BITFIELD(ECC_WorldStatic) | ECC_TO_BITFIELD(ECC_WorldDynamic)) // << Changed this line
			, FCollisionQueryParams(NAME_AILineOfSight, true, IgnoreActor));

		NumberOfLoSChecksPerformed++;

		if (bHitSocket == false || (HitResult.Actor.IsValid() && HitResult.Actor->IsOwnedBy(this))) {
			OutSeenLocation = socketLocation;
			OutSightStrength = 1;

			return true;
		}
	}

	const bool bHit = GetWorld()->LineTraceSingleByObjectType(HitResult, ObserverLocation, GetActorLocation()
		, FCollisionObjectQueryParams(ECC_TO_BITFIELD(ECC_WorldStatic) | ECC_TO_BITFIELD(ECC_WorldDynamic)) // << Changed this line
		, FCollisionQueryParams(NAME_AILineOfSight, true, IgnoreActor));

	NumberOfLoSChecksPerformed++;

	if (bHit == false || (HitResult.Actor.IsValid() && HitResult.Actor->IsOwnedBy(this)))
	{
		OutSeenLocation = GetActorLocation();
		OutSightStrength = 1;

		return true;
	}

	OutSightStrength = 0;
	return false;
}

And now it works perfectly 🙂