Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added AI Perception implementation, removed Pawn Sensing. #50

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ Saved
*.pdb
*.exe
*-DebugGame.dll
SurvivalGame/Binaries/Win64/SurvivalGame.exp
SurvivalGame/Binaries/Win64/SurvivalGame.lib
SurvivalGame/Binaries
SurvivalGame/Releases
*.log
*.target.xml
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include "SZombieCharacter.h"

/* AI Specific includes */
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AISenseConfig_Hearing.h"
#include "Perception/AIPerceptionComponent.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
Expand All @@ -16,6 +19,30 @@ ASZombieAIController::ASZombieAIController(const class FObjectInitializer& Objec
BehaviorComp = ObjectInitializer.CreateDefaultSubobject<UBehaviorTreeComponent>(this, TEXT("BehaviorComp"));
BlackboardComp = ObjectInitializer.CreateDefaultSubobject<UBlackboardComponent>(this, TEXT("BlackboardComp"));

/* Setup sight sense config */
UAISenseConfig_Sight* SightConfig = ObjectInitializer.CreateDefaultSubobject<UAISenseConfig_Sight>(this, TEXT("Sight Config"));
SightConfig->SightRadius = 2000;
SightConfig->LoseSightRadius = 2500;
SightConfig->PeripheralVisionAngleDegrees = 60.0f;
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->SetMaxAge(2.5f);

/* Setup hearing sense config */
UAISenseConfig_Hearing* HearingConfig = ObjectInitializer.CreateDefaultSubobject<UAISenseConfig_Hearing>(this, TEXT("Hearing Config"));
HearingConfig->HearingRange = 600.0f;
HearingConfig->LoSHearingRange = 1200.0f;
HearingConfig->DetectionByAffiliation.bDetectEnemies = true;
HearingConfig->DetectionByAffiliation.bDetectFriendlies = true;
HearingConfig->SetMaxAge(2.5f);

/* Configure AI perception */
UAIPerceptionComponent* AIPerceptionComp = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerceptionComp"));
AIPerceptionComp->OnTargetPerceptionUpdated.AddDynamic(this, &ASZombieAIController::OnTargetPerceptionUpdated);
AIPerceptionComp->ConfigureSense(*SightConfig);
AIPerceptionComp->SetDominantSense(SightConfig->GetSenseImplementation());
AIPerceptionComp->ConfigureSense(*HearingConfig);
SetPerceptionComponent(*AIPerceptionComp);

/* Match with the AI/ZombieBlackboard */
PatrolLocationKeyName = "PatrolLocation";
CurrentWaypointKeyName = "CurrentWaypoint";
Expand Down Expand Up @@ -56,6 +83,24 @@ void ASZombieAIController::UnPossess()
}


void ASZombieAIController::OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
ASBaseCharacter* Target = Cast<ASBaseCharacter>(Actor);

if (Target && (!Target->IsAlive() || !Stimulus.IsActive()))
{
Target = nullptr;
}

SetTargetEnemy(Target);
ASZombieCharacter* ZombieBot = Cast<ASZombieCharacter>(GetPawn());
if (ZombieBot)
{
ZombieBot->SetSensedTarget(Target ? true : false);
}
}


void ASZombieAIController::SetWaypoint(ASBotWaypoint* NewWaypoint)
{
if (BlackboardComp)
Expand Down Expand Up @@ -103,3 +148,9 @@ void ASZombieAIController::SetBlackboardBotType(EBotBehaviorType NewType)
BlackboardComp->SetValueAsEnum(BotTypeKeyName, (uint8)NewType);
}
}


ETeamAttitude::Type ASZombieAIController::GetTeamAttitudeTowards(const AActor & Other) const
{
return Cast<ASCharacter>(&Other) ? ETeamAttitude::Type::Hostile : ETeamAttitude::Type::Friendly;
}
101 changes: 15 additions & 86 deletions SurvivalGame/Source/SurvivalGame/Private/AI/SZombieCharacter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
#include "SBotWaypoint.h"
#include "SPlayerState.h"

/* AI Include */
#include "Perception/PawnSensingComponent.h"

