Compare commits
2 Commits
86d3ae118d
...
391a35ac2c
| Author | SHA1 | Date | |
|---|---|---|---|
| 391a35ac2c | |||
| 44f7e860aa |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -76,17 +76,31 @@ void UPS_AI_Behavior_BTService_UpdateThreat::TickNode(
|
||||
|
||||
if (ThreatActor)
|
||||
{
|
||||
// Target switched by perception (higher score) → reset LOS tracking
|
||||
// Target switched by perception (higher score)
|
||||
if (ThreatActor != CurrentBBTarget)
|
||||
{
|
||||
Memory->TimeSinceLOS = 0.0f;
|
||||
Memory->bHadLOS = true;
|
||||
Memory->bInvestigating = false;
|
||||
Memory->LastVisiblePosition = ThreatActor->GetActorLocation();
|
||||
BB->ClearValue(PS_AI_Behavior_BB::LastKnownTargetPosition);
|
||||
const EPS_AI_Behavior_State CurrentState = AIC->GetBehaviorState();
|
||||
|
||||
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] UpdateThreat: target switched to '%s', LOS tracking reset"),
|
||||
*AIC->GetName(), *ThreatActor->GetName());
|
||||
// During TakingCover: don't switch target — cover is positioned against current threat.
|
||||
// The flanking check will handle cover invalidation if needed.
|
||||
if (CurrentBBTarget && CurrentState == EPS_AI_Behavior_State::TakingCover)
|
||||
{
|
||||
UE_LOG(LogPS_AI_Behavior, Log,
|
||||
TEXT("[%s] UpdateThreat: suppressed target switch to '%s' — in TakingCover (keeping '%s')"),
|
||||
*AIC->GetName(), *ThreatActor->GetName(), *CurrentBBTarget->GetName());
|
||||
ThreatActor = CurrentBBTarget; // Keep current target
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory->TimeSinceLOS = 0.0f;
|
||||
Memory->bHadLOS = true;
|
||||
Memory->bInvestigating = false;
|
||||
Memory->LastVisiblePosition = ThreatActor->GetActorLocation();
|
||||
BB->ClearValue(PS_AI_Behavior_BB::LastKnownTargetPosition);
|
||||
|
||||
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] UpdateThreat: target switched to '%s', LOS tracking reset"),
|
||||
*AIC->GetName(), *ThreatActor->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
BB->SetValueAsObject(PS_AI_Behavior_BB::ThreatActor, ThreatActor);
|
||||
|
||||
@ -220,8 +220,15 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
||||
if (Memory->LOSCheckTimer <= 0.0f)
|
||||
{
|
||||
Memory->LOSCheckTimer = 0.5f;
|
||||
const bool bThreatHasLOS = UPS_AI_Behavior_Statics::HasLineOfSight(
|
||||
Pawn->GetWorld(), Target, Pawn, 60.0f);
|
||||
// Check if threat can see NPC at chest height (not feet)
|
||||
const FVector ThreatEye = Target->GetActorLocation() + FVector(0, 0, 60.0f);
|
||||
const FVector NpcChest = Pawn->GetActorLocation() + FVector(0, 0, 60.0f);
|
||||
FHitResult FlankHit;
|
||||
FCollisionQueryParams FlankParams(SCENE_QUERY_STAT(FlankLOS), true);
|
||||
FlankParams.AddIgnoredActor(Target);
|
||||
FlankParams.AddIgnoredActor(Pawn);
|
||||
const bool bThreatHasLOS = !Pawn->GetWorld()->LineTraceSingleByChannel(
|
||||
FlankHit, ThreatEye, NpcChest, ECC_Visibility, FlankParams);
|
||||
if (bThreatHasLOS)
|
||||
{
|
||||
UE_LOG(LogPS_AI_Behavior, Log,
|
||||
@ -469,6 +476,14 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
||||
DrawDebugString(World, Memory->FiringPosition + FVector(0, 0, 50.0f),
|
||||
TEXT("FIRE"), nullptr, FColor::Red, 0.0f, true);
|
||||
}
|
||||
else if (Memory->SubState == EPS_AI_Behavior_CombatSubState::Peeking)
|
||||
{
|
||||
// No firing position — shooting in place
|
||||
DrawDebugSphere(World, Pawn->GetActorLocation() + FVector(0, 0, 30.0f),
|
||||
15.0f, 6, FColor::Orange, false, 0.0f);
|
||||
DrawDebugString(World, Pawn->GetActorLocation() + FVector(0, 0, 50.0f),
|
||||
TEXT("IN PLACE"), nullptr, FColor::Orange, 0.0f, true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -687,8 +702,23 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::OnFiringPositionQueryFinished(
|
||||
// EQS failed → fallback: peek in place if LOS exists
|
||||
Memory->bHasFiringPosition = false;
|
||||
|
||||
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] CoverShootCycle: firing position EQS failed, peeking in place"),
|
||||
*AIC->GetName());
|
||||
const int32 NumItems = Result.IsValid() ? Result->Items.Num() : 0;
|
||||
const FVector CoverLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
|
||||
UE_LOG(LogPS_AI_Behavior, Log,
|
||||
TEXT("[%s] CoverShootCycle: firing position EQS FAILED at cover %s — items=%d"),
|
||||
*AIC->GetName(), *CoverLoc.ToString(), NumItems);
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
if (bDebugDraw)
|
||||
{
|
||||
UWorld* World = Pawn->GetWorld();
|
||||
const FVector PawnLoc = Pawn->GetActorLocation();
|
||||
DrawDebugSphere(World, PawnLoc + FVector(0, 0, 30.0f),
|
||||
20.0f, 8, FColor::Orange, false, 5.0f);
|
||||
DrawDebugString(World, PawnLoc + FVector(0, 0, 55.0f),
|
||||
TEXT("NO FIRE POS"), nullptr, FColor::Orange, 5.0f, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Target)
|
||||
{
|
||||
|
||||
@ -440,18 +440,8 @@ void UPS_AI_Behavior_BTTask_FindCover::OnRefinementQueryFinished(
|
||||
|
||||
// Log all items with their scores for debugging
|
||||
const int32 NumSurvived = Result->Items.Num();
|
||||
UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] FindCover: EQS refinement — %d items survived filters:"),
|
||||
*AIC->GetName(), NumSurvived);
|
||||
for (int32 i = 0; i < NumSurvived; ++i)
|
||||
{
|
||||
if (!Result->Items[i].IsValid()) continue;
|
||||
const FVector Loc = Result->GetItemAsLocation(i);
|
||||
const float ItemScore = Result->GetItemScore(i);
|
||||
UE_LOG(LogPS_AI_Behavior, Warning, TEXT(" [%d] %s score=%.3f %s"),
|
||||
i, *Loc.ToString(), ItemScore, (i == 0) ? TEXT("← CHOSEN") : TEXT(""));
|
||||
}
|
||||
UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] FindCover: refined %s → %s"),
|
||||
*AIC->GetName(), *OriginalCoverPos.ToString(), *FinalPos.ToString());
|
||||
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] FindCover: EQS refinement — %d items survived, refined %s → %s"),
|
||||
*AIC->GetName(), NumSurvived, *OriginalCoverPos.ToString(), *FinalPos.ToString());
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
if (bDebugDraw)
|
||||
@ -487,8 +477,26 @@ void UPS_AI_Behavior_BTTask_FindCover::OnRefinementQueryFinished(
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] FindCover: EQS refinement failed, using original cover position"),
|
||||
*AIC->GetName());
|
||||
const int32 NumItems = Result.IsValid() ? Result->Items.Num() : 0;
|
||||
UE_LOG(LogPS_AI_Behavior, Log,
|
||||
TEXT("[%s] FindCover: EQS refinement FAILED — items=%d, valid=%s"),
|
||||
*AIC->GetName(), NumItems,
|
||||
Result.IsValid() ? TEXT("yes") : TEXT("no"));
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
if (bDebugDraw)
|
||||
{
|
||||
UWorld* World = AIC->GetWorld();
|
||||
if (World)
|
||||
{
|
||||
// Show that refinement failed — orange box at original position
|
||||
DrawDebugBox(World, OriginalCoverPos + FVector(0, 0, 50.0f),
|
||||
FVector(20.0f), FColor::Orange, false, 5.0f, 0, 2.5f);
|
||||
DrawDebugString(World, OriginalCoverPos + FVector(0, 0, 75.0f),
|
||||
TEXT("NO REFINE"), nullptr, FColor::Orange, 5.0f, true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Update BB with refined position
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "EnvironmentQuery/EnvQueryTypes.h"
|
||||
#include "EnvironmentQuery/Items/EnvQueryItemType_Point.h"
|
||||
#include "BehaviorTree/BlackboardComponent.h"
|
||||
#include "NavigationSystem.h"
|
||||
|
||||
void UPS_AI_Behavior_EQSContext_CoverLocation::ProvideContext(
|
||||
FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
|
||||
@ -22,19 +23,39 @@ void UPS_AI_Behavior_EQSContext_CoverLocation::ProvideContext(
|
||||
UBlackboardComponent* BB = AIC->GetBlackboardComponent();
|
||||
if (!BB) return;
|
||||
|
||||
// Prefer the original CoverPoint actor location (center of cover geometry)
|
||||
// over the refined CoverLocation (which may be offset behind cover)
|
||||
const AActor* CoverPoint = Cast<AActor>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||
if (CoverPoint)
|
||||
// Use CoverPoint actor location (center of cover geometry) for circle generation.
|
||||
// Fallback to CoverLocation vector if no actor.
|
||||
FVector RawLoc = FVector::ZeroVector;
|
||||
const AActor* CoverPointActor = Cast<AActor>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||
if (CoverPointActor)
|
||||
{
|
||||
UEnvQueryItemType_Point::SetContextHelper(ContextData, CoverPoint->GetActorLocation());
|
||||
return;
|
||||
RawLoc = CoverPointActor->GetActorLocation();
|
||||
}
|
||||
else
|
||||
{
|
||||
RawLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
|
||||
}
|
||||
|
||||
// Fallback to CoverLocation vector (procedural cover, no actor)
|
||||
const FVector CoverLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
|
||||
if (!CoverLoc.IsZero())
|
||||
if (RawLoc.IsZero()) return;
|
||||
|
||||
// Project to navmesh so the EQS generator works correctly
|
||||
// (cover points inside geometry or above navmesh need projection)
|
||||
FVector FinalLoc = RawLoc;
|
||||
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(QuerierPawn->GetWorld());
|
||||
if (NavSys)
|
||||
{
|
||||
UEnvQueryItemType_Point::SetContextHelper(ContextData, CoverLoc);
|
||||
FNavLocation NavLoc;
|
||||
if (NavSys->ProjectPointToNavigation(RawLoc, NavLoc, FVector(200.0f, 200.0f, 200.0f)))
|
||||
{
|
||||
FinalLoc = NavLoc.Location;
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
||||
TEXT("[EQSContext_CoverLocation] %s '%s' raw=%s → nav=%s"),
|
||||
CoverPointActor ? TEXT("CoverPoint") : TEXT("CoverLocation"),
|
||||
CoverPointActor ? *CoverPointActor->GetName() : TEXT("(vector)"),
|
||||
*RawLoc.ToString(), *FinalLoc.ToString());
|
||||
|
||||
UEnvQueryItemType_Point::SetContextHelper(ContextData, FinalLoc);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h"
|
||||
#include "CollisionQueryParams.h"
|
||||
#include "Engine/World.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
|
||||
UPS_AI_Behavior_EQSTest_CoverQuality::UPS_AI_Behavior_EQSTest_CoverQuality()
|
||||
{
|
||||
@ -38,6 +39,20 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
|
||||
|
||||
FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(CoverQualityEQS), true);
|
||||
|
||||
// Ignore the threat actor itself (e.g. AimTargetActor sphere blocks its own traces)
|
||||
TArray<AActor*> ThreatActors;
|
||||
if (QueryInstance.PrepareContext(UPS_AI_Behavior_EQSContext_Threat::StaticClass(), ThreatActors))
|
||||
{
|
||||
for (AActor* A : ThreatActors)
|
||||
{
|
||||
TraceParams.AddIgnoredActor(A);
|
||||
for (AActor* Parent = A->GetAttachParentActor(); Parent; Parent = Parent->GetAttachParentActor())
|
||||
{
|
||||
TraceParams.AddIgnoredActor(Parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute height steps
|
||||
TArray<float> TraceHeights;
|
||||
if (NumTraceHeights == 1)
|
||||
@ -86,7 +101,7 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
|
||||
{
|
||||
const float HitDist = FVector::Dist(CandidatePos, Hit.ImpactPoint);
|
||||
|
||||
UE_LOG(LogPS_AI_Behavior, Log,
|
||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
||||
TEXT("CoverQuality[%d] h=%.0f lat=%.0f: HIT '%s' dist=%.0fcm"),
|
||||
It.GetIndex(), Height, Lateral,
|
||||
Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("null"),
|
||||
@ -100,7 +115,7 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogPS_AI_Behavior, Log,
|
||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
||||
TEXT("CoverQuality[%d] h=%.0f lat=%.0f: NO HIT"),
|
||||
It.GetIndex(), Height, Lateral);
|
||||
}
|
||||
@ -109,12 +124,26 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
|
||||
|
||||
const float Score = BlockedCount / static_cast<float>(TotalTraces);
|
||||
|
||||
UE_LOG(LogPS_AI_Behavior, Log,
|
||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
||||
TEXT("CoverQuality[%d] at %s → score=%.2f (blocked=%d/%d)"),
|
||||
It.GetIndex(), *CandidatePos.ToString(),
|
||||
Score, (int32)BlockedCount, TotalTraces);
|
||||
|
||||
It.SetScore(TestPurpose, FilterType, Score, 0.0f, 1.0f);
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
if (bDrawDebug)
|
||||
{
|
||||
// Color: red(0) → yellow(0.5) → green(1)
|
||||
const uint8 R = static_cast<uint8>(FMath::Lerp(255.0f, 0.0f, FMath::Clamp(Score, 0.0f, 1.0f)));
|
||||
const uint8 G = static_cast<uint8>(FMath::Lerp(0.0f, 255.0f, FMath::Clamp(Score, 0.0f, 1.0f)));
|
||||
DrawDebugBox(const_cast<UWorld*>(World), CandidatePos + FVector(0, 0, 20.0f),
|
||||
FVector(8.0f), FColor(R, G, 0), false, 5.0f);
|
||||
DrawDebugString(const_cast<UWorld*>(World), CandidatePos + FVector(0, 0, 35.0f),
|
||||
FString::Printf(TEXT("%.0f%%"), Score * 100.0f), nullptr,
|
||||
FColor(R, G, 0), 5.0f, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
It.SetScore(TestPurpose, FilterType, Score, FloatValueMin.GetValue(), FloatValueMax.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,10 +2,12 @@
|
||||
|
||||
#include "EQS/PS_AI_Behavior_EQSTest_LineOfSight.h"
|
||||
#include "EQS/PS_AI_Behavior_EQSContext_Threat.h"
|
||||
#include "PS_AI_Behavior_Definitions.h"
|
||||
#include "EnvironmentQuery/EnvQueryTypes.h"
|
||||
#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h"
|
||||
#include "CollisionQueryParams.h"
|
||||
#include "Engine/World.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
|
||||
UPS_AI_Behavior_EQSTest_LineOfSight::UPS_AI_Behavior_EQSTest_LineOfSight()
|
||||
{
|
||||
@ -37,20 +39,91 @@ void UPS_AI_Behavior_EQSTest_LineOfSight::RunTest(FEnvQueryInstance& QueryInstan
|
||||
|
||||
FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(LOSTestEQS), true);
|
||||
|
||||
const FVector TraceEnd = ThreatLoc + FVector(0, 0, TargetHeightOffset);
|
||||
// Ignore the threat actor itself (e.g. AimTargetActor sphere blocks its own traces)
|
||||
TArray<AActor*> ThreatActors;
|
||||
if (QueryInstance.PrepareContext(UPS_AI_Behavior_EQSContext_Threat::StaticClass(), ThreatActors))
|
||||
{
|
||||
for (AActor* A : ThreatActors)
|
||||
{
|
||||
TraceParams.AddIgnoredActor(A);
|
||||
// Also ignore parent chain (AimTarget → Character capsule)
|
||||
for (AActor* Parent = A->GetAttachParentActor(); Parent; Parent = Parent->GetAttachParentActor())
|
||||
{
|
||||
TraceParams.AddIgnoredActor(Parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lateral offsets: center + optional left/right perpendicular to threat direction
|
||||
TArray<float> LateralOffsets;
|
||||
LateralOffsets.Add(0.0f);
|
||||
if (LateralSpread > 0.0f)
|
||||
{
|
||||
LateralOffsets.Add(-LateralSpread);
|
||||
LateralOffsets.Add(LateralSpread);
|
||||
}
|
||||
const int32 TotalTraces = LateralOffsets.Num();
|
||||
|
||||
for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
|
||||
{
|
||||
const FVector CandidatePos = GetItemLocation(QueryInstance, It.GetIndex());
|
||||
const FVector TraceStart = CandidatePos + FVector(0, 0, TraceHeight);
|
||||
|
||||
FHitResult Hit;
|
||||
const bool bBlocked = World->LineTraceSingleByChannel(
|
||||
Hit, TraceStart, TraceEnd, ECC_Visibility, TraceParams);
|
||||
// Direction from candidate to threat (2D) and its perpendicular
|
||||
const FVector DirToThreat = (ThreatLoc - CandidatePos).GetSafeNormal2D();
|
||||
const FVector LateralDir = FVector::CrossProduct(FVector::UpVector, DirToThreat);
|
||||
|
||||
// Score: 1.0 = clear LOS (not blocked), 0.0 = blocked
|
||||
const float Score = bBlocked ? 0.0f : 1.0f;
|
||||
It.SetScore(TestPurpose, FilterType, Score, 0.0f, 1.0f);
|
||||
int32 ClearCount = 0;
|
||||
for (float Lateral : LateralOffsets)
|
||||
{
|
||||
const FVector TraceStart = CandidatePos + FVector(0, 0, TraceHeight) + LateralDir * Lateral;
|
||||
const FVector TraceEnd = ThreatLoc + FVector(0, 0, TargetHeightOffset) + LateralDir * Lateral;
|
||||
|
||||
FHitResult Hit;
|
||||
const bool bBlocked = World->LineTraceSingleByChannel(
|
||||
Hit, TraceStart, TraceEnd, ECC_Visibility, TraceParams);
|
||||
|
||||
if (!bBlocked)
|
||||
{
|
||||
ClearCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
||||
TEXT("LOS[%d] lat=%.0f: BLOCKED by '%s' at %s (dist=%.0fcm)"),
|
||||
It.GetIndex(), Lateral,
|
||||
Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("null"),
|
||||
*Hit.ImpactPoint.ToString(),
|
||||
FVector::Dist(CandidatePos, Hit.ImpactPoint));
|
||||
}
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
if (bDrawDebug)
|
||||
{
|
||||
const FColor Color = bBlocked ? FColor::Red : FColor::Green;
|
||||
DrawDebugLine(const_cast<UWorld*>(World), TraceStart, TraceEnd,
|
||||
Color, false, 5.0f, 0, 0.5f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Score: ratio of clear traces (1.0 = all clear, 0.0 = all blocked)
|
||||
const float Score = static_cast<float>(ClearCount) / static_cast<float>(TotalTraces);
|
||||
|
||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
||||
TEXT("LOS[%d] at %s → clear=%d/%d score=%.2f"),
|
||||
It.GetIndex(), *CandidatePos.ToString(),
|
||||
ClearCount, TotalTraces, Score);
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
if (bDrawDebug)
|
||||
{
|
||||
const uint8 G = static_cast<uint8>(Score * 255.0f);
|
||||
DrawDebugSphere(const_cast<UWorld*>(World), CandidatePos + FVector(0, 0, 20.0f),
|
||||
10.0f, 6, FColor(255 - G, G, 0), false, 5.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
It.SetScore(TestPurpose, FilterType, Score, FloatValueMin.GetValue(), FloatValueMax.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "GameFramework/SpectatorPawn.h"
|
||||
#include "AIController.h"
|
||||
#include "BehaviorTree/BlackboardComponent.h"
|
||||
#include "EngineUtils.h"
|
||||
|
||||
UPS_AI_Behavior_PerceptionComponent::UPS_AI_Behavior_PerceptionComponent()
|
||||
{
|
||||
@ -270,6 +271,53 @@ AActor* UPS_AI_Behavior_PerceptionComponent::GetHighestThreatActor(
|
||||
TArray<AActor*> PerceivedActors;
|
||||
GetCurrentlyPerceivedActors(nullptr, PerceivedActors); // All senses at once
|
||||
|
||||
// ─── Omniscient awareness for high-priority target types ────────
|
||||
// Protectors (police, player) are always known if within sight radius,
|
||||
// even outside the perception cone. This ensures enemies prioritize
|
||||
// dangerous threats they're aware of (e.g. a cop standing next to them).
|
||||
if (TargetPriority.Num() > 0)
|
||||
{
|
||||
const AAIController* OwnerAIC = Cast<AAIController>(GetOwner());
|
||||
const APawn* OwnerPawn = OwnerAIC ? OwnerAIC->GetPawn() : nullptr;
|
||||
|
||||
if (OwnerPawn)
|
||||
{
|
||||
const EPS_AI_Behavior_TargetType TopPriority = TargetPriority[0];
|
||||
const UPS_AI_Behavior_Settings* Settings = GetDefault<UPS_AI_Behavior_Settings>();
|
||||
const float AwarenessRadius = Settings->DefaultSightRadius;
|
||||
const FVector OwnerLoc = OwnerPawn->GetActorLocation();
|
||||
|
||||
for (TActorIterator<APawn> It(GetWorld()); It; ++It)
|
||||
{
|
||||
APawn* CandidatePawn = *It;
|
||||
if (!CandidatePawn || CandidatePawn == OwnerPawn) continue;
|
||||
|
||||
// Already perceived → skip
|
||||
if (PerceivedActors.Contains(CandidatePawn)) continue;
|
||||
|
||||
// Check distance
|
||||
const float Dist = FVector::Dist(OwnerLoc, CandidatePawn->GetActorLocation());
|
||||
if (Dist > AwarenessRadius) continue;
|
||||
|
||||
// Check if this actor matches the top priority type
|
||||
const EPS_AI_Behavior_TargetType CandidateType = ClassifyActor(CandidatePawn);
|
||||
const EPS_AI_Behavior_TargetType MappedType =
|
||||
(CandidateType == EPS_AI_Behavior_TargetType::Player)
|
||||
? EPS_AI_Behavior_TargetType::Protector
|
||||
: CandidateType;
|
||||
|
||||
if (MappedType == TopPriority)
|
||||
{
|
||||
PerceivedActors.Add(CandidatePawn);
|
||||
UE_LOG(LogPS_AI_Behavior, Log,
|
||||
TEXT("[%s] Omniscient awareness: added '%s' (type=%d, dist=%.0f) — top priority target"),
|
||||
*OwnerPawn->GetName(), *CandidatePawn->GetName(),
|
||||
static_cast<int32>(CandidateType), Dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PerceivedActors.Num() == 0)
|
||||
{
|
||||
return nullptr;
|
||||
|
||||
@ -25,11 +25,11 @@ public:
|
||||
UPROPERTY(EditAnywhere, Category = "Cover", meta = (ClampMin = "1", ClampMax = "5"))
|
||||
int32 NumTraceHeights = 3;
|
||||
|
||||
/** Minimum height for the lowest trace (cm relative to item — items are at capsule center, ~90cm above ground). */
|
||||
/** Minimum height for the lowest trace (cm relative to item — items are at navmesh level). */
|
||||
UPROPERTY(EditAnywhere, Category = "Cover")
|
||||
float MinTraceHeight = 0.0f;
|
||||
|
||||
/** Maximum height for the highest trace (cm relative to item — 70 ≈ top of head from capsule center). */
|
||||
/** Maximum height for the highest trace (cm relative to item — 70 ≈ knee-to-waist from navmesh). */
|
||||
UPROPERTY(EditAnywhere, Category = "Cover")
|
||||
float MaxTraceHeight = 70.0f;
|
||||
|
||||
@ -48,6 +48,9 @@ public:
|
||||
UPROPERTY(EditAnywhere, Category = "Cover", meta = (ClampMin = "0.0", ClampMax = "100.0"))
|
||||
float LateralSpread = 30.0f;
|
||||
|
||||
/** Draw debug boxes for each candidate with color-coded score. */
|
||||
UPROPERTY(EditAnywhere, Category = "Debug")
|
||||
bool bDrawDebug = false;
|
||||
|
||||
protected:
|
||||
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
|
||||
|
||||
@ -31,6 +31,18 @@ public:
|
||||
UPROPERTY(EditDefaultsOnly, Category = "LOS", meta = (ClampMin = "0.0"))
|
||||
float TargetHeightOffset = 100.0f;
|
||||
|
||||
/**
|
||||
* Lateral offset (cm) for side traces perpendicular to threat direction.
|
||||
* 0 = center trace only, 30 = adds traces at ±30cm.
|
||||
* Requires wider clear LOS, not just a narrow slit past cover edge.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "LOS", meta = (ClampMin = "0.0", ClampMax = "100.0"))
|
||||
float LateralSpread = 0.0f;
|
||||
|
||||
/** Draw debug spheres and LOS lines for each candidate. */
|
||||
UPROPERTY(EditAnywhere, Category = "Debug")
|
||||
bool bDrawDebug = false;
|
||||
|
||||
protected:
|
||||
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
|
||||
virtual FText GetDescriptionTitle() const override;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user