Unreal AI Perception C++ – Friend or Enemy

If you tried to use the perception system from Unreal, you might have realized that you can’t configure it completely via blueprints. Especially the part where you configure how it should react to friendly, neutral, or hostile actors.

You have to use the following code in the Controller class. It will work for the sight perception if you add it to the Character class but fail for the hearing perception. This realization cost me some tears.

First, add the perception component and the wanted sense config.

class FIRSTOFUS_API AYourController : public AAIController
{
 GENERATED_BODY()
public:
 AYourController(); 

 UPROPERTY(VisibleAnywhere, Category = AI)
 TObjectPtr<UAIPerceptionComponent> AIPerceptionComponent = nullptr;
 TObjectPtr<class UAISenseConfig_Sight> AISenseConfigSight = nullptr;
 TObjectPtr<class UAISenseConfig_Hearing> AISenseConfigHearing = nullptr;
};
AYourController::AYourController()
{
 AIPerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>("PerceptionComponent");
 AISenseConfigSight = CreateDefaultSubobject<UAISenseConfig_Sight>("SenseSight");
 AISenseConfigSight->DetectionByAffiliation.bDetectEnemies = true;
 AISenseConfigSight->DetectionByAffiliation.bDetectFriendlies = false;
 AISenseConfigSight->DetectionByAffiliation.bDetectNeutrals = false;

 AISenseConfigHearing = CreateDefaultSubobject<UAISenseConfig_Hearing>("SenseHearing");
 AISenseConfigHearing->DetectionByAffiliation.bDetectEnemies = true;
 AISenseConfigHearing->DetectionByAffiliation.bDetectFriendlies = false;
 AISenseConfigHearing->DetectionByAffiliation.bDetectNeutrals = false;

 AIPerceptionComponent->ConfigureSense(*AISenseConfigSight);
 AIPerceptionComponent->ConfigureSense(*AISenseConfigHearing);
 AIPerceptionComponent->SetDominantSense(UAISenseConfig_Sight::StaticClass());
// Add an team id. I use e.g enums for the affiliation types 
AAIController::SetGenericTeamId(FGenericTeamId(1));
}

Then you have to overwrite the following function. There you can decide how the controller should respond to a given TeamId. In this example it’s friendly to the TeamIds 0 and 1;

class FIRSTOFUS_API AYourController : public AAIController
{
 GENERATED_BODY()
public:
 AYourController(); 
 virtual ETeamAttitude::Type GetTeamAttitudeTowards(const AActor& Other) const override;

 UPROPERTY(VisibleAnywhere, Category = AI)
 TObjectPtr<UAIPerceptionComponent> AIPerceptionComponent = nullptr;
 TObjectPtr<class UAISenseConfig_Sight> AISenseConfigSight = nullptr;
 TObjectPtr<class UAISenseConfig_Hearing> AISenseConfigHearing = nullptr;
};
ETeamAttitude::Type AYourController::GetTeamAttitudeTowards(const AActor& Other) const
{
 if (APawn const* OtherPawn = Cast<APawn>(&Other))
 {
  if (auto const TeamAgent = Cast<IGenericTeamAgentInterface>(OtherPawn->GetController()))
  {
                   if (TeamAgent->GetGenericTeamId() == FGenericTeamId(0)
                       || TeamAgent->GetGenericTeamId() == FGenericTeamId(1)) 
                   {
                        return ETeamAttitude::Friendly;
                   }
                   else 
                   {
                       return ETeamAttitude::Hostile;
                   }
  }
 }
 return ETeamAttitude::Neutral;
}

Last but not least, you can now add certain behaviors based on the triggered sense.

class FIRSTOFUS_API AYourController : public AAIController
{
 GENERATED_BODY()
public:
 AYourController(); 
 virtual ETeamAttitude::Type GetTeamAttitudeTowards(const AActor& Other) const override;
 virtual void BeginPlay() override;

 UFUNCTION()
 void OnTargetPerceptionUpdated_Delegate(AActor* Actor, FAIStimulus Stimulus);

 UPROPERTY(VisibleAnywhere, Category = AI)
 TObjectPtr<UAIPerceptionComponent> AIPerceptionComponent = nullptr;
 TObjectPtr<class UAISenseConfig_Sight> AISenseConfigSight = nullptr;
 TObjectPtr<class UAISenseConfig_Hearing> AISenseConfigHearing = nullptr;
};
void AYourController::BeginPlay()
{
 Super::BeginPlay();
 AIPerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(this, &ThisClass::OnTargetPerceptionUpdated_Delegate);
}

void AYourController::OnTargetPerceptionUpdated_Delegate(AActor* Actor, FAIStimulus Stimulus)
{
 switch (Stimulus.Type)
 {
  case 0:
   // react to sight stimulus
  case 1:
   // react to hearing;
  default:
   return;
 }
}

The other controllers need to inherit from IGenericTeamAgentInterface and set their team id. In this example I take the PlayerController

class FIRSTOFUS_API AOtherController : public APlayerController,  public IGenericTeamAgentInterface
{
 GENERATED_BODY()
public:
 FGenericTeamId TeamId = FGenericTeamId(4);
 virtual FGenericTeamId GetGenericTeamId() const override;
}
FGenericTeamId AOtherController::GetGenericTeamId() const
{
 return TeamId;
}

Now you can have, for example, enemies who can be distracted by the sound of throwable things and react if they see you but would not attack each other.

Leave a Reply

Your email address will not be published. Required fields are marked *