Download

[UE4, C++] AI Sight Perception problem - Enemy won't detect player and is trying to go outside the map!

Enemy just won’t detect the player, it is also trying to go outside the map. I am just following a tutorial on youtube - Unreal Engine & C++ Tutorial - 1st Person Shooter Game - YouTube, I’ve been trying to solve this problem for almost a month now and check the code twice, thrice! But the problem persists please help me, I am getting sick of it and think that it is a bug in ue4 itself also note that I have around 60 enemies in the map, maybe that’s the problem. I have tried it with one enemy and with two enemies but it’s still the same!

This is the code for AI Sight Perception for the enemy:

void AEnemy::OnSensed(const TArray<AActor*>& UpdatedActors)
{
for (int i = 0; i < UpdatedActors.Num(); i++)
{
FActorPerceptionBlueprintInfo Info;
AIPerComp->GetActorsPerception(UpdatedActors[i], Info);

  if (Info.LastSensedStimuli[0].WasSuccessfullySensed())
  {
  	FVector dir = UpdatedActors[i]->GetActorLocation() - GetActorLocation();
  	dir.Z = 0.0f;

  	CurrentVelocity = dir.GetSafeNormal() * MovementSpeed;

  	SetNewRotation(UpdatedActors[i]->GetActorLocation(), GetActorLocation());
  }

  else
  {
  	FVector dir = BaseLocation - GetActorLocation();
  	dir.Z = 0.0f;

  	if (dir.SizeSquared2D() > 1.0f)
  	{
  		CurrentVelocity = dir.GetSafeNormal() * MovementSpeed;
  		BackToBaseLocation = true;

  		SetNewRotation(BaseLocation, GetActorLocation());
  	}
  }

}
}

void AEnemy::SetNewRotation(FVector TargetPosition, FVector CurrentPosition)
{
FVector NewDirection = TargetPosition - CurrentPosition;
NewDirection.Z = 0.0f;

EnemyRotation = NewDirection.Rotation();

SetActorRotation(EnemyRotation);
}

Well, I never used this feature and I haven’t watched the video either because it’s a 2h30 video.
But I’ll try to help anyways, just making a guess.
In your code you have:

	SetNewRotation(UpdatedActors[i]->GetActorLocation(), GetActorLocation());

You are setting the rotation with the location. Is this correct?

1:31:48 is where he is doing the explanation of the AI Sight Perception, and how should I put it in code? (just started with ue4)

I tried using the code below:
SetNewRotation(UpdatedActors[i]->GetActorRotation(), GetActorRotation());
But it gives this error:

Ok, I checked the video.
This is correct after all:
SetNewRotation(BaseLocation, GetActorLocation());
Forget what I said.

It will be very hard to help you with this problem. If the problem happens with a single enemy, debug the code to help you understand what is going on. But it’s important that you understand the code you are copying or else you will have a hard time. Usually guessing is not an option in this cases.
Maybe if I had your project files I could help.

If you need to get rotation between two vectors/points use
UKismetMathLibrary::FindLookAtRotation docs.unrealengine.com/…

Also, it’s hard to spot an error just by looking at the code. I’d recommend you to use your IDE debug tools or simple prints to watch variable values. If you need visual representation use DrawDebug…
For example, use DrawDebugLine to see where your vector is pointing or DrawDebugSphere to check coordinates.
Try testing your code only with one enemy on an empty level, it’s way easier to debug a single instance.
In addition

I’ll send you the Enemy.h and Enemy.cpp file.

Enemy.h file:

#pragma once

#include “CoreMinimal.h”
#include “GameFramework/Character.h”
#include “Enemy.generated.h”

UCLASS()
class MONSTER_SHOOTER_API AEnemy : public ACharacter
{
GENERATED_BODY()

public:
// Sets default values for this character’s properties
AEnemy();

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;

UPROPERTY(EditAnywhere)
class UBoxComponent* DamageCollision;

UFUNCTION()
void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& Hit);

UPROPERTY(VisibleDefaultsOnly, Category = Enemy)
class UAIPerceptionComponent* AIPerComp;

UPROPERTY(VisibleDefaultsOnly, Category = Enemy)
class UAISenseConfig_Sight* SightConfig;

UFUNCTION()
void OnSensed(const TArray<AActor*>& UpdatedActors);

UPROPERTY(VisibleAnywhere, Category = Movement)
FRotator EnemyRotation;

UPROPERTY(VisibleAnywhere, Category = Movement)
FVector BaseLocation;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement)
FVector CurrentVelocity;

UPROPERTY(VisibleAnywhere, Category = Movement)
float MovementSpeed;

void SetNewRotation(FVector TargetPosition, FVector CurrentPosition);

bool BackToBaseLocation;
FVector NewLocation;
float DistanceSquared;