// Sets default values
ASZombieCharacter::ASZombieCharacter(const class FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
Expand All @@ -19,12 +16,10 @@ ASZombieCharacter::ASZombieCharacter(const class FObjectInitializer& ObjectIniti
Because the zombie AIController is a blueprint in content and it's better to avoid content references in code. */
/*AIControllerClass = ASZombieAIController::StaticClass();*/

/* Our sensing component to detect players by visibility and noise checks. */
PawnSensingComp = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSensingComp"));
PawnSensingComp->SetPeripheralVisionAngle(60.0f);
PawnSensingComp->SightRadius = 2000;
PawnSensingComp->HearingThreshold = 600;
PawnSensingComp->LOSHearingThreshold = 1200;
/* Ignore this channel or it will absorb the trace impacts instead of the skeletal mesh */
GetCapsuleComponent()->SetCollisionResponseToChannel(COLLISION_WEAPON, ECR_Ignore);
GetCapsuleComponent()->SetCapsuleHalfHeight(96.0f, false);
GetCapsuleComponent()->SetCapsuleRadius(42.0f);

/* Ignore this channel or it will absorb the trace impacts instead of the skeletal mesh */
GetCapsuleComponent()->SetCollisionResponseToChannel(COLLISION_WEAPON, ECR_Ignore);
Expand Down Expand Up @@ -55,7 +50,6 @@ ASZombieCharacter::ASZombieCharacter(const class FObjectInitializer& ObjectIniti

/* By default we will not let the AI patrol, we can override this value per-instance. */
BotType = EBotBehaviorType::Passive;
SenseTimeOut = 2.5f;

/* Note: Visual Setup is done in the AI/ZombieCharacter Blueprint file */
}
Expand All @@ -65,12 +59,6 @@ void ASZombieCharacter::BeginPlay()
{
Super::BeginPlay();

/* This is the earliest moment we can bind our delegates to the component */
if (PawnSensingComp)
{
PawnSensingComp->OnSeePawn.AddDynamic(this, &ASZombieCharacter::OnSeePlayer);
PawnSensingComp->OnHearNoise.AddDynamic(this, &ASZombieCharacter::OnHearNoise);
}
if (MeleeCollisionComp)
{
MeleeCollisionComp->OnComponentBeginOverlap.AddDynamic(this, &ASZombieCharacter::OnMeleeCompBeginOverlap);
Expand All @@ -88,76 +76,6 @@ void ASZombieCharacter::BeginPlay()
}


void ASZombieCharacter::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);

/* Check if the last time we sensed a player is beyond the time out value to prevent bot from endlessly following a player. */
if (bSensedTarget && (GetWorld()->TimeSeconds - LastSeenTime) > SenseTimeOut
&& (GetWorld()->TimeSeconds - LastHeardTime) > SenseTimeOut)
{
ASZombieAIController* AIController = Cast<ASZombieAIController>(GetController());
if (AIController)
{
bSensedTarget = false;
/* Reset */
AIController->SetTargetEnemy(nullptr);

/* Stop playing the hunting sound */
BroadcastUpdateAudioLoop(false);
}
}
}


void ASZombieCharacter::OnSeePlayer(APawn* Pawn)
{
if (!IsAlive())
{
return;
}

if (!bSensedTarget)
{
BroadcastUpdateAudioLoop(true);
}

/* Keep track of the time the player was last sensed in order to clear the target */
LastSeenTime = GetWorld()->GetTimeSeconds();
bSensedTarget = true;

ASZombieAIController* AIController = Cast<ASZombieAIController>(GetController());
ASBaseCharacter* SensedPawn = Cast<ASBaseCharacter>(Pawn);
if (AIController && SensedPawn->IsAlive())
{
AIController->SetTargetEnemy(SensedPawn);
}
}


void ASZombieCharacter::OnHearNoise(APawn* PawnInstigator, const FVector& Location, float Volume)
{
if (!IsAlive())
{
return;
}

if (!bSensedTarget)
{
BroadcastUpdateAudioLoop(true);
}

bSensedTarget = true;
LastHeardTime = GetWorld()->GetTimeSeconds();

ASZombieAIController* AIController = Cast<ASZombieAIController>(GetController());
if (AIController)
{
AIController->SetTargetEnemy(PawnInstigator);
}
}


void ASZombieCharacter::PerformMeleeStrike(AActor* HitActor)
{
if (LastMeleeAttackTime > GetWorld()->GetTimeSeconds() - MeleeStrikeCooldown)
Expand Down Expand Up @@ -290,6 +208,17 @@ bool ASZombieCharacter::IsSprinting() const
}


void ASZombieCharacter::SetSensedTarget(bool bNewSensedTarget)
{
if (bSensedTarget != bNewSensedTarget)
{
BroadcastUpdateAudioLoop(bSensedTarget);
}

bSensedTarget = bNewSensedTarget;
}


void ASZombieCharacter::BroadcastUpdateAudioLoop_Implementation(bool bNewSensedTarget)
{
/* Start playing the hunting sound and the "noticed player" sound if the state is about to change */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include "SCharacterMovementComponent.h"
#include "SDamageType.h"


ASBaseCharacter::ASBaseCharacter(const class FObjectInitializer& ObjectInitializer)
/* Override the movement class from the base class to our own to support multiple speeds (eg. sprinting) */
: Super(ObjectInitializer.SetDefaultSubobjectClass<USCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
Expand All @@ -16,9 +15,6 @@ ASBaseCharacter::ASBaseCharacter(const class FObjectInitializer& ObjectInitializ
TargetingSpeedModifier = 0.5f;
SprintingSpeedModifier = 2.0f;

/* Noise emitter for both players and enemies. This keeps track of MakeNoise data and is used by the pawnsensing component in our SZombieCharacter class */
NoiseEmitterComp = CreateDefaultSubobject<UPawnNoiseEmitterComponent>(TEXT("NoiseEmitterComp"));

/* Don't collide with camera checks to keep 3rd person camera at position when zombies or other players are standing behind us */
GetMesh()->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);
GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "AIController.h"
#include "SCharacter.h"
#include "SBotWaypoint.h"
#include "Perception/AIPerceptionTypes.h"
#include "SZombieAIController.generated.h"

class UBehaviorTreeComponent;
Expand All @@ -24,6 +25,13 @@ class SURVIVALGAME_API ASZombieAIController : public AAIController

virtual void UnPossess() override;

/* Called whenever AI stimulus is updated */
UFUNCTION()
void OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus);

/* Used to determine whether or not an actor is hostile or friendly */
virtual ETeamAttitude::Type GetTeamAttitudeTowards(const AActor& Other) const override;

UBehaviorTreeComponent* BehaviorComp;

UBlackboardComponent* BlackboardComp;
Expand All @@ -41,7 +49,6 @@ class SURVIVALGAME_API ASZombieAIController : public AAIController
FName BotTypeKeyName;

public:

ASBotWaypoint* GetWaypoint();

ASBaseCharacter* GetTargetEnemy();
Expand Down
25 changes: 2 additions & 23 deletions SurvivalGame/Source/SurvivalGame/Public/AI/SZombieCharacter.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,18 @@ class SURVIVALGAME_API ASZombieCharacter : public ASBaseCharacter
{
GENERATED_BODY()

/* Last time the player was spotted */
float LastSeenTime;

/* Last time the player was heard */
float LastHeardTime;

/* Last time we attacked something */
float LastMeleeAttackTime;

/* Time-out value to clear the sensed position of the player. Should be higher than Sense interval in the PawnSense component not never miss sense ticks. */
UPROPERTY(EditDefaultsOnly, Category = "AI")
float SenseTimeOut;

/* Resets after sense time-out to avoid unnecessary clearing of target each tick */
bool bSensedTarget;

UPROPERTY(VisibleAnywhere, Category = "AI")
class UPawnSensingComponent* PawnSensingComp;

virtual void BeginPlay() override;

virtual void Tick(float DeltaSeconds) override;

protected:

virtual bool IsSprinting() const override;

/* Triggered by pawn sensing component when a pawn is spotted */
/* When using functions as delegates they need to be marked with UFUNCTION(). We assign this function to FSeePawnDelegate */
UFUNCTION()
void OnSeePlayer(APawn* Pawn);

UFUNCTION()
void OnHearNoise(APawn* PawnInstigator, const FVector& Location, float Volume);

UPROPERTY(VisibleAnywhere, Category = "Attacking")
UCapsuleComponent* MeleeCollisionComp;

Expand Down Expand Up @@ -125,4 +102,6 @@ class SURVIVALGAME_API ASZombieCharacter : public ASBaseCharacter

/* Change default bot type during gameplay */
void SetBotType(EBotBehaviorType NewType);

void SetSensedTarget(bool bNewSensedTarget);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ class SURVIVALGAME_API ASBaseCharacter : public ACharacter
{
GENERATED_BODY()

/* Tracks noise data used by the pawn sensing component */
UPawnNoiseEmitterComponent* NoiseEmitterComp;

public:
// Sets default values for this character's properties
ASBaseCharacter(const class FObjectInitializer& ObjectInitializer);
Expand Down