Using AI perception sight to target head in UE5.3

Hello. I’m making third person game and have a problem with AI Perception in UE5.3. AI Perception Sight targets the center of the body of my character and I need it to use head of my character as the target.

So, I made research and found out that the only way to implement this is to use C++. My project was made with the help of blueprint only and I never used C++ before, so don’t judge me too harshly. I also find following instructions IAISightTargetInterface::CanBeSeenFrom | Unreal Engine 5.2 Documentation and used it to make a code.

First, I added to my BP_NPC_Base (this is base for all NPC character in my game) a AIPerception and set Sight as following.

Then I added to the BP_NPC_Base a parent character class in C++ named C_Character and used Visual Studio 2022 to implemented following code based on instructions mentioned above.
C_Character.h code is as follows

C_Character.cpp code is as follows

So, after that it seems that AISIghtTargetInterface was implemented as Inherent in Class Settings.

But nothing happens. The AIPerseption sight green sphere is still in the center of the torso of my character.

Did I miss something? Do you have any suggestions? I would be glad to have a piece of advice.

Here is the code for cpp file.

    // Fill out your copyright notice in the Description page of Project Settings.

#include "C_Character.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISense_Sight.h"
#include "Kismet/GameplayStatics.h"
#include "DrawDebugHelpers.h"

// Sets default values
AC_Character::AC_Character()
{
	// Set this character to call Tick() every frame. 
	PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void AC_Character::BeginPlay()
{
	Super::BeginPlay();
}

// Called every frame
void AC_Character::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

// Called to bind functionality to input
void AC_Character::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
}

// Implement the CanBeSeenFrom method
UAISense_Sight::EVisibilityResult AC_Character::CanBeSeenFrom(const FCanBeSeenFromContext& Context, FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested, float& OutSightStrength, int32* UserData, const FOnPendingVisibilityQueryProcessedDelegate* Delegate)
{
    // Get the player character
    ACharacter* PlayerCharacter = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);

    // Check if the player character is valid
    if (!IsValid(PlayerCharacter))
    {
        return UAISense_Sight::EVisibilityResult::NotVisible;  // Player character is not valid, consider not visible
    }

    // Get the head socket location of the player character
    FVector PlayerHeadLocation = FVector::ZeroVector;
    if (USkeletalMeshComponent* PlayerMesh = PlayerCharacter->GetMesh())
    {
        // Get the head bone name
        FName HeadBoneName = PlayerMesh->GetBoneName(PlayerMesh->GetBoneIndex("head"));
        // Get the head bone location
        PlayerHeadLocation = PlayerMesh->GetBoneLocation(HeadBoneName);
    }
    else
    {
        // Fallback to the middle of the player character if mesh or socket is not found
        PlayerHeadLocation = PlayerCharacter->GetActorLocation();
    }

    // Perform a line trace from the NPC's head to the player's head
    FHitResult HitResult;
    FCollisionQueryParams CollisionParams;
    CollisionParams.AddIgnoredActor(Context.IgnoreActor);

    bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, Context.ObserverLocation, PlayerHeadLocation, ECC_Visibility, CollisionParams);

    // Optional: Visualize the line trace for debugging purposes
    if (bHit)
    {
        DrawDebugLine(GetWorld(), Context.ObserverLocation, PlayerHeadLocation, FColor::Green, false, 0.1f, 0, 1.0f);
    }
    else
    {
        DrawDebugLine(GetWorld(), Context.ObserverLocation, PlayerHeadLocation, FColor::Red, false, 0.1f, 0, 1.0f);
    }

    // If the line trace hits the player's head, consider the player's head visible
    if (bHit && HitResult.GetActor() == PlayerCharacter)
    {
        OutSeenLocation = PlayerHeadLocation;
        OutNumberOfLoSChecksPerformed = 1;
        OutNumberOfAsyncLosCheckRequested = 0;
        OutSightStrength = 1.0f; // Max visibility strength
        return UAISense_Sight::EVisibilityResult::Visible; // Player head is visible
    }
    else
    {
        OutNumberOfLoSChecksPerformed = 1;
        OutNumberOfAsyncLosCheckRequested = 0;
        OutSightStrength = 0.0f; // No visibility
        return UAISense_Sight::EVisibilityResult::NotVisible; // Player head is not visible
    }

    // This line should be outside the if-else block
    return UAISense_Sight::EVisibilityResult::NotVisible; // Default to not visible 
}

Here is the code for h file

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Perception/AISightTargetInterface.h" // Include the AISightTargetInterface header
#include "C_Character.generated.h"

UCLASS()
class TRIDEVIATOE6_API AC_Character : public ACharacter, public IAISightTargetInterface // Add IAISightTargetInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AC_Character();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	// Implement the CanBeSeenFrom method from IAISightTargetInterface
	virtual UAISense_Sight::EVisibilityResult CanBeSeenFrom(const FCanBeSeenFromContext& Context, FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested, float& OutSightStrength, int32* UserData = nullptr, const FOnPendingVisibilityQueryProcessedDelegate* Delegate = nullptr) override;
};

You are supposed to override that function on your character, not the ai.

...
FVector SightTargetLocation = GetMesh()->GetSocketLocation("neck_01");

bool hit = GetWorld()->LineTraceSingleByChannel(HitResult, Context.ObserverLocation, SightTargetLocation, ECC_Visibility, FCollisionQueryParams(Name_AILineOfSight, false, Context.IgnoreActor));

AActor* actor = Cast<AActor>(HitResult.GetActor());

if (!hit || (IsValid(actor) && actor->IsOwnedBy(this))) {
	OutNumberOfLoSChecksPerformed = 1;
	OutNumberOfAsyncLosCheckRequested = 0;
	OutSightStrength = 1;
	//We hit the player
	return UAISense_Sight::EVisibilityResult::Visible;
}

OutNumberOfLoSChecksPerformed = 1;
OutNumberOfAsyncLosCheckRequested = 0;
OutSightStrength = 0;

return UAISense_Sight::EVisibilityResult::NotVisible;