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