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.

Dev Log 1

Since I procrastinated on this blog for so long, I now have to remember what I did in July and early August.
I set up a GitHub project and repository and started to implement the basic locomotion. The character’s movement is essential and, in my opinion, should look polished. After a while, I was pretty unhappy with the result and looked for alternatives.

There was Lyra, ALS4, and AGR Pro. They all looked good, so I spent a couple of hours with them.
The Lyra project contains too many concepts I was unfamiliar with, but it’s well documented, and there seems to be a community growing around this project.
ALS4 has a big community, and it contains certain features I would like to have (e.g., different kinds of weapon poses). The downside is that it’s all blueprint, has this uber animation blueprint, and is not replicated.
Then I found the community version of it. It’s in C++ and also replicated. I felt immediately comfortable with it and skipped AGR Pro.
So I decided to use ALS4 for the main character and build the zombie locomotion from scratch based on the concepts of Lyra. After this project, I will maybe look for an Unreal job, and then it would be good to know how to work with them (e.g., multithreaded animations).

Then I implemented basic shooting and throwing mechanics, added pickable weapons and weapon switching, got familiar with the Unreal perception system in C++, and added some basic AI. Then I added a basic melee attack and a stealth finisher.

Maybe you ask yourself what the difference is between the prototype from before. The prototype contained some templates from the marketplace, had copy & paste code, a lot of “dead” code, and plenty of blueprints. Furthermore, I learned a lot during the prototyping. The current project has better architecture, and every feature is implemented in such a way that you can iterate over it and extend it. It’s also based on ALS4.

One of the last things I added, which took a long time, was bow and arrow shooting. I wanted the character to pick the string, and the arrow’s force should depend on how far the string gets pulled. After that, I added a basic ammo system.

In hindsight, it does not sound like much 🙁

So I am currently at the end of what I call “zombie week,” where I am extending the zombie features.

Unreal Engine Dismemberment 1

Overview
I will show you two ways to implement simple limb dismemberment in Ureal Engine.
The first way needs no extra assets but requires that you can separate your skeleton mesh.

The second way requires assets for the limbs you want to dismember.

Dismemberment 1

Your mesh has detachable limbs which do not “stretch”. You will know what I mean by “stretching” as soon as your try it yourself. If they are detachable, they should look like this.

All you have to do here is remove the bone’s constraints. If you also add an impulse, the bone will fly away.

Blueprint

C++

GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
GetMesh()->BreakConstraint({0.f, 0.f, 0.f}, {0.f, 0.f, 0.f}, "lowerarm_r");

Dismemberment 2

Let’s say your skeleton mesh is not detachable but instead starts to stretch. Then it gets a little bit more complicated. Let’s start with the setup.

First, create an actor blueprint and add StaticMesh or SkeletalMesh component depending on what kind of mesh your limb is.

Second, add a socket to the bone you want to detach. Add your mesh as a preview asset and adjust the location and rotation of the socket such that it will overlap with the original arm.

Now we have to hide the bone, which gets separated and spawn the limb at the same position.

Blueprint

C++

GetMesh()->HideBoneByName("BoneName", EPhysBodyOp::PBO_Term);

auto const BoneSocketLocation = GetMesh()->GetSocketLocation("YourSocket"));
auto const BoneSocketRotation = GetMesh()->GetSocketRotation("YourSocket");

FActorSpawnParameters SpawnParameters;
SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

GetWorld()->SpawnActor(YourLimbClass,
                  &BoneSocketLocation,
                  &BoneSocketRotation,
                  SpawnParameters);

That’s it. For example, you can execute these parts of code in an overlap or hit event. These provide you access to the overlapped actor and, therefore, access to the skeletal mesh.