UPROPERTY(EditAnywhere, BlueprintReadOnly)
float Health = 100.0f;

UPROPERTY(EditAnywhere)
float DamageValue = 5.0f;

public:
void DealDamage(float DamageAmount);
};

Enemy.cpp file:

#include “Enemy.h”

#include “Components/BoxComponent.h”
#include “MonsterShooterCharacter.h”
#include “Perception/AIPerceptionComponent.h”
#include “Perception/AISenseConfig_Sight.h”

// Sets default values
AEnemy::AEnemy()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don’t need it.
PrimaryActorTick.bCanEverTick = true;

DamageCollision = CreateDefaultSubobject(TEXT(“Damage Collision”));
DamageCollision->SetupAttachment(RootComponent);

AIPerComp = CreateDefaultSubobject(TEXT(“AI Perception Component”));
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT(“Sight Config”));

SightConfig->SightRadius = 10000.0f;
SightConfig->LoseSightRadius = 10030.0f;
SightConfig->PeripheralVisionAngleDegrees = 90.0f;
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectFriendlies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
SightConfig->SetMaxAge(0.1f);

AIPerComp->ConfigureSense(*SightConfig);
AIPerComp->SetDominantSense(SightConfig->GetSenseImplementation());
AIPerComp->OnPerceptionUpdated.AddDynamic(this, &AEnemy::OnSensed);

CurrentVelocity = FVector::ZeroVector;
MovementSpeed = -375.0f;

DistanceSquared = BIG_NUMBER;
}

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

DamageCollision->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::OnHit);

BaseLocation = this->GetActorLocation();
}

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

if (!CurrentVelocity.IsZero())
{
NewLocation = GetActorLocation() + CurrentVelocity * DeltaTime;

  if (BackToBaseLocation)
  {
  	if ((NewLocation - BaseLocation).SizeSquared2D() > DistanceSquared)
  	{
  		DistanceSquared = (NewLocation - BaseLocation).SizeSquared2D();
  	}

  	else
  	{
  		CurrentVelocity = FVector::ZeroVector;
  		DistanceSquared = BIG_NUMBER;
  		BackToBaseLocation = false;

  		SetNewRotation(GetActorForwardVector(), GetActorLocation());
  	}
  }

  SetActorLocation(NewLocation);

}
}

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

}

void AEnemy::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& Hit)
{
AMonsterShooterCharacter* Char = Cast(OtherActor);

if (Char)
{
Char->DealDamage(DamageValue);
}
}

void AEnemy::OnSensed(const TArray<AActor*>& UpdatedActors)
{
for (int i = 0; i < UpdatedActors.Num(); i++)
{
FActorPerceptionBlueprintInfo Info;
AIPerComp->GetActorsPerception(UpdatedActors[i], Info);

  if (Info.LastSensedStimuli[0].WasSuccessfullySensed())
  {
  	FVector dir = UpdatedActors[i]->GetActorLocation() - GetActorLocation();
  	dir.Z = 0.0f;

  	CurrentVelocity = dir.GetSafeNormal() * MovementSpeed;

  	SetNewRotation(UpdatedActors[i]->GetActorLocation(), GetActorLocation());
  }

  else
  {
  	FVector dir = BaseLocation - GetActorLocation();
  	dir.Z = 0.0f;

  	if (dir.SizeSquared2D() > 1.0f)
  	{
  		CurrentVelocity = dir.GetSafeNormal() * MovementSpeed;
  		BackToBaseLocation = true;

  		SetNewRotation(BaseLocation, GetActorLocation());
  	}
  }

}
}

void AEnemy::SetNewRotation(FVector TargetPosition, FVector CurrentPosition)
{
FVector NewDirection = TargetPosition - CurrentPosition;
NewDirection.Z = 0.0f;

EnemyRotation = NewDirection.Rotation();

SetActorRotation(EnemyRotation);
}

void AEnemy::DealDamage(float DamageAmount)
{
Health -= DamageAmount;

if (Health <= 0.0f)
{
Destroy();
}
}

So what are you suggestion me to do? Should I follow the article which you’ve sent me? Also I’ve tried testing the same code on another empty level with one enemy, two enemies but still it doesn’t work.

I am understanding the code quite well and think that this code it correct, but there is some problem I am unaware of.

Unfortunately, I cannot compile your code due to the external dependencies so You need to debug your code yourself.

So the mistake can be caused by these points:

  • You don’t check that UpdatedActors[i] == Player before moving. As the result, AI moves to the latest spotted pawn which is likely not the player if you have ~60 pawns

  • Your enemy is trying to leave the map because you tell him to do so in Tick().
    But you did not tell him when to stop moving.

If these two things are not the problem, please describe what is exactly wrong and what behavior you are expecting. Also, it’d be nice if you could provide the link to the whole project so I can launch it on my computer.

