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;
};