Files
UnrealEngine/Engine/Source/Runtime/AIModule/Private/Perception/PawnSensingComponent.cpp
2025-05-18 13:04:45 +08:00

482 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Perception/PawnSensingComponent.h"
#include "EngineGlobals.h"
#include "TimerManager.h"
#include "CollisionQueryParams.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/Controller.h"
#include "GameFramework/PlayerController.h"
#include "Engine/Engine.h"
#include "EngineUtils.h"
#include "AIController.h"
#include "Components/PawnNoiseEmitterComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PawnSensingComponent)
DECLARE_CYCLE_STAT(TEXT("Sensing"),STAT_AI_Sensing,STATGROUP_AI);
UPawnSensingComponent::UPawnSensingComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
LOSHearingThreshold = 2800.f;
HearingThreshold = 1400.0f;
SightRadius = 5000.0f;
PeripheralVisionAngle = 90.f;
PeripheralVisionCosine = FMath::Cos(FMath::DegreesToRadians(PeripheralVisionAngle));
SensingInterval = 0.5f;
HearingMaxSoundAge = 1.f;
bOnlySensePlayers = true;
bHearNoises = true;
bSeePawns = true;
PrimaryComponentTick.bCanEverTick = false;
bWantsInitializeComponent = true;
bAutoActivate = false;
bEnableSensingUpdates = true;
}
void UPawnSensingComponent::SetPeripheralVisionAngle(const float NewPeripheralVisionAngle)
{
PeripheralVisionAngle = NewPeripheralVisionAngle;
PeripheralVisionCosine = FMath::Cos(FMath::DegreesToRadians(PeripheralVisionAngle));
}
void UPawnSensingComponent::InitializeComponent()
{
Super::InitializeComponent();
SetPeripheralVisionAngle(PeripheralVisionAngle);
if (bEnableSensingUpdates)
{
bEnableSensingUpdates = false; // force an update
SetSensingUpdatesEnabled(true);
}
}
void UPawnSensingComponent::SetSensingUpdatesEnabled(const bool bEnabled)
{
if (bEnableSensingUpdates != bEnabled)
{
bEnableSensingUpdates = bEnabled;
if (bEnabled && SensingInterval > 0.f)
{
// Stagger initial updates so all sensors do not update at the same time (to avoid hitches).
const float InitialDelay = (SensingInterval * FMath::SRand()) + KINDA_SMALL_NUMBER;
SetTimer(InitialDelay);
}
else
{
SetTimer(0.f);
}
}
}
void UPawnSensingComponent::SetTimer(const float TimeInterval)
{
// Only necessary to update if we are the server
AActor* const Owner = GetOwner();
if (IsValid(Owner) && GEngine->GetNetMode(GetWorld()) < NM_Client)
{
Owner->GetWorldTimerManager().SetTimer(TimerHandle_OnTimer, this, &UPawnSensingComponent::OnTimer, TimeInterval, false);
}
}
void UPawnSensingComponent::SetSensingInterval(const float NewSensingInterval)
{
if (SensingInterval != NewSensingInterval)
{
SensingInterval = NewSensingInterval;
AActor* const Owner = GetOwner();
if (IsValid(Owner))
{
if (SensingInterval <= 0.f)
{
SetTimer(0.f);
}
else if (bEnableSensingUpdates)
{
float CurrentElapsed = Owner->GetWorldTimerManager().GetTimerElapsed(TimerHandle_OnTimer);
CurrentElapsed = FMath::Max(0.f, CurrentElapsed);
if (CurrentElapsed < SensingInterval)
{
// Extend lifetime by remaining time.
SetTimer(SensingInterval - CurrentElapsed);
}
else if (CurrentElapsed > SensingInterval)
{
// Basically fire next update, because time has already expired.
// Don't want to fire immediately in case an update tries to change the interval, looping endlessly.
SetTimer(KINDA_SMALL_NUMBER);
}
}
}
}
}
void UPawnSensingComponent::OnTimer()
{
SCOPE_CYCLE_COUNTER(STAT_AI_Sensing);
AActor* const Owner = GetOwner();
if (!IsValid(Owner) || !IsValid(Owner->GetWorld()))
{
return;
}
if (CanSenseAnything())
{
UpdateAISensing();
}
if (bEnableSensingUpdates)
{
SetTimer(SensingInterval);
}
};
AActor* UPawnSensingComponent::GetSensorActor() const
{
AActor* SensorActor = GetOwner();
AController* Controller = Cast<AController>(SensorActor);
if (IsValid(Controller))
{
// In case the owner is a controller, use the controlled pawn as the sensing location.
SensorActor = Controller->GetPawn();
}
if (!IsValid(SensorActor))
{
return NULL;
}
return SensorActor;
}
AController* UPawnSensingComponent::GetSensorController() const
{
AActor* SensorActor = GetOwner();
AController* Controller = Cast<AController>(SensorActor);
if (IsValid(Controller))
{
return Controller;
}
else
{
APawn* Pawn = Cast<APawn>(SensorActor);
if (IsValid(Pawn) && IsValid(Pawn->GetController()))
{
return Pawn->GetController();
}
}
return NULL;
}
bool UPawnSensingComponent::IsSensorActor(const AActor* Actor) const
{
return (Actor == GetSensorActor());
}
bool UPawnSensingComponent::HasLineOfSightTo(const AActor* Other) const
{
AController* SensorController = GetSensorController();
if (SensorController == NULL)
{
return false;
}
return SensorController->LineOfSightTo(Other, FVector::ZeroVector, true);
}
bool UPawnSensingComponent::CanSenseAnything() const
{
return ((bHearNoises && OnHearNoise.IsBound()) ||
(bSeePawns && OnSeePawn.IsBound()));
}
void UPawnSensingComponent::UpdateAISensing()
{
AActor* const Owner = GetOwner();
check(IsValid(Owner));
check(IsValid(Owner->GetWorld()));
if (bOnlySensePlayers)
{
for (FConstPlayerControllerIterator Iterator = Owner->GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
APlayerController* PC = Iterator->Get();
if (IsValid(PC))
{
APawn* Pawn = PC->GetPawn();
if (IsValid(Pawn) && !IsSensorActor(Pawn))
{
SensePawn(*Pawn);
}
}
}
}
else
{
for (APawn* Pawn : TActorRange<APawn>(Owner->GetWorld()))
{
if (!IsSensorActor(Pawn))
{
SensePawn(*Pawn);
}
}
}
}
void UPawnSensingComponent::SensePawn(APawn& Pawn)
{
// Visibility checks
bool bHasSeenPawn = false;
bool bHasFailedLineOfSightCheck = false;
if (bSeePawns && ShouldCheckVisibilityOf(&Pawn))
{
if (CouldSeePawn(&Pawn, true))
{
if (HasLineOfSightTo(&Pawn))
{
BroadcastOnSeePawn(Pawn);
bHasSeenPawn = true;
}
else
{
bHasFailedLineOfSightCheck = true;
}
}
}
// Sound checks
// No need to 'hear' something if you've already seen it!
if (bHasSeenPawn)
{
return;
}
// Might not be able to hear or react to the sound at all...
if (!bHearNoises || !OnHearNoise.IsBound())
{
return;
}
const UPawnNoiseEmitterComponent* NoiseEmitterComponent = Pawn.GetPawnNoiseEmitterComponent();
if (NoiseEmitterComponent && ShouldCheckAudibilityOf(&Pawn))
{
// ToDo: This should all still be refactored more significantly. There's no reason that we should have to
// explicitly check "local" and "remote" (i.e. Pawn-emitted and other-source-emitted) sounds separately here.
// The noise emitter should handle all of those details for us so the sensing component doesn't need to know about
// them at all!
if (IsNoiseRelevant(Pawn, *NoiseEmitterComponent, true) && CanHear(Pawn.GetActorLocation(), NoiseEmitterComponent->GetLastNoiseVolume(true), bHasFailedLineOfSightCheck))
{
BroadcastOnHearLocalNoise(Pawn, Pawn.GetActorLocation(), NoiseEmitterComponent->GetLastNoiseVolume(true));
}
else if (IsNoiseRelevant(Pawn, *NoiseEmitterComponent, false) && CanHear(NoiseEmitterComponent->LastRemoteNoisePosition, NoiseEmitterComponent->GetLastNoiseVolume(false), false))
{
BroadcastOnHearRemoteNoise(Pawn, NoiseEmitterComponent->LastRemoteNoisePosition, NoiseEmitterComponent->GetLastNoiseVolume(false));
}
}
}
void UPawnSensingComponent::BroadcastOnSeePawn(APawn& Pawn)
{
OnSeePawn.Broadcast(&Pawn);
}
void UPawnSensingComponent::BroadcastOnHearLocalNoise(APawn& Instigator, const FVector& Location, float Volume)
{
OnHearNoise.Broadcast(&Instigator, Location, Volume);
}
void UPawnSensingComponent::BroadcastOnHearRemoteNoise(APawn& Instigator, const FVector& Location, float Volume)
{
OnHearNoise.Broadcast(&Instigator, Location, Volume);
}
bool UPawnSensingComponent::CouldSeePawn(const APawn *Other, bool bMaySkipChecks) const
{
if (!Other)
{
return false;
}
const AActor* Owner = GetOwner();
if (!Owner)
{
return false;
}
FVector const OtherLoc = Other->GetActorLocation();
FVector const SensorLoc = GetSensorLocation();
FVector const SelfToOther = OtherLoc - SensorLoc;
// check max sight distance
FVector::FReal const SelfToOtherDistSquared = SelfToOther.SizeSquared();
if (SelfToOtherDistSquared > FMath::Square(SightRadius))
{
return false;
}
// may skip if more than some fraction of maxdist away (longer time to acquire)
if (bMaySkipChecks && (FMath::Square(FMath::FRand()) * SelfToOtherDistSquared > FMath::Square(0.4f * SightRadius)))
{
return false;
}
// UE_LOG(LogPath, Warning, TEXT("DistanceToOtherSquared = %f, SightRadiusSquared: %f"), SelfToOtherDistSquared, FMath::Square(SightRadius));
// check field of view
FVector const SelfToOtherDir = SelfToOther.GetSafeNormal();
FVector const MyFacingDir = GetSensorRotation().Vector();
// UE_LOG(LogPath, Warning, TEXT("DotProductFacing: %f, PeripheralVisionCosine: %f"), SelfToOtherDir | MyFacingDir, PeripheralVisionCosine);
return ((SelfToOtherDir | MyFacingDir) >= PeripheralVisionCosine);
}
bool UPawnSensingComponent::IsNoiseRelevant(const APawn& Pawn, const UPawnNoiseEmitterComponent& NoiseEmitterComponent, bool bSourceWithinNoiseEmitter) const
{
// If sound has no volume, it's not relevant.
if (NoiseEmitterComponent.GetLastNoiseVolume(bSourceWithinNoiseEmitter) <= 0.f)
{
return false;
}
float LastNoiseTime = NoiseEmitterComponent.GetLastNoiseTime(bSourceWithinNoiseEmitter);
// If the sound is too old, it's not relevant.
if (Pawn.GetWorld()->TimeSince(LastNoiseTime) > HearingMaxSoundAge)
{
return false;
}
// If there's no sensor actor or sensor was created since the noise was emitted, it's irrelevant.
AActor* SensorActor = GetSensorActor();
if (!SensorActor || (LastNoiseTime < SensorActor->CreationTime))
{
return false;
}
return true;
}
FVector UPawnSensingComponent::GetSensorLocation() const
{
FVector SensorLocation(FVector::ZeroVector);
const AActor* SensorActor = GetSensorActor();
if (SensorActor != NULL)
{
FRotator ViewRotation;
SensorActor->GetActorEyesViewPoint(SensorLocation, ViewRotation);
}
return SensorLocation;
}
FRotator UPawnSensingComponent::GetSensorRotation() const
{
FRotator SensorRotation(FRotator::ZeroRotator);
const AActor* SensorActor = GetSensorActor();
if (SensorActor != NULL)
{
SensorRotation = SensorActor->GetActorRotation();
}
return SensorRotation;
}
bool UPawnSensingComponent::CanHear(const FVector& NoiseLoc, float Loudness, bool bFailedLOS) const
{
if (Loudness <= 0.f)
{
return false;
}
const AActor* const Owner = GetOwner();
if (!IsValid(Owner))
{
return false;
}
FVector const HearingLocation = GetSensorLocation();
FVector::FReal const LoudnessAdjustedDistSq = (HearingLocation - NoiseLoc).SizeSquared()/(Loudness*Loudness);
if (LoudnessAdjustedDistSq <= FMath::Square(HearingThreshold))
{
// Hear even occluded sounds within HearingThreshold
return true;
}
// check if sound close enough to do LOS check, and LOS hasn't already failed
if (bFailedLOS || (LoudnessAdjustedDistSq > FMath::Square(LOSHearingThreshold)))
{
return false;
}
// check if sound is occluded
return !Owner->GetWorld()->LineTraceTestByChannel(HearingLocation, NoiseLoc, ECC_Visibility, FCollisionQueryParams(SCENE_QUERY_STAT(CanHear), true, Owner));
}
bool UPawnSensingComponent::ShouldCheckVisibilityOf(APawn *Pawn) const
{
const bool bPawnIsPlayer = (Pawn->GetController() && Pawn->GetController()->PlayerState);
if (!bSeePawns || (bOnlySensePlayers && !bPawnIsPlayer))
{
return false;
}
if (bPawnIsPlayer && AAIController::AreAIIgnoringPlayers())
{
return false;
}
if (Pawn->IsHidden())
{
return false;
}
return true;
}
bool UPawnSensingComponent::ShouldCheckAudibilityOf(APawn* Pawn) const
{
const bool bPawnIsPlayer = (Pawn->GetController() && Pawn->GetController()->PlayerState);
if (!bHearNoises || (bOnlySensePlayers && !bPawnIsPlayer))
{
return false;
}
if (bPawnIsPlayer && AAIController::AreAIIgnoringPlayers())
{
return false;
}
return true;
}