413 lines
11 KiB
C++
413 lines
11 KiB
C++
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
|
|
|
|
|
#include "Targeting/GCS_TargetingSystemComponent.h"
|
|
|
|
#include "GCS_LogChannels.h"
|
|
#include "Kismet/KismetMathLibrary.h"
|
|
#include "Camera/CameraComponent.h"
|
|
#include "GameFramework/Character.h"
|
|
#include "Camera/PlayerCameraManager.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
#include "TargetingSystem/TargetingSubsystem.h"
|
|
|
|
UGCS_TargetingSystemComponent::UGCS_TargetingSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
|
{
|
|
PrimaryComponentTick.bCanEverTick = true;
|
|
SetIsReplicatedByDefault(true);
|
|
}
|
|
|
|
UGCS_TargetingSystemComponent* UGCS_TargetingSystemComponent::GetTargetingSystemComponent(const AActor* Actor)
|
|
{
|
|
return Actor ? Actor->FindComponentByClass<UGCS_TargetingSystemComponent>() : nullptr;
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
DOREPLIFETIME_CONDITION(ThisClass, TargetedActor, COND_OwnerOnly);
|
|
}
|
|
|
|
|
|
// Called when the game starts
|
|
void UGCS_TargetingSystemComponent::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
// ...
|
|
}
|
|
|
|
|
|
// Called every frame
|
|
void UGCS_TargetingSystemComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
|
{
|
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
|
|
|
RefreshTargeting(DeltaTime);
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::RefreshTargeting(float DeltaTime)
|
|
{
|
|
if (GetOwnerRole() >= ROLE_AutonomousProxy)
|
|
{
|
|
if (bAutoUpdatePotentialTargets || IsValid(TargetedActor))
|
|
{
|
|
RefreshPotentialTargets();
|
|
}
|
|
|
|
bool LocalBool = PotentialTargets.Contains(TargetedActor);
|
|
|
|
// Current TargetedActor no longer consider valid.
|
|
if (TargetedActor && !LocalBool)
|
|
{
|
|
OnLockOff();
|
|
SetTargetedActor(nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::SearchForActorToTarget()
|
|
{
|
|
RefreshPotentialTargets();
|
|
SelectFromPotentialTargets();
|
|
//No target found/unlocked.
|
|
if (!IsValid(TargetedActor) && !bAutoUpdatePotentialTargets)
|
|
{
|
|
PotentialTargets.Empty();
|
|
}
|
|
}
|
|
|
|
AActor* UGCS_TargetingSystemComponent::SelectClosestActorFromPotentialTargets(float Radius) const
|
|
{
|
|
// 检查 Owner 是否有效
|
|
if (!GetOwner())
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("SelectClosestActorFromPotentialTargets: Owner is null"));
|
|
return nullptr;
|
|
}
|
|
|
|
// 检查 PotentialTargets 是否为空
|
|
if (PotentialTargets.Num() == 0)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// 过滤有效 Actor 并在范围内
|
|
TArray<AActor*> FilteredPotentialTargets;
|
|
const FVector OwnerLocation = GetOwner()->GetActorLocation();
|
|
|
|
for (AActor* Actor : PotentialTargets)
|
|
{
|
|
// 检查 Actor 是否有效且未被销毁
|
|
if (IsValid(Actor))
|
|
{
|
|
float Distance = FVector::Dist(OwnerLocation, Actor->GetActorLocation());
|
|
if (Distance <= Radius)
|
|
{
|
|
FilteredPotentialTargets.Add(Actor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 如果过滤后没有有效目标
|
|
if (FilteredPotentialTargets.Num() == 0)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// 寻找最近的 Actor
|
|
AActor* ClosestActor = FilteredPotentialTargets[0];
|
|
float MinDistance = FVector::Dist(OwnerLocation, ClosestActor->GetActorLocation());
|
|
|
|
for (int32 i = 1; i < FilteredPotentialTargets.Num(); ++i)
|
|
{
|
|
AActor* CurrentActor = FilteredPotentialTargets[i];
|
|
if (IsValid(CurrentActor))
|
|
{
|
|
float Distance = FVector::Dist(OwnerLocation, CurrentActor->GetActorLocation());
|
|
if (Distance < MinDistance)
|
|
{
|
|
MinDistance = Distance;
|
|
ClosestActor = CurrentActor;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ClosestActor;
|
|
}
|
|
|
|
bool UGCS_TargetingSystemComponent::FilterActorsWithPreset(UTargetingPreset* InTargetingPreset, const TArray<AActor*> InTargets, TArray<AActor*>& OutActors)
|
|
{
|
|
if (InTargetingPreset == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (UTargetingSubsystem* TargetingSubsystem = UTargetingSubsystem::Get(GetWorld()))
|
|
{
|
|
FTargetingSourceContext SourceContext;
|
|
SourceContext.SourceActor = GetOwner();
|
|
|
|
FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(TargetingPreset, SourceContext);
|
|
|
|
if (TargetingHandle.IsValid() && InTargets.Num() > 0)
|
|
{
|
|
FTargetingDefaultResultsSet& TargetingResults = FTargetingDefaultResultsSet::FindOrAdd(TargetingHandle);
|
|
for (AActor* Target : InTargets)
|
|
{
|
|
if (!Target)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bAddResult = !TargetingResults.TargetResults.ContainsByPredicate([Target](const FTargetingDefaultResultData& Data) -> bool
|
|
{
|
|
return (Data.HitResult.GetActor() == Target);
|
|
});
|
|
|
|
if (bAddResult)
|
|
{
|
|
FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData();
|
|
ResultData->HitResult.HitObjectHandle = FActorInstanceHandle(Target);
|
|
ResultData->HitResult.Location = Target->GetActorLocation();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateWeakLambda(this, [&](FTargetingRequestHandle InTargetingHandle)
|
|
{
|
|
TargetingSubsystem->GetTargetingResultsActors(InTargetingHandle, OutActors);
|
|
});
|
|
|
|
FTargetingImmediateTaskData& ImmeidateTaskData = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle);
|
|
ImmeidateTaskData.bReleaseOnCompletion = true;
|
|
|
|
TargetingSubsystem->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate);
|
|
}
|
|
|
|
return !OutActors.IsEmpty();
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::SetTargetedActor(AActor* NewActor)
|
|
{
|
|
SetTargetedActor(NewActor, true);
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::SetTargetedActor(AActor* NewActor, bool bSendRpc)
|
|
{
|
|
if (GetOwnerRole() < ROLE_AutonomousProxy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bSendRpc)
|
|
{
|
|
if (GetOwnerRole() >= ROLE_Authority)
|
|
{
|
|
ClientSetTargetedActor(NewActor);
|
|
}
|
|
else
|
|
{
|
|
ServerSetTargetedActor(NewActor);
|
|
}
|
|
}
|
|
|
|
TargetedActor = NewActor;
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::ClientSetTargetedActor_Implementation(AActor* NewActor)
|
|
{
|
|
SetTargetedActor(NewActor, false);
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::ServerSetTargetedActor_Implementation(AActor* NewActor)
|
|
{
|
|
SetTargetedActor(NewActor, false);
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::OnLockOff_Implementation()
|
|
{
|
|
OnTargetLockOffEvent.Broadcast(TargetedActor);
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::OnLockOn_Implementation()
|
|
{
|
|
OnTargetLockOnEvent.Broadcast(TargetedActor);
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::SelectFromPotentialTargets()
|
|
{
|
|
if (!IsValid(TargetedActor))
|
|
{
|
|
TMap<AActor*, float> LocalPotentialTargets;
|
|
|
|
for (TObjectPtr<AActor>& Elem : PotentialTargets)
|
|
{
|
|
if (CanBeTargeted(Elem))
|
|
{
|
|
LocalPotentialTargets.Add(Elem, UKismetMathLibrary::Abs(CalculateViewAngle(Elem)));
|
|
}
|
|
}
|
|
|
|
if (LocalPotentialTargets.Num() > 0)
|
|
{
|
|
TArray<AActor*> LocalTargets;
|
|
TArray<float> LocalAngles;
|
|
LocalPotentialTargets.GenerateKeyArray(LocalTargets);
|
|
LocalPotentialTargets.GenerateValueArray(LocalAngles);
|
|
|
|
int32 MinIndex;
|
|
const float MaxValue = FMath::Min<float>(LocalAngles, &MinIndex);
|
|
SetTargetedActor(LocalTargets[MinIndex]);
|
|
OnLockOn();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OnLockOff();
|
|
SetTargetedActor(nullptr);
|
|
}
|
|
}
|
|
|
|
void UGCS_TargetingSystemComponent::RefreshPotentialTargets()
|
|
{
|
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TargetingSystemComponent::RefreshPotentialTargets"), UGCS_TargetingSystemComponent_RefreshPotentialTargets, STATGROUP_GCS)
|
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
|
if (TargetingPreset == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (UTargetingSubsystem* TargetingSubsystem = UTargetingSubsystem::Get(GetWorld()))
|
|
{
|
|
FTargetingSourceContext SourceContext;
|
|
SourceContext.SourceActor = GetOwner();
|
|
SourceContext.SourceObject = this;
|
|
|
|
FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(TargetingPreset, SourceContext);
|
|
FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateWeakLambda(this, [this,TargetingSubsystem](FTargetingRequestHandle InTargetingHandle)
|
|
{
|
|
TArray<AActor*> Results;
|
|
TargetingSubsystem->GetTargetingResultsActors(InTargetingHandle, Results);
|
|
PotentialTargets.Empty();
|
|
PotentialTargets = Results;
|
|
});
|
|
|
|
if (bUseAsyncTargeting)
|
|
{
|
|
FTargetingAsyncTaskData& AsyncTaskData = FTargetingAsyncTaskData::FindOrAdd(TargetingHandle);
|
|
AsyncTaskData.bReleaseOnCompletion = true;
|
|
|
|
TargetingSubsystem->StartAsyncTargetingRequestWithHandle(TargetingHandle, Delegate);
|
|
}
|
|
else
|
|
{
|
|
FTargetingImmediateTaskData& ImmeidateTaskData = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle);
|
|
ImmeidateTaskData.bReleaseOnCompletion = true;
|
|
|
|
TargetingSubsystem->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UGCS_TargetingSystemComponent::CanBeTargeted_Implementation(AActor* ActorToTarget)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
void UGCS_TargetingSystemComponent::StaticSwitchToNewTarget(bool RightDirection)
|
|
{
|
|
if (TargetedActor)
|
|
{
|
|
TMap<AActor*, float> LocalPotentialTargets;
|
|
|
|
for (TObjectPtr<AActor>& Elem : PotentialTargets)
|
|
{
|
|
if (Elem != TargetedActor)
|
|
{
|
|
float LocalDistance = UKismetMathLibrary::Vector_Distance(GetOwner()->GetActorLocation(), Elem->GetActorLocation());
|
|
|
|
FRotator RequiredRotation = UKismetMathLibrary::FindLookAtRotation(GetOwner()->GetActorLocation(), Elem->GetActorLocation());
|
|
FRotator DeltaRotation;
|
|
|
|
ACharacter* LocalOwnerCharacter = Cast<ACharacter>(GetOwner());
|
|
|
|
DeltaRotation = UKismetMathLibrary::NormalizedDeltaRotator(LocalOwnerCharacter->GetControlRotation(), RequiredRotation);
|
|
|
|
if (RightDirection == 1)
|
|
{
|
|
if (DeltaRotation.Yaw < 0.f && DeltaRotation.Yaw > -100.f)
|
|
{
|
|
LocalPotentialTargets.Add(Elem, DeltaRotation.Yaw);
|
|
}
|
|
else
|
|
{
|
|
LocalPotentialTargets.Add(Elem, -10000.f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (DeltaRotation.Yaw > 0.f && DeltaRotation.Yaw < 100.f)
|
|
{
|
|
LocalPotentialTargets.Add(Elem, DeltaRotation.Yaw);
|
|
}
|
|
else
|
|
{
|
|
LocalPotentialTargets.Add(Elem, 10000.f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LocalPotentialTargets.Num() > 0)
|
|
{
|
|
TArray<AActor*> LocalTargets;
|
|
TArray<float> LocalAngles;
|
|
LocalPotentialTargets.GenerateKeyArray(LocalTargets);
|
|
LocalPotentialTargets.GenerateValueArray(LocalAngles);
|
|
|
|
if (RightDirection == 1)
|
|
{
|
|
int32 FoundIndex;
|
|
const float MaxValue = FMath::Max<float>(LocalAngles, &FoundIndex);
|
|
if (MaxValue > -10000.f)
|
|
{
|
|
OnLockOff();
|
|
SetTargetedActor(LocalTargets[FoundIndex]);
|
|
OnLockOn();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 FoundIndex;
|
|
const float MinValue = FMath::Min<float>(LocalAngles, &FoundIndex);
|
|
if (MinValue < 10000.f)
|
|
{
|
|
OnLockOff();
|
|
SetTargetedActor(LocalTargets[FoundIndex]);
|
|
OnLockOn();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
float UGCS_TargetingSystemComponent::CalculateViewAngle(const AActor* TargetActor)
|
|
{
|
|
APawn* OwningPawn = GetPawn<APawn>();
|
|
FRotator FinalRotation = FRotator::ZeroRotator;
|
|
if (TargetActor && OwningPawn)
|
|
{
|
|
FRotator RequiredRotation = UKismetMathLibrary::FindLookAtRotation(OwningPawn->GetPawnViewLocation(), TargetActor->GetActorLocation());
|
|
|
|
FRotator DeltaRotation = UKismetMathLibrary::NormalizedDeltaRotator(RequiredRotation, OwningPawn->GetViewRotation());
|
|
FinalRotation = DeltaRotation;
|
|
}
|
|
|
|
return UKismetMathLibrary::Abs(UKismetMathLibrary::Abs(FinalRotation.Yaw) + UKismetMathLibrary::Abs(FinalRotation.Pitch));
|
|
}
|