So what are you suggesting me to do, please bear with me, I just started out with UE4 and am still discovering stuff.

What is exactly wrong and what behavior you are expecting?

The Problem I’m Facing:
The enemies mostly try to go outside the map through the corners of a map (square map), and around two enemies are inactive for some time then after about 10 seconds they also try to get outside the map through the corners. Another thing I have noticed is that they come back inside the maps through the walls and then some just fall down and die.
ezgif-2-8c7835ce57

The Behavior I’m Expecting:
The behavior I’m expecting is that all the enemies should move towards the player and attack him (no animation or anything) so that the player’s health decreases, The player can also attack the enemies and kill them.
ezgif-2-1f435d70a6

I see.
Basically, you get this behavior because you update location only once and you get the last spotted pawn.
So your current code logic

  1. Detect Actor
  2. SetUp velocity direction
  3. Move towards.

What you need to de instead

  1. Detect Actor
  2. Check DetectedActor == Player
  3. Save Reference to Player to access his location
  4. Before moving, recalculate the direction you need to move to if the player reference is valid.
  5. Finally move towards
  6. If you loose player just clear the player reference WasSuccessfullySensed() == false

I am extremely sorry what a fool I am, can you please tell me which code should be replaced with which?

Create variable in the Header file

UPROPERTY()
APawn* SpottedPlayer = nullptr;

And edit AEnemy::OnSensed function

void AEnemy::OnSensed(const TArray<AActor*>& UpdatedActors)
{
	for (int i = 0; i < UpdatedActors.Num(); i++)
	{
		if(UpdatedActors[i] == UGameplayStatics::GetPlayerPawn(GetWorld(),0))
		{
			FActorPerceptionBlueprintInfo Info;
			AIPerComp->GetActorsPerception(UpdatedActors[i], Info);
			
			if (Info.LastSensedStimuli[i].WasSuccessfullySensed())
			{
				SpottedPlayer = Cast<APawn>(UpdatedActors[I]);
			}
			else
			{
				SpottedPlayer = nullptr;
			}
		}
	}

And Change your Tick()

void AEnemy::Tick(float DeltaTime)
	{
		Super::Tick(DeltaTime);

		if(IsValid(SpottedPlayer))
		{
			CurrentVelocity = (SpottedPlayer->GetActorLocation() - GetActorLocation()) * MovementSpeed;
		}

		if (!CurrentVelocity.IsZero())
		{
			NewLocation = GetActorLocation() + CurrentVelocity * DeltaTime;

			if (BackToBaseLocation)
			{
				if ((NewLocation - BaseLocation).SizeSquared2D() > DistanceSquared)
				{
					DistanceSquared = (NewLocation - BaseLocation).SizeSquared2D();
				}

				else
				{
					CurrentVelocity = FVector::ZeroVector;
					DistanceSquared = BIG_NUMBER;
					BackToBaseLocation = false;

					SetNewRotation(GetActorForwardVector(), GetActorLocation());
				}
			}

			SetActorLocation(NewLocation);
		}
	}

That shall work. I can’t test it cause I don’t have your project files.

NOTE that this is not the best way to make AI movement. Most likely using unreal build-in movement with pathfinding would be a better option.

It’s not working, the enemies just stand still in their respected positions. I copied your code then I tried this too, still it doesn’t work.

void AEnemy::OnSensed(const TArray<AActor*>& UpdatedActors)
{
for (int i = 0; i < UpdatedActors.Num(); i++)
{
if (UpdatedActors[i] == UGameplayStatics::GetPlayerPawn(GetWorld(), 0))
{
FActorPerceptionBlueprintInfo Info;
AIPerComp->GetActorsPerception(UpdatedActors[i], Info);

  	if (Info.LastSensedStimuli[i].WasSuccessfullySensed())
  	{
  		SpottedPlayer = Cast<APawn>(UpdatedActors[i]);

  		FVector dir = UpdatedActors[i]->GetActorLocation() - GetActorLocation();
  		dir.Z = 0.0f;

  		CurrentVelocity = dir.GetSafeNormal() * MovementSpeed;

  		SetNewRotation(UpdatedActors[i]->GetActorLocation(), GetActorLocation());
  	}
  	else
  	{
  		SpottedPlayer = nullptr;

  		FVector dir = BaseLocation - GetActorLocation();
  		dir.Z = 0.0f;

  		if (dir.SizeSquared2D() > 1.0f)
  		{
  			CurrentVelocity = dir.GetSafeNormal() * MovementSpeed;
  			BackToBaseLocation = true;

  			SetNewRotation(BaseLocation, GetActorLocation());
  		}
  	}
  }

}
}

Well, I need your project files then to help you. At least I’ll be able to compile it.
Are you using some version control like Git?
Or you can just upload .rar file on google drive and share the link

Here you go, I sent you all the source code files in a WinRar Zip file.
Download them here