Compare commits
No commits in common. "391a35ac2cb8c2c10dec09c85acfe1e4f78180a2" and "86d3ae118df19911ac8f22bf743ad307f2156ccd" have entirely different histories.
391a35ac2c
...
86d3ae118d
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,31 +76,17 @@ void UPS_AI_Behavior_BTService_UpdateThreat::TickNode(
|
|||||||
|
|
||||||
if (ThreatActor)
|
if (ThreatActor)
|
||||||
{
|
{
|
||||||
// Target switched by perception (higher score)
|
// Target switched by perception (higher score) → reset LOS tracking
|
||||||
if (ThreatActor != CurrentBBTarget)
|
if (ThreatActor != CurrentBBTarget)
|
||||||
{
|
{
|
||||||
const EPS_AI_Behavior_State CurrentState = AIC->GetBehaviorState();
|
Memory->TimeSinceLOS = 0.0f;
|
||||||
|
Memory->bHadLOS = true;
|
||||||
|
Memory->bInvestigating = false;
|
||||||
|
Memory->LastVisiblePosition = ThreatActor->GetActorLocation();
|
||||||
|
BB->ClearValue(PS_AI_Behavior_BB::LastKnownTargetPosition);
|
||||||
|
|
||||||
// During TakingCover: don't switch target — cover is positioned against current threat.
|
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] UpdateThreat: target switched to '%s', LOS tracking reset"),
|
||||||
// The flanking check will handle cover invalidation if needed.
|
*AIC->GetName(), *ThreatActor->GetName());
|
||||||
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);
|
BB->SetValueAsObject(PS_AI_Behavior_BB::ThreatActor, ThreatActor);
|
||||||
|
|||||||
@ -220,15 +220,8 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
|||||||
if (Memory->LOSCheckTimer <= 0.0f)
|
if (Memory->LOSCheckTimer <= 0.0f)
|
||||||
{
|
{
|
||||||
Memory->LOSCheckTimer = 0.5f;
|
Memory->LOSCheckTimer = 0.5f;
|
||||||
// Check if threat can see NPC at chest height (not feet)
|
const bool bThreatHasLOS = UPS_AI_Behavior_Statics::HasLineOfSight(
|
||||||
const FVector ThreatEye = Target->GetActorLocation() + FVector(0, 0, 60.0f);
|
Pawn->GetWorld(), Target, Pawn, 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)
|
if (bThreatHasLOS)
|
||||||
{
|
{
|
||||||
UE_LOG(LogPS_AI_Behavior, Log,
|
UE_LOG(LogPS_AI_Behavior, Log,
|
||||||
@ -476,14 +469,6 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
|||||||
DrawDebugString(World, Memory->FiringPosition + FVector(0, 0, 50.0f),
|
DrawDebugString(World, Memory->FiringPosition + FVector(0, 0, 50.0f),
|
||||||
TEXT("FIRE"), nullptr, FColor::Red, 0.0f, true);
|
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
|
#endif
|
||||||
}
|
}
|
||||||
@ -702,23 +687,8 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::OnFiringPositionQueryFinished(
|
|||||||
// EQS failed → fallback: peek in place if LOS exists
|
// EQS failed → fallback: peek in place if LOS exists
|
||||||
Memory->bHasFiringPosition = false;
|
Memory->bHasFiringPosition = false;
|
||||||
|
|
||||||
const int32 NumItems = Result.IsValid() ? Result->Items.Num() : 0;
|
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] CoverShootCycle: firing position EQS failed, peeking in place"),
|
||||||
const FVector CoverLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
|
*AIC->GetName());
|
||||||
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)
|
if (Target)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -440,8 +440,18 @@ void UPS_AI_Behavior_BTTask_FindCover::OnRefinementQueryFinished(
|
|||||||
|
|
||||||
// Log all items with their scores for debugging
|
// Log all items with their scores for debugging
|
||||||
const int32 NumSurvived = Result->Items.Num();
|
const int32 NumSurvived = Result->Items.Num();
|
||||||
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] FindCover: EQS refinement — %d items survived, refined %s → %s"),
|
UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] FindCover: EQS refinement — %d items survived filters:"),
|
||||||
*AIC->GetName(), NumSurvived, *OriginalCoverPos.ToString(), *FinalPos.ToString());
|
*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());
|
||||||
|
|
||||||
#if ENABLE_DRAW_DEBUG
|
#if ENABLE_DRAW_DEBUG
|
||||||
if (bDebugDraw)
|
if (bDebugDraw)
|
||||||
@ -477,26 +487,8 @@ void UPS_AI_Behavior_BTTask_FindCover::OnRefinementQueryFinished(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const int32 NumItems = Result.IsValid() ? Result->Items.Num() : 0;
|
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] FindCover: EQS refinement failed, using original cover position"),
|
||||||
UE_LOG(LogPS_AI_Behavior, Log,
|
*AIC->GetName());
|
||||||
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
|
// Update BB with refined position
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
#include "EnvironmentQuery/EnvQueryTypes.h"
|
#include "EnvironmentQuery/EnvQueryTypes.h"
|
||||||
#include "EnvironmentQuery/Items/EnvQueryItemType_Point.h"
|
#include "EnvironmentQuery/Items/EnvQueryItemType_Point.h"
|
||||||
#include "BehaviorTree/BlackboardComponent.h"
|
#include "BehaviorTree/BlackboardComponent.h"
|
||||||
#include "NavigationSystem.h"
|
|
||||||
|
|
||||||
void UPS_AI_Behavior_EQSContext_CoverLocation::ProvideContext(
|
void UPS_AI_Behavior_EQSContext_CoverLocation::ProvideContext(
|
||||||
FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
|
FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
|
||||||
@ -23,39 +22,19 @@ void UPS_AI_Behavior_EQSContext_CoverLocation::ProvideContext(
|
|||||||
UBlackboardComponent* BB = AIC->GetBlackboardComponent();
|
UBlackboardComponent* BB = AIC->GetBlackboardComponent();
|
||||||
if (!BB) return;
|
if (!BB) return;
|
||||||
|
|
||||||
// Use CoverPoint actor location (center of cover geometry) for circle generation.
|
// Prefer the original CoverPoint actor location (center of cover geometry)
|
||||||
// Fallback to CoverLocation vector if no actor.
|
// over the refined CoverLocation (which may be offset behind cover)
|
||||||
FVector RawLoc = FVector::ZeroVector;
|
const AActor* CoverPoint = Cast<AActor>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||||
const AActor* CoverPointActor = Cast<AActor>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
if (CoverPoint)
|
||||||
if (CoverPointActor)
|
|
||||||
{
|
{
|
||||||
RawLoc = CoverPointActor->GetActorLocation();
|
UEnvQueryItemType_Point::SetContextHelper(ContextData, CoverPoint->GetActorLocation());
|
||||||
}
|
return;
|
||||||
else
|
|
||||||
{
|
|
||||||
RawLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RawLoc.IsZero()) return;
|
// Fallback to CoverLocation vector (procedural cover, no actor)
|
||||||
|
const FVector CoverLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
|
||||||
// Project to navmesh so the EQS generator works correctly
|
if (!CoverLoc.IsZero())
|
||||||
// (cover points inside geometry or above navmesh need projection)
|
|
||||||
FVector FinalLoc = RawLoc;
|
|
||||||
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(QuerierPawn->GetWorld());
|
|
||||||
if (NavSys)
|
|
||||||
{
|
{
|
||||||
FNavLocation NavLoc;
|
UEnvQueryItemType_Point::SetContextHelper(ContextData, CoverLoc);
|
||||||
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,7 +7,6 @@
|
|||||||
#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h"
|
#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h"
|
||||||
#include "CollisionQueryParams.h"
|
#include "CollisionQueryParams.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "DrawDebugHelpers.h"
|
|
||||||
|
|
||||||
UPS_AI_Behavior_EQSTest_CoverQuality::UPS_AI_Behavior_EQSTest_CoverQuality()
|
UPS_AI_Behavior_EQSTest_CoverQuality::UPS_AI_Behavior_EQSTest_CoverQuality()
|
||||||
{
|
{
|
||||||
@ -39,20 +38,6 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
|
|||||||
|
|
||||||
FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(CoverQualityEQS), true);
|
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
|
// Compute height steps
|
||||||
TArray<float> TraceHeights;
|
TArray<float> TraceHeights;
|
||||||
if (NumTraceHeights == 1)
|
if (NumTraceHeights == 1)
|
||||||
@ -101,7 +86,7 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
|
|||||||
{
|
{
|
||||||
const float HitDist = FVector::Dist(CandidatePos, Hit.ImpactPoint);
|
const float HitDist = FVector::Dist(CandidatePos, Hit.ImpactPoint);
|
||||||
|
|
||||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
UE_LOG(LogPS_AI_Behavior, Log,
|
||||||
TEXT("CoverQuality[%d] h=%.0f lat=%.0f: HIT '%s' dist=%.0fcm"),
|
TEXT("CoverQuality[%d] h=%.0f lat=%.0f: HIT '%s' dist=%.0fcm"),
|
||||||
It.GetIndex(), Height, Lateral,
|
It.GetIndex(), Height, Lateral,
|
||||||
Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("null"),
|
Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("null"),
|
||||||
@ -115,7 +100,7 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
UE_LOG(LogPS_AI_Behavior, Log,
|
||||||
TEXT("CoverQuality[%d] h=%.0f lat=%.0f: NO HIT"),
|
TEXT("CoverQuality[%d] h=%.0f lat=%.0f: NO HIT"),
|
||||||
It.GetIndex(), Height, Lateral);
|
It.GetIndex(), Height, Lateral);
|
||||||
}
|
}
|
||||||
@ -124,26 +109,12 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
|
|||||||
|
|
||||||
const float Score = BlockedCount / static_cast<float>(TotalTraces);
|
const float Score = BlockedCount / static_cast<float>(TotalTraces);
|
||||||
|
|
||||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
UE_LOG(LogPS_AI_Behavior, Log,
|
||||||
TEXT("CoverQuality[%d] at %s → score=%.2f (blocked=%d/%d)"),
|
TEXT("CoverQuality[%d] at %s → score=%.2f (blocked=%d/%d)"),
|
||||||
It.GetIndex(), *CandidatePos.ToString(),
|
It.GetIndex(), *CandidatePos.ToString(),
|
||||||
Score, (int32)BlockedCount, TotalTraces);
|
Score, (int32)BlockedCount, TotalTraces);
|
||||||
|
|
||||||
#if ENABLE_DRAW_DEBUG
|
It.SetScore(TestPurpose, FilterType, Score, 0.0f, 1.0f);
|
||||||
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,12 +2,10 @@
|
|||||||
|
|
||||||
#include "EQS/PS_AI_Behavior_EQSTest_LineOfSight.h"
|
#include "EQS/PS_AI_Behavior_EQSTest_LineOfSight.h"
|
||||||
#include "EQS/PS_AI_Behavior_EQSContext_Threat.h"
|
#include "EQS/PS_AI_Behavior_EQSContext_Threat.h"
|
||||||
#include "PS_AI_Behavior_Definitions.h"
|
|
||||||
#include "EnvironmentQuery/EnvQueryTypes.h"
|
#include "EnvironmentQuery/EnvQueryTypes.h"
|
||||||
#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h"
|
#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h"
|
||||||
#include "CollisionQueryParams.h"
|
#include "CollisionQueryParams.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "DrawDebugHelpers.h"
|
|
||||||
|
|
||||||
UPS_AI_Behavior_EQSTest_LineOfSight::UPS_AI_Behavior_EQSTest_LineOfSight()
|
UPS_AI_Behavior_EQSTest_LineOfSight::UPS_AI_Behavior_EQSTest_LineOfSight()
|
||||||
{
|
{
|
||||||
@ -39,91 +37,20 @@ void UPS_AI_Behavior_EQSTest_LineOfSight::RunTest(FEnvQueryInstance& QueryInstan
|
|||||||
|
|
||||||
FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(LOSTestEQS), true);
|
FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(LOSTestEQS), true);
|
||||||
|
|
||||||
// Ignore the threat actor itself (e.g. AimTargetActor sphere blocks its own traces)
|
const FVector TraceEnd = ThreatLoc + FVector(0, 0, TargetHeightOffset);
|
||||||
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)
|
for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
|
||||||
{
|
{
|
||||||
const FVector CandidatePos = GetItemLocation(QueryInstance, It.GetIndex());
|
const FVector CandidatePos = GetItemLocation(QueryInstance, It.GetIndex());
|
||||||
|
const FVector TraceStart = CandidatePos + FVector(0, 0, TraceHeight);
|
||||||
|
|
||||||
// Direction from candidate to threat (2D) and its perpendicular
|
FHitResult Hit;
|
||||||
const FVector DirToThreat = (ThreatLoc - CandidatePos).GetSafeNormal2D();
|
const bool bBlocked = World->LineTraceSingleByChannel(
|
||||||
const FVector LateralDir = FVector::CrossProduct(FVector::UpVector, DirToThreat);
|
Hit, TraceStart, TraceEnd, ECC_Visibility, TraceParams);
|
||||||
|
|
||||||
int32 ClearCount = 0;
|
// Score: 1.0 = clear LOS (not blocked), 0.0 = blocked
|
||||||
for (float Lateral : LateralOffsets)
|
const float Score = bBlocked ? 0.0f : 1.0f;
|
||||||
{
|
It.SetScore(TestPurpose, FilterType, Score, 0.0f, 1.0f);
|
||||||
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,7 +16,6 @@
|
|||||||
#include "GameFramework/SpectatorPawn.h"
|
#include "GameFramework/SpectatorPawn.h"
|
||||||
#include "AIController.h"
|
#include "AIController.h"
|
||||||
#include "BehaviorTree/BlackboardComponent.h"
|
#include "BehaviorTree/BlackboardComponent.h"
|
||||||
#include "EngineUtils.h"
|
|
||||||
|
|
||||||
UPS_AI_Behavior_PerceptionComponent::UPS_AI_Behavior_PerceptionComponent()
|
UPS_AI_Behavior_PerceptionComponent::UPS_AI_Behavior_PerceptionComponent()
|
||||||
{
|
{
|
||||||
@ -271,53 +270,6 @@ AActor* UPS_AI_Behavior_PerceptionComponent::GetHighestThreatActor(
|
|||||||
TArray<AActor*> PerceivedActors;
|
TArray<AActor*> PerceivedActors;
|
||||||
GetCurrentlyPerceivedActors(nullptr, PerceivedActors); // All senses at once
|
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)
|
if (PerceivedActors.Num() == 0)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@ -25,11 +25,11 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, Category = "Cover", meta = (ClampMin = "1", ClampMax = "5"))
|
UPROPERTY(EditAnywhere, Category = "Cover", meta = (ClampMin = "1", ClampMax = "5"))
|
||||||
int32 NumTraceHeights = 3;
|
int32 NumTraceHeights = 3;
|
||||||
|
|
||||||
/** Minimum height for the lowest trace (cm relative to item — items are at navmesh level). */
|
/** Minimum height for the lowest trace (cm relative to item — items are at capsule center, ~90cm above ground). */
|
||||||
UPROPERTY(EditAnywhere, Category = "Cover")
|
UPROPERTY(EditAnywhere, Category = "Cover")
|
||||||
float MinTraceHeight = 0.0f;
|
float MinTraceHeight = 0.0f;
|
||||||
|
|
||||||
/** Maximum height for the highest trace (cm relative to item — 70 ≈ knee-to-waist from navmesh). */
|
/** Maximum height for the highest trace (cm relative to item — 70 ≈ top of head from capsule center). */
|
||||||
UPROPERTY(EditAnywhere, Category = "Cover")
|
UPROPERTY(EditAnywhere, Category = "Cover")
|
||||||
float MaxTraceHeight = 70.0f;
|
float MaxTraceHeight = 70.0f;
|
||||||
|
|
||||||
@ -48,9 +48,6 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, Category = "Cover", meta = (ClampMin = "0.0", ClampMax = "100.0"))
|
UPROPERTY(EditAnywhere, Category = "Cover", meta = (ClampMin = "0.0", ClampMax = "100.0"))
|
||||||
float LateralSpread = 30.0f;
|
float LateralSpread = 30.0f;
|
||||||
|
|
||||||
/** Draw debug boxes for each candidate with color-coded score. */
|
|
||||||
UPROPERTY(EditAnywhere, Category = "Debug")
|
|
||||||
bool bDrawDebug = false;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
|
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
|
||||||
|
|||||||
@ -31,18 +31,6 @@ public:
|
|||||||
UPROPERTY(EditDefaultsOnly, Category = "LOS", meta = (ClampMin = "0.0"))
|
UPROPERTY(EditDefaultsOnly, Category = "LOS", meta = (ClampMin = "0.0"))
|
||||||
float TargetHeightOffset = 100.0f;
|
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:
|
protected:
|
||||||
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
|
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
|
||||||
virtual FText GetDescriptionTitle() 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