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
// 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
}
// 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;
};
Hi, I had the same problem you did and got to this thread after searching.
From your part and the previous answer I put together my code and it now effectively tracks the head:
How I did it is implementing it in the character you want to trace, so not in the AI that looks for the character, but your character who you want to be visible, in my case this is called CharacterMaster.