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

927 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Perception/AIPerceptionComponent.h"
#include "Async/TaskGraphInterfaces.h"
#include "GameFramework/Controller.h"
#include "AIController.h"
#include "Perception/AISenseConfig.h"
#include "Stats/Stats.h"
#include "VisualLogger/VisualLogger.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AIPerceptionComponent)
#if WITH_GAMEPLAY_DEBUGGER_MENU
#include "GameplayDebuggerTypes.h"
#include "GameplayDebuggerCategory.h"
#endif // WITH_GAMEPLAY_DEBUGGER_MENU
DECLARE_CYCLE_STAT(TEXT("Perception Component ProcessStimuli"), STAT_AI_PercepComp_ProcessStimuli, STATGROUP_AI);
DECLARE_CYCLE_STAT(TEXT("Requesting UAIPerceptionComponent::RemoveDeadData call from within a const function"),
STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData,
STATGROUP_TaskGraphTasks);
//----------------------------------------------------------------------//
// FActorPerceptionInfo
//----------------------------------------------------------------------//
void FActorPerceptionInfo::Merge(const FActorPerceptionInfo& Other)
{
for (uint32 Index = 0; Index < FAISenseID::GetSize(); ++Index)
{
if (LastSensedStimuli[Index].GetAge() > Other.LastSensedStimuli[Index].GetAge())
{
LastSensedStimuli[Index] = Other.LastSensedStimuli[Index];
}
}
}
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
FActorPerceptionBlueprintInfo::FActorPerceptionBlueprintInfo(const FActorPerceptionInfo& Info)
{
Target = Info.Target.Get();
LastSensedStimuli = Info.LastSensedStimuli;
bIsHostile = Info.bIsHostile;
bIsFriendly = Info.bIsFriendly;
}
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
FActorPerceptionUpdateInfo::FActorPerceptionUpdateInfo(const int32 InTargetId, const TWeakObjectPtr<AActor>& InTarget, const FAIStimulus& InStimulus)
: TargetId(InTargetId)
, Target(InTarget)
, Stimulus(InStimulus)
{
}
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
const int32 UAIPerceptionComponent::InitialStimuliToProcessArraySize = 10;
UAIPerceptionComponent::UAIPerceptionComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, PerceptionListenerId(FPerceptionListenerID::InvalidID())
, bCleanedUp(false)
{
bForgetStaleActors = GET_AI_CONFIG_VAR(bForgetStaleActors);
}
void UAIPerceptionComponent::RequestStimuliListenerUpdate()
{
UAIPerceptionSystem* AIPerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld());
if (AIPerceptionSys != nullptr)
{
AIPerceptionSys->UpdateListener(*this);
}
}
namespace
{
struct FConfigOfSenseID
{
FConfigOfSenseID(const FAISenseID& InSenseID)
: SenseID(InSenseID)
{}
bool operator()(const UAISenseConfig* SenseConfig) const
{
return SenseConfig && SenseConfig->GetSenseID() == SenseID;
}
const FAISenseID SenseID;
};
}
const UAISenseConfig* UAIPerceptionComponent::GetSenseConfig(const FAISenseID& SenseID) const
{
int32 ConfigIndex = SensesConfig.IndexOfByPredicate(FConfigOfSenseID(SenseID));
return ConfigIndex != INDEX_NONE ? SensesConfig[ConfigIndex] : nullptr;
}
UAISenseConfig* UAIPerceptionComponent::GetSenseConfig(const FAISenseID& SenseID)
{
int32 ConfigIndex = SensesConfig.IndexOfByPredicate(FConfigOfSenseID(SenseID));
return ConfigIndex != INDEX_NONE ? SensesConfig[ConfigIndex] : nullptr;
}
void UAIPerceptionComponent::PostInitProperties()
{
Super::PostInitProperties();
if (DominantSense)
{
DominantSenseID = UAISense::GetSenseID(DominantSense);
}
}
void UAIPerceptionComponent::ConfigureSense(UAISenseConfig& Config)
{
// first check if we're reconfiguring a sense
bool bIsNewConfig = true;
for (TObjectPtr<UAISenseConfig>& SenseConfig : SensesConfig)
{
if (SenseConfig != nullptr && SenseConfig->GetClass() == Config.GetClass())
{
SenseConfig = &Config;
bIsNewConfig = false;
break;
}
}
if (bIsNewConfig)
{
SensesConfig.Add(&Config);
}
if (IsRegistered())
{
UAIPerceptionSystem* AIPerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld());
if (AIPerceptionSys != nullptr)
{
if (bIsNewConfig)
{
RegisterSenseConfig(Config, *AIPerceptionSys);
}
else
{
SetMaxStimulusAge(Config.GetSenseID(), Config.GetMaxAge());
}
AIPerceptionSys->OnListenerConfigUpdated(Config.GetSenseID(), *this);
}
}
// else the sense will be auto-configured during OnRegister
}
UAIPerceptionComponent::TAISenseConfigConstIterator UAIPerceptionComponent::GetSensesConfigIterator() const
{
return ToRawPtrTArrayUnsafe(SensesConfig).CreateConstIterator();
}
void UAIPerceptionComponent::SetMaxStimulusAge(const FAISenseID SenseID, float MaxAge)
{
if (!ensureMsgf(SenseID.IsValid(), TEXT("Sense must exist to update max age")))
{
return;
}
if (MaxActiveAge.IsValidIndex(SenseID) == false)
{
MaxActiveAge.AddUninitialized(SenseID - MaxActiveAge.Num() + 1);
}
MaxActiveAge[SenseID] = MaxAge;
// @todo process all data already gathered and see if any _still_active_ stimuli
// got it's expiration prolonged, with SetExpirationAge
}
void UAIPerceptionComponent::OnRegister()
{
Super::OnRegister();
bCleanedUp = false;
AActor* Owner = GetOwner();
if (Owner)
{
Owner->OnEndPlay.AddUniqueDynamic(this, &UAIPerceptionComponent::OnOwnerEndPlay);
}
UAIPerceptionSystem* AIPerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld());
if (AIPerceptionSys != nullptr)
{
PerceptionFilter.Clear();
if (SensesConfig.Num() > 0)
{
// set up perception listener based on SensesConfig
for (auto SenseConfig : SensesConfig)
{
if (SenseConfig)
{
RegisterSenseConfig(*SenseConfig, *AIPerceptionSys);
}
}
AIPerceptionSys->UpdateListener(*this);
}
}
AIOwner = Cast<AAIController>(Owner);
if (AIOwner)
{
if (AIOwner->GetAIPerceptionComponent() == nullptr)
{
// this should not be needed but apparently AAIController::PostRegisterAllComponents
// gets called component's OnRegister
AIOwner->SetPerceptionComponent(*this);
}
else
{
ensure(AIOwner->GetAIPerceptionComponent() == this || (AIOwner->GetWorld() && AIOwner->GetWorld()->WorldType != EWorldType::Editor));
}
}
else if (Owner)
{
UE_CVLOG_UELOG(Owner->GetWorld() && (Owner->GetWorld()->WorldType != EWorldType::Editor), Owner, LogAIPerception, Log, TEXT("%s: Owner %s is not an AIController so make sure you bind to OnPerceptionUpdated, otherwise you won't get notified about updated actor perception events"), ANSI_TO_TCHAR(__FUNCTION__), *Owner->GetName());
}
}
void UAIPerceptionComponent::RegisterSenseConfig(const UAISenseConfig& SenseConfig, UAIPerceptionSystem& AIPerceptionSys)
{
const TSubclassOf<UAISense> SenseImplementation = SenseConfig.GetSenseImplementation();
if (SenseImplementation)
{
// make sure it's registered with perception system
const FAISenseID SenseID = AIPerceptionSys.RegisterSenseClass(SenseImplementation);
check(SenseID.IsValid());
if (SenseConfig.GetStartsEnabled())
{
PerceptionFilter.AcceptChannel(SenseID);
}
SetMaxStimulusAge(SenseID, SenseConfig.GetMaxAge());
}
}
void UAIPerceptionComponent::OnUnregister()
{
CleanUp();
Super::OnUnregister();
}
void UAIPerceptionComponent::OnOwnerEndPlay(AActor* Actor, EEndPlayReason::Type EndPlayReason)
{
if (EndPlayReason != EEndPlayReason::EndPlayInEditor && EndPlayReason != EEndPlayReason::Quit)
{
CleanUp();
}
}
void UAIPerceptionComponent::CleanUp()
{
if (bCleanedUp == false)
{
ForgetAll();
UAIPerceptionSystem* AIPerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld());
if (AIPerceptionSys != nullptr)
{
AIPerceptionSys->UnregisterListener(*this);
AActor* MutableBodyActor = GetMutableBodyActor();
if (MutableBodyActor)
{
AIPerceptionSys->UnregisterSource(*MutableBodyActor);
}
}
if (HasAnyFlags(RF_BeginDestroyed) == false)
{
AActor* Owner = GetOwner();
if (Owner != nullptr)
{
Owner->OnEndPlay.RemoveDynamic(this, &UAIPerceptionComponent::OnOwnerEndPlay);
}
}
bCleanedUp = true;
}
}
void UAIPerceptionComponent::BeginDestroy()
{
if (!HasAnyFlags(RF_ClassDefaultObject))
{
CleanUp();
}
Super::BeginDestroy();
}
void UAIPerceptionComponent::UpdatePerceptionAllowList(const FAISenseID Channel, const bool bNewValue)
{
// Return if we don't have a Sense Config as it doesn't make sense to update the perception allow list.
// Also modifying this often requires the Sense Config further along the call stack.
if (GetSenseConfig(Channel) == nullptr)
{
UE_VLOG_UELOG(GetOwner(), LogAIPerception, Warning, TEXT("%s: %s: Channel has no Sense Config. Bailing out!!"), ANSI_TO_TCHAR(__FUNCTION__), *Channel.Name.ToString());
return;
}
const bool bCurrentValue = PerceptionFilter.ShouldRespondToChannel(Channel);
if (bNewValue != bCurrentValue)
{
bNewValue ? PerceptionFilter.AcceptChannel(Channel) : PerceptionFilter.FilterOutChannel(Channel);
RequestStimuliListenerUpdate();
}
}
bool UAIPerceptionComponent::GetFilteredActors(const TFunctionRef<bool(const FActorPerceptionInfo&)>& Predicate, TArray<AActor*>& OutActors) const
{
bool bDeadDataFound = false;
OutActors.Reserve(PerceptualData.Num());
for (FActorPerceptionContainer::TConstIterator DataIt = GetPerceptualDataConstIterator(); DataIt; ++DataIt)
{
const FActorPerceptionInfo& ActorPerceptionInfo = DataIt->Value;
if (Predicate(ActorPerceptionInfo))
{
if (AActor* Actor = ActorPerceptionInfo.Target.Get())
{
OutActors.Add(Actor);
}
else
{
bDeadDataFound = true;
}
}
}
return bDeadDataFound;
}
void UAIPerceptionComponent::GetHostileActors(TArray<AActor*>& OutActors) const
{
const bool bDeadDataFound = GetFilteredActors([](const FActorPerceptionInfo& ActorPerceptionInfo) {
return (ActorPerceptionInfo.bIsHostile && ActorPerceptionInfo.HasAnyKnownStimulus());
}, OutActors);
if (bDeadDataFound)
{
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateUObject(const_cast<UAIPerceptionComponent*>(this), &UAIPerceptionComponent::RemoveDeadData),
GET_STATID(STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData), nullptr, ENamedThreads::GameThread);
}
}
void UAIPerceptionComponent::GetHostileActorsBySense(TSubclassOf<UAISense> SenseToFilterBy, TArray<AActor*>& OutActors) const
{
const FAISenseID SenseIdFilter = UAISense::GetSenseID(SenseToFilterBy);
if (SenseIdFilter == FAISenseID::InvalidID())
{
UE_VLOG(GetOwner(), LogAIPerception, Warning, TEXT("UAIPerceptionComponent::GetHostileActorsBySense called with an invalid or yet unregistered sense. Bailing out."));
return;
}
const bool bDeadDataFound = GetFilteredActors([SenseIdFilter](const FActorPerceptionInfo& ActorPerceptionInfo) {
return (ActorPerceptionInfo.bIsHostile && ActorPerceptionInfo.HasKnownStimulusOfSense(SenseIdFilter));
}, OutActors);
if (bDeadDataFound)
{
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateUObject(const_cast<UAIPerceptionComponent*>(this), &UAIPerceptionComponent::RemoveDeadData),
GET_STATID(STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData), nullptr, ENamedThreads::GameThread);
}
}
const FActorPerceptionInfo* UAIPerceptionComponent::GetFreshestTrace(const FAISenseID Sense) const
{
// @note will stop on first age 0 stimulus
float BestAge = FAIStimulus::NeverHappenedAge;
const FActorPerceptionInfo* Result = nullptr;
bool bDeadDataFound = false;
for (FActorPerceptionContainer::TConstIterator DataIt = GetPerceptualDataConstIterator(); DataIt; ++DataIt)
{
const FActorPerceptionInfo* Info = &DataIt->Value;
const float Age = Info->LastSensedStimuli[Sense].GetAge();
if (Age < BestAge)
{
if (Info->Target.IsValid())
{
BestAge = Age;
Result = Info;
if (BestAge == 0.f)
{
// won't find any younger then this
break;
}
}
else
{
bDeadDataFound = true;
}
}
}
if (bDeadDataFound)
{
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateUObject(const_cast<UAIPerceptionComponent*>(this), &UAIPerceptionComponent::RemoveDeadData),
GET_STATID(STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData), nullptr, ENamedThreads::GameThread);
}
return Result;
}
void UAIPerceptionComponent::SetDominantSense(TSubclassOf<UAISense> InDominantSense)
{
if (DominantSense != InDominantSense)
{
DominantSense = InDominantSense;
DominantSenseID = UAISense::GetSenseID(InDominantSense);
// update all perceptual info with this info
for (FActorPerceptionContainer::TIterator DataIt = GetPerceptualDataIterator(); DataIt; ++DataIt)
{
DataIt->Value.DominantSense = DominantSenseID;
}
}
}
FGenericTeamId UAIPerceptionComponent::GetTeamIdentifier() const
{
return FGenericTeamId::GetTeamIdentifier(GetOwner());
}
FVector UAIPerceptionComponent::GetActorLocation(const AActor& Actor) const
{
// not that Actor == NULL is valid
const FActorPerceptionInfo* ActorInfo = GetActorInfo(Actor);
return ActorInfo ? ActorInfo->GetLastStimulusLocation() : FAISystem::InvalidLocation;
}
void UAIPerceptionComponent::GetLocationAndDirection(FVector& Location, FVector& Direction) const
{
const AActor* OwnerActor = Cast<AActor>(GetOuter());
if (OwnerActor != nullptr)
{
FRotator ViewRotation(ForceInitToZero);
OwnerActor->GetActorEyesViewPoint(Location, ViewRotation);
Direction = ViewRotation.Vector();
}
}
const AActor* UAIPerceptionComponent::GetBodyActor() const
{
const AController* OwnerController = Cast<AController>(GetOuter());
if (OwnerController != nullptr)
{
return OwnerController->GetPawn();
}
return Cast<AActor>(GetOuter());
}
AActor* UAIPerceptionComponent::GetMutableBodyActor()
{
return const_cast<AActor*>(GetBodyActor());
}
void UAIPerceptionComponent::RegisterStimulus(AActor* Source, const FAIStimulus& Stimulus)
{
FStimulusToProcess& StimulusToProcess = StimuliToProcess.Add_GetRef(FStimulusToProcess(Source, Stimulus));
StimulusToProcess.Stimulus.SetExpirationAge(MaxActiveAge[int32(Stimulus.Type)]);
}
void UAIPerceptionComponent::ProcessStimuli()
{
SCOPE_CYCLE_COUNTER(STAT_AI_PercepComp_ProcessStimuli);
if(StimuliToProcess.Num() == 0)
{
UE_VLOG(GetOwner(), LogAIPerception, Warning, TEXT("UAIPerceptionComponent::ProcessStimuli called without any Stimuli to process"));
return;
}
const bool bBroadcastEveryTargetUpdate = OnTargetPerceptionUpdated.IsBound();
const bool bBroadcastEveryTargetInfoUpdate = OnTargetPerceptionInfoUpdated.IsBound();
TArray<FStimulusToProcess> ProcessingStimuli = MoveTemp(StimuliToProcess);
TArray<AActor*> UpdatedActors;
UpdatedActors.Reserve(ProcessingStimuli.Num());
TArray<AActor*> ActorsToForget;
ActorsToForget.Reserve(ProcessingStimuli.Num());
TArray<TObjectKey<AActor>, TInlineAllocator<8>> DataToRemove;
for (FStimulusToProcess& SourcedStimulus : ProcessingStimuli)
{
const TObjectKey<AActor>& SourceKey = SourcedStimulus.Source;
FActorPerceptionInfo* PerceptualInfo = PerceptualData.Find(SourceKey);
AActor* SourceActor = nullptr;
if (PerceptualInfo == nullptr)
{
if (SourcedStimulus.Stimulus.WasSuccessfullySensed() == false)
{
// this means it's a failed perception of an actor our owner is not aware of
// at all so there's no point in creating perceptual data for a failed stimulus
continue;
}
else
{
SourceActor = CastChecked<AActor>(SourceKey.ResolveObjectPtr(), ECastCheckedType::NullAllowed);
// no existing perceptual data and source no longer valid: nothing to do with this stimulus
if (SourceActor == nullptr)
{
continue;
}
// create an entry
PerceptualInfo = &PerceptualData.Add(SourceKey, FActorPerceptionInfo(SourceActor));
// tell it what's our dominant sense
PerceptualInfo->DominantSense = DominantSenseID;
PerceptualInfo->bIsHostile = (FGenericTeamId::GetAttitude(GetOwner(), SourceActor) == ETeamAttitude::Hostile);
PerceptualInfo->bIsFriendly = PerceptualInfo->bIsHostile ? false : (FGenericTeamId::GetAttitude(GetOwner(), SourceActor) == ETeamAttitude::Friendly);
}
}
if (PerceptualInfo->LastSensedStimuli.Num() <= SourcedStimulus.Stimulus.Type)
{
const int32 NumberToAdd = SourcedStimulus.Stimulus.Type - PerceptualInfo->LastSensedStimuli.Num() + 1;
PerceptualInfo->LastSensedStimuli.AddDefaulted(NumberToAdd);
}
check(SourcedStimulus.Stimulus.Type.IsValid());
FAIStimulus& StimulusStore = PerceptualInfo->LastSensedStimuli[SourcedStimulus.Stimulus.Type];
const bool bActorInfoUpdated = SourcedStimulus.Stimulus.WantsToNotifyOnlyOnPerceptionChange() == false
|| SourcedStimulus.Stimulus.WasSuccessfullySensed() != StimulusStore.WasSuccessfullySensed();
bool bRequiresUpdate = bActorInfoUpdated;
if (SourcedStimulus.Stimulus.WasSuccessfullySensed())
{
bRequiresUpdate |= ConditionallyStoreSuccessfulStimulus(StimulusStore, SourcedStimulus.Stimulus);
}
else if (StimulusStore.IsExpired() == false)
{
if (bActorInfoUpdated)
{
// @note there some more valid info in SourcedStimulus->Stimulus regarding test that failed
// may be useful in future
StimulusStore.MarkNoLongerSensed();
StimulusStore.SetStimulusAge(0);
}
}
else
{
HandleExpiredStimulus(StimulusStore);
if (bForgetStaleActors && !PerceptualInfo->HasAnyCurrentStimulus())
{
if (AActor* ActorToForget = PerceptualInfo->Target.Get())
{
ActorsToForget.Add(ActorToForget);
}
}
}
// if the new stimulus is "valid" or it's info that "no longer sensed" and it used to be sensed successfully
if (bRequiresUpdate)
{
// Source Actor is only resolved from SourceKey when required but might already have been resolved for new entry
SourceActor = (SourceActor == nullptr) ? CastChecked<AActor>(SourceKey.ResolveObjectPtr(), ECastCheckedType::NullAllowed) : SourceActor;
if (SourceActor == nullptr)
{
DataToRemove.Add(SourceKey);
}
else
{
UpdatedActors.AddUnique(SourceActor);
if (bBroadcastEveryTargetUpdate)
{
OnTargetPerceptionUpdated.Broadcast(SourceActor, StimulusStore);
}
}
if (bBroadcastEveryTargetInfoUpdate)
{
OnTargetPerceptionInfoUpdated.Broadcast(FActorPerceptionUpdateInfo(GetTypeHash(SourceKey), PerceptualInfo->Target, StimulusStore));
}
}
}
if (UpdatedActors.Num() > 0)
{
if (AIOwner != nullptr)
{
AIOwner->ActorsPerceptionUpdated(UpdatedActors);
}
OnPerceptionUpdated.Broadcast(UpdatedActors);
}
// forget actors that are no longer perceived
for (AActor* ActorToForget : ActorsToForget)
{
ForgetActor(ActorToForget);
}
// remove perceptual info related to stale actors
for (const TObjectKey<AActor>& SourceKey : DataToRemove)
{
PerceptualData.Remove(SourceKey);
}
}
void UAIPerceptionComponent::RefreshStimulus(FAIStimulus& StimulusStore, const FAIStimulus& NewStimulus)
{
ConditionallyStoreSuccessfulStimulus(StimulusStore, NewStimulus);
}
bool UAIPerceptionComponent::ConditionallyStoreSuccessfulStimulus(FAIStimulus& StimulusStore, const FAIStimulus& NewStimulus)
{
// if new stimulus is younger or stronger
// note that stimulus Age depends on PerceptionSystem::PerceptionAgingRate. It's possible that
// both already stored and the new stimulus have Age of 0, but stored stimulus' actual age is in [0, PerceptionSystem::PerceptionAgingRate)
if (NewStimulus.GetAge() <= StimulusStore.GetAge() || StimulusStore.Strength < NewStimulus.Strength)
{
StimulusStore = NewStimulus;
}
return false;
}
void UAIPerceptionComponent::HandleExpiredStimulus(FAIStimulus& StimulusStore)
{
ensure(StimulusStore.IsExpired() == true);
}
bool UAIPerceptionComponent::AgeStimuli(const float ConstPerceptionAgingRate)
{
bool bExpiredStimuli = false;
for (FActorPerceptionContainer::TIterator It(PerceptualData); It; ++It)
{
FActorPerceptionInfo& ActorPerceptionInfo = It->Value;
for (FAIStimulus& Stimulus : ActorPerceptionInfo.LastSensedStimuli)
{
// Age the stimulus. If it is active but has just expired, mark it as such
if (Stimulus.AgeStimulus(ConstPerceptionAgingRate) == false
&& (Stimulus.IsActive() || Stimulus.WantsToNotifyOnlyOnPerceptionChange())
&& Stimulus.IsExpired() == false)
{
AActor* TargetActor = ActorPerceptionInfo.Target.Get();
if (TargetActor)
{
Stimulus.MarkExpired();
RegisterStimulus(TargetActor, Stimulus);
bExpiredStimuli = true;
}
}
}
}
return bExpiredStimuli;
}
void UAIPerceptionComponent::ForgetActor(AActor* ActorToForget)
{
if (PerceptualData.Num() > 0)
{
UAIPerceptionSystem* AIPerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld());
if (AIPerceptionSys != nullptr && ActorToForget != nullptr)
{
AIPerceptionSys->OnListenerForgetsActor(*this, *ActorToForget);
}
const int32 NumRemoved = PerceptualData.Remove(ActorToForget);
// notify anyone interested
if (NumRemoved > 0 && OnTargetPerceptionForgotten.IsBound())
{
OnTargetPerceptionForgotten.Broadcast(ActorToForget);
}
}
}
void UAIPerceptionComponent::ForgetAll()
{
if (PerceptualData.Num() > 0)
{
UAIPerceptionSystem* AIPerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld());
if (AIPerceptionSys != nullptr)
{
AIPerceptionSys->OnListenerForgetsAll(*this);
}
// notify anyone interested
if (OnTargetPerceptionForgotten.IsBound())
{
for (FActorPerceptionContainer::TConstIterator DataIt = GetPerceptualDataConstIterator(); DataIt; ++DataIt)
{
if (AActor* ActorToForget = DataIt.Value().Target.Get())
{
OnTargetPerceptionForgotten.Broadcast(ActorToForget);
}
}
}
PerceptualData.Reset();
}
}
float UAIPerceptionComponent::GetYoungestStimulusAge(const AActor& Source) const
{
const FActorPerceptionInfo* Info = GetActorInfo(Source);
if (Info == nullptr)
{
return FAIStimulus::NeverHappenedAge;
}
float SmallestAge = FAIStimulus::NeverHappenedAge;
for (int32 SenseID = 0; SenseID < Info->LastSensedStimuli.Num(); ++SenseID)
{
if (Info->LastSensedStimuli[SenseID].WasSuccessfullySensed())
{
float SenseAge = Info->LastSensedStimuli[SenseID].GetAge();
if (SenseAge < SmallestAge)
{
SmallestAge = SenseAge;
}
}
}
return SmallestAge;
}
bool UAIPerceptionComponent::HasAnyActiveStimulus(const AActor& Source) const
{
const FActorPerceptionInfo* Info = GetActorInfo(Source);
if (Info == nullptr)
{
return false;
}
return Info->HasAnyKnownStimulus();
}
bool UAIPerceptionComponent::HasAnyCurrentStimulus(const AActor& Source) const
{
const FActorPerceptionInfo* Info = GetActorInfo(Source);
if (Info == nullptr)
{
return false;
}
return Info->HasAnyCurrentStimulus();
}
bool UAIPerceptionComponent::HasActiveStimulus(const AActor& Source, const FAISenseID Sense) const
{
const FActorPerceptionInfo* Info = GetActorInfo(Source);
return (Info
&& Info->LastSensedStimuli.IsValidIndex(Sense)
&& Info->LastSensedStimuli[Sense].WasSuccessfullySensed()
&& Info->LastSensedStimuli[Sense].GetAge() < FAIStimulus::NeverHappenedAge
&& (Info->LastSensedStimuli[Sense].GetAge() <= MaxActiveAge[Sense] || MaxActiveAge[Sense] == 0.f));
}
void UAIPerceptionComponent::RemoveDeadData()
{
for (FActorPerceptionContainer::TIterator It(PerceptualData); It; ++It)
{
if (It->Value.Target.IsValid() == false)
{
It.RemoveCurrent();
}
}
}
//----------------------------------------------------------------------//
// blueprint interface
//----------------------------------------------------------------------//
void UAIPerceptionComponent::GetPerceivedHostileActors(TArray<AActor*>& OutActors) const
{
GetHostileActors(OutActors);
}
void UAIPerceptionComponent::GetPerceivedHostileActorsBySense(const TSubclassOf<UAISense> SenseToUse, TArray<AActor*>& OutActors) const
{
GetHostileActorsBySense(SenseToUse, OutActors);
}
void UAIPerceptionComponent::GetCurrentlyPerceivedActors(TSubclassOf<UAISense> SenseToUse, TArray<AActor*>& OutActors) const
{
const FAISenseID SenseID = UAISense::GetSenseID(SenseToUse);
OutActors.Reserve(PerceptualData.Num());
for (FActorPerceptionContainer::TConstIterator DataIt = GetPerceptualDataConstIterator(); DataIt; ++DataIt)
{
const bool bCurrentlyPerceived = (SenseToUse == nullptr) ? DataIt->Value.HasAnyCurrentStimulus() : DataIt->Value.IsSenseActive(SenseID);
if (bCurrentlyPerceived)
{
if (AActor* Actor = DataIt->Value.Target.Get())
{
OutActors.Add(Actor);
}
}
}
}
void UAIPerceptionComponent::GetKnownPerceivedActors(TSubclassOf<UAISense> SenseToUse, TArray<AActor*>& OutActors) const
{
const FAISenseID SenseID = UAISense::GetSenseID(SenseToUse);
OutActors.Reserve(PerceptualData.Num());
for (FActorPerceptionContainer::TConstIterator DataIt = GetPerceptualDataConstIterator(); DataIt; ++DataIt)
{
const bool bWasEverPerceived = (SenseToUse == nullptr) ? DataIt->Value.HasAnyKnownStimulus() : DataIt->Value.HasKnownStimulusOfSense(SenseID);
if (bWasEverPerceived)
{
if (DataIt->Value.Target.IsValid())
{
OutActors.Add(DataIt->Value.Target.Get());
}
}
}
}
bool UAIPerceptionComponent::GetActorsPerception(AActor* Actor, FActorPerceptionBlueprintInfo& Info)
{
bool bInfoFound = false;
if (Actor != nullptr && Actor->IsPendingKillPending() == false)
{
const FActorPerceptionInfo* PerceivedInfo = GetActorInfo(*Actor);
if (PerceivedInfo)
{
Info = FActorPerceptionBlueprintInfo(*PerceivedInfo);
bInfoFound = true;
}
}
return bInfoFound;
}
void UAIPerceptionComponent::SetSenseEnabled(TSubclassOf<UAISense> SenseClass, const bool bEnable)
{
const FAISenseID SenseID = UAISense::GetSenseID(SenseClass);
if (SenseID.IsValid())
{
UpdatePerceptionAllowList(SenseID, bEnable);
}
}
bool UAIPerceptionComponent::IsSenseEnabled(TSubclassOf<UAISense> SenseClass) const
{
const FAISenseID SenseID = UAISense::GetSenseID(SenseClass);
if (!SenseID.IsValid() || GetSenseConfig(SenseID) == nullptr)
{
return false;
}
return PerceptionFilter.ShouldRespondToChannel(SenseID);
}
//----------------------------------------------------------------------//
// debug
//----------------------------------------------------------------------//
#if WITH_GAMEPLAY_DEBUGGER_MENU
void UAIPerceptionComponent::DescribeSelfToGameplayDebugger(FGameplayDebuggerCategory* DebuggerCategory) const
{
if (DebuggerCategory == nullptr)
{
return;
}
for (UAIPerceptionComponent::FActorPerceptionContainer::TConstIterator It(GetPerceptualDataConstIterator()); It; ++It)
{
const FActorPerceptionInfo& ActorPerceptionInfo = It->Value;
const AActor* Target = ActorPerceptionInfo.Target.Get();
if (Target != nullptr)
{
const FVector TargetLocation = Target->GetActorLocation();
for (const FAIStimulus& Stimulus : ActorPerceptionInfo.LastSensedStimuli)
{
const UAISenseConfig* SenseConfig = GetSenseConfig(Stimulus.Type);
if (Stimulus.IsValid() && (Stimulus.IsExpired() == false) && SenseConfig)
{
const FString Description = FString::Printf(TEXT("%s: %.2f age:%.2f"), *SenseConfig->GetSenseName(), Stimulus.Strength, Stimulus.GetAge());
const FColor DebugColor = SenseConfig->GetDebugColor();
DebuggerCategory->AddShape(FGameplayDebuggerShape::MakePoint(Stimulus.StimulusLocation + FVector(0, 0, 30), 30.0f, DebugColor, Description));
DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(Stimulus.ReceiverLocation, Stimulus.StimulusLocation, DebugColor));
DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(TargetLocation, Stimulus.StimulusLocation, FColor::Black));
}
}
}
}
for (const UAISenseConfig* SenseConfig : SensesConfig)
{
if (SenseConfig)
{
SenseConfig->DescribeSelfToGameplayDebugger(this, DebuggerCategory);
}
}
}
#endif // WITH_GAMEPLAY_DEBUGGER_MENU
#if ENABLE_VISUAL_LOG
void UAIPerceptionComponent::DescribeSelfToVisLog(FVisualLogEntry* Snapshot) const
{
}
#endif // ENABLE_VISUAL_LOG