第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetActors/GGA_AbilityTargetActor_LineTrace.h"
#include "DrawDebugHelpers.h"
#include "GameFramework/PlayerController.h"
AGGA_AbilityTargetActor_LineTrace::AGGA_AbilityTargetActor_LineTrace()
{
}
void AGGA_AbilityTargetActor_LineTrace::Configure(
const FGameplayAbilityTargetingLocationInfo& InStartLocation,
FGameplayTag InAimingTag,
FGameplayTag InAimingRemovalTag,
FCollisionProfileName InTraceProfile,
FGameplayTargetDataFilterHandle InFilter,
TSubclassOf<AGameplayAbilityWorldReticle> InReticleClass,
FWorldReticleParameters InReticleParams,
bool bInIgnoreBlockingHits,
bool bInShouldProduceTargetDataOnServer,
bool bInUsePersistentHitResults,
bool bInDebug,
bool bInTraceAffectsAimPitch,
bool bInTraceFromPlayerViewPoint,
bool bInUseAimingSpreadMod,
float InMaxRange,
float InBaseSpread,
float InAimingSpreadMod,
float InTargetingSpreadIncrement,
float InTargetingSpreadMax,
int32 InMaxHitResultsPerTrace,
int32 InNumberOfTraces)
{
StartLocation = InStartLocation;
AimingTag = InAimingTag;
AimingRemovalTag = InAimingRemovalTag;
TraceProfile = InTraceProfile;
Filter = InFilter;
ReticleClass = InReticleClass;
ReticleParams = InReticleParams;
bIgnoreBlockingHits = bInIgnoreBlockingHits;
ShouldProduceTargetDataOnServer = bInShouldProduceTargetDataOnServer;
bUsePersistentHitResults = bInUsePersistentHitResults;
bDebug = bInDebug;
bTraceAffectsAimPitch = bInTraceAffectsAimPitch;
bTraceFromPlayerViewPoint = bInTraceFromPlayerViewPoint;
bUseAimingSpreadMod = bInUseAimingSpreadMod;
MaxRange = InMaxRange;
BaseSpread = InBaseSpread;
AimingSpreadMod = InAimingSpreadMod;
TargetingSpreadIncrement = InTargetingSpreadIncrement;
TargetingSpreadMax = InTargetingSpreadMax;
MaxHitResultsPerTrace = InMaxHitResultsPerTrace;
NumberOfTraces = InNumberOfTraces;
if (bUsePersistentHitResults)
{
NumberOfTraces = 1;
}
}
void AGGA_AbilityTargetActor_LineTrace::DoTrace(TArray<FHitResult>& HitResults, const UWorld* World, const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start, const FVector& End, FName ProfileName,
const FCollisionQueryParams Params)
{
LineTraceWithFilter(HitResults, World, FilterHandle, Start, End, ProfileName, Params);
}
void AGGA_AbilityTargetActor_LineTrace::ShowDebugTrace(TArray<FHitResult>& HitResults, EDrawDebugTrace::Type DrawDebugType, float Duration)
{
#if ENABLE_DRAW_DEBUG
if (bDebug)
{
FVector ViewStart = StartLocation.GetTargetingTransform().GetLocation();
FRotator ViewRot;
if (PrimaryPC && bTraceFromPlayerViewPoint)
{
PrimaryPC->GetPlayerViewPoint(ViewStart, ViewRot);
}
FVector TraceEnd = HitResults[0].TraceEnd;
if (NumberOfTraces > 1 || bUsePersistentHitResults)
{
TraceEnd = CurrentTraceEnd;
}
DrawDebugLineTraceMulti(GetWorld(), ViewStart, TraceEnd, DrawDebugType, true, HitResults, FLinearColor::Green, FLinearColor::Red, Duration);
}
#endif
}
#if ENABLE_DRAW_DEBUG
// Copied from KismetTraceUtils.cpp
void AGGA_AbilityTargetActor_LineTrace::DrawDebugLineTraceMulti(const UWorld* World, const FVector& Start, const FVector& End, EDrawDebugTrace::Type DrawDebugType, bool bHit, const TArray<FHitResult>& OutHits,
FLinearColor TraceColor, FLinearColor TraceHitColor, float DrawTime)
{
if (DrawDebugType != EDrawDebugTrace::None)
{
bool bPersistent = DrawDebugType == EDrawDebugTrace::Persistent;
float LifeTime = (DrawDebugType == EDrawDebugTrace::ForDuration) ? DrawTime : 0.f;
// @fixme, draw line with thickness = 2.f?
if (bHit && OutHits.Last().bBlockingHit)
{
// Red up to the blocking hit, green thereafter
FVector const BlockingHitPoint = OutHits.Last().ImpactPoint;
::DrawDebugLine(World, Start, BlockingHitPoint, TraceColor.ToFColor(true), bPersistent, LifeTime);
::DrawDebugLine(World, BlockingHitPoint, End, TraceHitColor.ToFColor(true), bPersistent, LifeTime);
}
else
{
// no hit means all red
::DrawDebugLine(World, Start, End, TraceColor.ToFColor(true), bPersistent, LifeTime);
}
// draw hits
for (int32 HitIdx = 0; HitIdx < OutHits.Num(); ++HitIdx)
{
FHitResult const& Hit = OutHits[HitIdx];
::DrawDebugPoint(World, Hit.ImpactPoint, 16.0f, (Hit.bBlockingHit ? TraceColor.ToFColor(true) : TraceHitColor.ToFColor(true)), bPersistent, LifeTime);
}
}
}
#endif // ENABLE_DRAW_DEBUG

View File

@@ -0,0 +1,187 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetActors/GGA_AbilityTargetActor_SphereTrace.h"
#include "WorldCollision.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "DrawDebugHelpers.h"
AGGA_AbilityTargetActor_SphereTrace::AGGA_AbilityTargetActor_SphereTrace()
{
TraceSphereRadius = 100.0f;
}
void AGGA_AbilityTargetActor_SphereTrace::Configure(
const FGameplayAbilityTargetingLocationInfo& InStartLocation,
FGameplayTag InAimingTag,
FGameplayTag InAimingRemovalTag,
FCollisionProfileName InTraceProfile,
FGameplayTargetDataFilterHandle InFilter,
TSubclassOf<AGameplayAbilityWorldReticle> InReticleClass,
FWorldReticleParameters InReticleParams,
bool bInIgnoreBlockingHits,
bool bInShouldProduceTargetDataOnServer,
bool bInUsePersistentHitResults,
bool bInDebug,
bool bInTraceAffectsAimPitch,
bool bInTraceFromPlayerViewPoint,
bool bInUseAimingSpreadMod,
float InMaxRange,
float InTraceSphereRadius,
float InBaseSpread,
float InAimingSpreadMod,
float InTargetingSpreadIncrement,
float InTargetingSpreadMax,
int32 InMaxHitResultsPerTrace,
int32 InNumberOfTraces)
{
StartLocation = InStartLocation;
AimingTag = InAimingTag;
AimingRemovalTag = InAimingRemovalTag;
TraceProfile = InTraceProfile;
Filter = InFilter;
ReticleClass = InReticleClass;
ReticleParams = InReticleParams;
bIgnoreBlockingHits = bInIgnoreBlockingHits;
ShouldProduceTargetDataOnServer = bInShouldProduceTargetDataOnServer;
bUsePersistentHitResults = bInUsePersistentHitResults;
bDebug = bInDebug;
bTraceAffectsAimPitch = bInTraceAffectsAimPitch;
bTraceFromPlayerViewPoint = bInTraceFromPlayerViewPoint;
bUseAimingSpreadMod = bInUseAimingSpreadMod;
MaxRange = InMaxRange;
TraceSphereRadius = InTraceSphereRadius;
BaseSpread = InBaseSpread;
AimingSpreadMod = InAimingSpreadMod;
TargetingSpreadIncrement = InTargetingSpreadIncrement;
TargetingSpreadMax = InTargetingSpreadMax;
MaxHitResultsPerTrace = InMaxHitResultsPerTrace;
NumberOfTraces = InNumberOfTraces;
if (bUsePersistentHitResults)
{
NumberOfTraces = 1;
}
}
void AGGA_AbilityTargetActor_SphereTrace::SphereTraceWithFilter(TArray<FHitResult>& OutHitResults, const UWorld* World,
const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start,
const FVector& End, float Radius, FName ProfileName,
const FCollisionQueryParams Params)
{
check(World);
TArray<FHitResult> HitResults;
World->SweepMultiByProfile(HitResults, Start, End, FQuat::Identity, ProfileName,
FCollisionShape::MakeSphere(Radius), Params);
TArray<FHitResult> FilteredHitResults;
// Start param could be player ViewPoint. We want HitResult to always display the StartLocation.
FVector TraceStart = StartLocation.GetTargetingTransform().GetLocation();
for (int32 HitIdx = 0; HitIdx < HitResults.Num(); ++HitIdx)
{
FHitResult& Hit = HitResults[HitIdx];
AActor* HitActor = Hit.GetActor();
if (!HitActor || FilterHandle.FilterPassesForActor(HitActor))
{
Hit.TraceStart = TraceStart;
Hit.TraceEnd = End;
FilteredHitResults.Add(Hit);
}
}
OutHitResults = FilteredHitResults;
return;
}
void AGGA_AbilityTargetActor_SphereTrace::DoTrace(TArray<FHitResult>& HitResults, const UWorld* World,
const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start,
const FVector& End, FName ProfileName, const FCollisionQueryParams Params)
{
SphereTraceWithFilter(HitResults, World, FilterHandle, Start, End, TraceSphereRadius, ProfileName, Params);
}
void AGGA_AbilityTargetActor_SphereTrace::ShowDebugTrace(TArray<FHitResult>& HitResults, EDrawDebugTrace::Type DrawDebugType,
float Duration)
{
#if ENABLE_DRAW_DEBUG
if (bDebug)
{
FVector ViewStart = StartLocation.GetTargetingTransform().GetLocation();
FRotator ViewRot;
if (PrimaryPC && bTraceFromPlayerViewPoint)
{
PrimaryPC->GetPlayerViewPoint(ViewStart, ViewRot);
}
FVector TraceEnd = HitResults[0].TraceEnd;
if (NumberOfTraces > 1 || bUsePersistentHitResults)
{
TraceEnd = CurrentTraceEnd;
}
DrawDebugSphereTraceMulti(GetWorld(), ViewStart, TraceEnd, TraceSphereRadius, DrawDebugType, true, HitResults,
FLinearColor::Green, FLinearColor::Red, Duration);
}
#endif
}
#if ENABLE_DRAW_DEBUG
// Copied from KismetTraceUtils.cpp
void AGGA_AbilityTargetActor_SphereTrace::DrawDebugSweptSphere(const UWorld* InWorld, FVector const& Start, FVector const& End,
float Radius, FColor const& Color, bool bPersistentLines, float LifeTime,
uint8 DepthPriority)
{
FVector const TraceVec = End - Start;
float const Dist = TraceVec.Size();
FVector const Center = Start + TraceVec * 0.5f;
float const HalfHeight = (Dist * 0.5f) + Radius;
FQuat const CapsuleRot = FRotationMatrix::MakeFromZ(TraceVec).ToQuat();
::DrawDebugCapsule(InWorld, Center, HalfHeight, Radius, CapsuleRot, Color, bPersistentLines, LifeTime,
DepthPriority);
}
void AGGA_AbilityTargetActor_SphereTrace::DrawDebugSphereTraceMulti(const UWorld* World, const FVector& Start, const FVector& End,
float Radius, EDrawDebugTrace::Type DrawDebugType, bool bHit,
const TArray<FHitResult>& OutHits, FLinearColor TraceColor,
FLinearColor TraceHitColor, float DrawTime)
{
if (DrawDebugType != EDrawDebugTrace::None)
{
bool bPersistent = DrawDebugType == EDrawDebugTrace::Persistent;
float LifeTime = (DrawDebugType == EDrawDebugTrace::ForDuration) ? DrawTime : 0.f;
if (bHit && OutHits.Last().bBlockingHit)
{
// Red up to the blocking hit, green thereafter
FVector const BlockingHitPoint = OutHits.Last().Location;
DrawDebugSweptSphere(World, Start, BlockingHitPoint, Radius, TraceColor.ToFColor(true), bPersistent,
LifeTime);
DrawDebugSweptSphere(World, BlockingHitPoint, End, Radius, TraceHitColor.ToFColor(true), bPersistent,
LifeTime);
}
else
{
// no hit means all red
DrawDebugSweptSphere(World, Start, End, Radius, TraceColor.ToFColor(true), bPersistent, LifeTime);
}
// draw hits
for (int32 HitIdx = 0; HitIdx < OutHits.Num(); ++HitIdx)
{
FHitResult const& Hit = OutHits[HitIdx];
::DrawDebugPoint(World, Hit.ImpactPoint, 16.0f,
(Hit.bBlockingHit ? TraceColor.ToFColor(true) : TraceHitColor.ToFColor(true)), bPersistent,
LifeTime);
}
}
}
#endif // ENABLE_DRAW_DEBUG

View File

@@ -0,0 +1,622 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetActors/GGA_AbilityTargetActor_Trace.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemLog.h"
#include "Engine/World.h"
#include "DrawDebugHelpers.h"
#include "GameFramework/PlayerController.h"
#include "GameplayAbilitySpec.h"
#include "Kismet/KismetMathLibrary.h"
AGGA_AbilityTargetActor_Trace::AGGA_AbilityTargetActor_Trace()
{
bDestroyOnConfirmation = false;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickGroup = TG_PostUpdateWork;
MaxHitResultsPerTrace = 1;
NumberOfTraces = 1;
bIgnoreBlockingHits = false;
bTraceAffectsAimPitch = true;
bTraceFromPlayerViewPoint = false;
MaxRange = 999999.0f;
bUseAimingSpreadMod = false;
BaseSpread = 0.0f;
AimingSpreadMod = 0.0f;
TargetingSpreadIncrement = 0.0f;
TargetingSpreadMax = 0.0f;
CurrentTargetingSpread = 0.0f;
bUsePersistentHitResults = false;
}
void AGGA_AbilityTargetActor_Trace::ResetSpread()
{
bUseAimingSpreadMod = false;
BaseSpread = 0.0f;
AimingSpreadMod = 0.0f;
TargetingSpreadIncrement = 0.0f;
TargetingSpreadMax = 0.0f;
CurrentTargetingSpread = 0.0f;
}
float AGGA_AbilityTargetActor_Trace::GetCurrentSpread() const
{
float FinalSpread = BaseSpread + CurrentTargetingSpread;
if (bUseAimingSpreadMod && AimingTag.IsValid() && AimingRemovalTag.IsValid())
{
UAbilitySystemComponent* ASC = OwningAbility->GetCurrentActorInfo()->AbilitySystemComponent.Get();
if (ASC && (ASC->GetTagCount(AimingTag) > ASC->GetTagCount(AimingRemovalTag)))
{
FinalSpread *= AimingSpreadMod;
}
}
return FinalSpread;
}
void AGGA_AbilityTargetActor_Trace::SetStartLocation(const FGameplayAbilityTargetingLocationInfo& InStartLocation)
{
StartLocation = InStartLocation;
}
void AGGA_AbilityTargetActor_Trace::SetShouldProduceTargetDataOnServer(bool bInShouldProduceTargetDataOnServer)
{
ShouldProduceTargetDataOnServer = bInShouldProduceTargetDataOnServer;
}
void AGGA_AbilityTargetActor_Trace::SetDestroyOnConfirmation(bool bInDestroyOnConfirmation)
{
bDestroyOnConfirmation = bInDestroyOnConfirmation;
}
void AGGA_AbilityTargetActor_Trace::StartTargeting(UGameplayAbility* Ability)
{
// Don't call to Super because we can have more than one Reticle
SetActorTickEnabled(true);
OwningAbility = Ability;
SourceActor = Ability->GetCurrentActorInfo()->AvatarActor.Get();
// This is a lazy way of emptying and repopulating the ReticleActors.
// We could come up with a solution that reuses them.
DestroyReticleActors();
if (ReticleClass)
{
for (int32 i = 0; i < MaxHitResultsPerTrace * NumberOfTraces; i++)
{
SpawnReticleActor(GetActorLocation(), GetActorRotation());
}
}
if (bUsePersistentHitResults)
{
PersistentHitResults.Empty();
}
}
void AGGA_AbilityTargetActor_Trace::ConfirmTargetingAndContinue()
{
check(ShouldProduceTargetData());
if (SourceActor)
{
TArray<FHitResult> HitResults = PerformTrace(SourceActor);
FGameplayAbilityTargetDataHandle Handle = MakeTargetData(HitResults);
TargetDataReadyDelegate.Broadcast(Handle);
#if ENABLE_DRAW_DEBUG
if (bDebug)
{
ShowDebugTrace(HitResults, EDrawDebugTrace::Type::ForDuration, 2.0f);
}
#endif
}
if (bUsePersistentHitResults)
{
PersistentHitResults.Empty();
}
}
void AGGA_AbilityTargetActor_Trace::CancelTargeting()
{
const FGameplayAbilityActorInfo* ActorInfo = (OwningAbility ? OwningAbility->GetCurrentActorInfo() : nullptr);
UAbilitySystemComponent* ASC = (ActorInfo ? ActorInfo->AbilitySystemComponent.Get() : nullptr);
if (ASC)
{
ASC->AbilityReplicatedEventDelegate(EAbilityGenericReplicatedEvent::GenericCancel,
OwningAbility->GetCurrentAbilitySpecHandle(),
OwningAbility->GetCurrentActivationInfo().GetActivationPredictionKey()).
Remove(GenericCancelHandle);
}
else
{
ABILITY_LOG(Warning, TEXT("AGameplayAbilityTargetActor::CancelTargeting called with null ASC! Actor %s"),
*GetName());
}
CanceledDelegate.Broadcast(FGameplayAbilityTargetDataHandle());
SetActorTickEnabled(false);
if (bUsePersistentHitResults)
{
PersistentHitResults.Empty();
}
}
void AGGA_AbilityTargetActor_Trace::BeginPlay()
{
Super::BeginPlay();
// 一开始就禁用Tick。我们会在StartTargeting中启用并在StopTargeting中禁用。
// 对于瞬间确认tick永远不会发生因为我们一次性StartConfirm然后马上Stop。
SetActorTickEnabled(false);
}
void AGGA_AbilityTargetActor_Trace::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
DestroyReticleActors();
Super::EndPlay(EndPlayReason);
}
void AGGA_AbilityTargetActor_Trace::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
TArray<FHitResult> HitResults;
if (bDebug || bUsePersistentHitResults)
{
// Only need to trace on Tick if we're showing debug or if we use persistent hit results, otherwise we just use the confirmation trace
HitResults = PerformTrace(SourceActor);
if (HitResults.Num() > 0)
{
const FVector StartPoint = StartLocation.GetTargetingTransform().GetLocation();
const FVector EndPoint = HitResults[0].Component.IsValid() ? HitResults[0].ImpactPoint : HitResults[0].TraceEnd;
const FRotator Rotator = UKismetMathLibrary::FindLookAtRotation(StartPoint, EndPoint);
SetActorLocationAndRotation(StartPoint, Rotator);
}
}
#if ENABLE_DRAW_DEBUG
if (SourceActor && bDebug)
{
ShowDebugTrace(HitResults, EDrawDebugTrace::Type::ForOneFrame);
}
#endif
}
void AGGA_AbilityTargetActor_Trace::LineTraceWithFilter(TArray<FHitResult>& OutHitResults, const UWorld* World,
const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start,
const FVector& End, FName ProfileName, const FCollisionQueryParams Params)
{
check(World);
TArray<FHitResult> HitResults;
World->LineTraceMultiByProfile(HitResults, Start, End, ProfileName, Params);
TArray<FHitResult> FilteredHitResults;
// Start param could be player ViewPoint. We want HitResult to always display the StartLocation.
FVector TraceStart = StartLocation.GetTargetingTransform().GetLocation();
for (int32 HitIdx = 0; HitIdx < HitResults.Num(); ++HitIdx)
{
FHitResult& Hit = HitResults[HitIdx];
AActor* HitActor = Hit.GetActor();
if (!HitActor || FilterHandle.FilterPassesForActor(HitActor))
{
Hit.TraceStart = TraceStart;
Hit.TraceEnd = End;
FilteredHitResults.Add(Hit);
}
}
OutHitResults = FilteredHitResults;
}
void AGGA_AbilityTargetActor_Trace::AimWithPlayerController(const AActor* InSourceActor, FCollisionQueryParams Params,
const FVector& TraceStart, FVector& OutTraceEnd, bool bIgnorePitch)
{
if (!OwningAbility) // Server and launching client only
{
return;
}
// Default values in case of AI Controller
FVector ViewStart = TraceStart;
FRotator ViewRot = StartLocation.GetTargetingTransform().GetRotation().Rotator();
if (PrimaryPC && bTraceFromPlayerViewPoint)
{
PrimaryPC->GetPlayerViewPoint(ViewStart, ViewRot);
}
const FVector ViewDir = ViewRot.Vector();
FVector ViewEnd = ViewStart + (ViewDir * MaxRange);
ClipCameraRayToAbilityRange(ViewStart, ViewDir, TraceStart, MaxRange, ViewEnd);
// Use first hit
TArray<FHitResult> HitResults;
LineTraceWithFilter(HitResults, InSourceActor->GetWorld(), Filter, ViewStart, ViewEnd, TraceProfile.Name, Params);
CurrentTargetingSpread = FMath::Min(TargetingSpreadMax, CurrentTargetingSpread + TargetingSpreadIncrement);
const bool bUseTraceResult = HitResults.Num() > 0 && (FVector::DistSquared(TraceStart, HitResults[0].Location) <= (
MaxRange * MaxRange));
const FVector AdjustedEnd = (bUseTraceResult) ? HitResults[0].Location : ViewEnd;
FVector AdjustedAimDir = (AdjustedEnd - TraceStart).GetSafeNormal();
if (AdjustedAimDir.IsZero())
{
AdjustedAimDir = ViewDir;
}
if (!bTraceAffectsAimPitch && bUseTraceResult)
{
FVector OriginalAimDir = (ViewEnd - TraceStart).GetSafeNormal();
if (!OriginalAimDir.IsZero())
{
// Convert to angles and use original pitch
const FRotator OriginalAimRot = OriginalAimDir.Rotation();
FRotator AdjustedAimRot = AdjustedAimDir.Rotation();
AdjustedAimRot.Pitch = OriginalAimRot.Pitch;
AdjustedAimDir = AdjustedAimRot.Vector();
}
}
const float CurrentSpread = GetCurrentSpread();
const float ConeHalfAngle = FMath::DegreesToRadians(CurrentSpread * 0.5f);
const int32 RandomSeed = FMath::Rand();
FRandomStream WeaponRandomStream(RandomSeed);
const FVector ShootDir = WeaponRandomStream.VRandCone(AdjustedAimDir, ConeHalfAngle, ConeHalfAngle);
OutTraceEnd = TraceStart + (ShootDir * MaxRange);
}
bool AGGA_AbilityTargetActor_Trace::ClipCameraRayToAbilityRange(FVector CameraLocation, FVector CameraDirection, FVector AbilityCenter,
float AbilityRange, FVector& ClippedPosition)
{
FVector CameraToCenter = AbilityCenter - CameraLocation;
float DotToCenter = FVector::DotProduct(CameraToCenter, CameraDirection);
if (DotToCenter >= 0)
//If this fails, we're pointed away from the center, but we might be inside the sphere and able to find a good exit point.
{
float DistanceSquared = CameraToCenter.SizeSquared() - (DotToCenter * DotToCenter);
float RadiusSquared = (AbilityRange * AbilityRange);
if (DistanceSquared <= RadiusSquared)
{
float DistanceFromCamera = FMath::Sqrt(RadiusSquared - DistanceSquared);
float DistanceAlongRay = DotToCenter + DistanceFromCamera;
//Subtracting instead of adding will get the other intersection point
ClippedPosition = CameraLocation + (DistanceAlongRay * CameraDirection);
//Cam aim point clipped to range sphere
return true;
}
}
return false;
}
void AGGA_AbilityTargetActor_Trace::StopTargeting()
{
SetActorTickEnabled(false);
DestroyReticleActors();
// Clear added callbacks
TargetDataReadyDelegate.Clear();
CanceledDelegate.Clear();
if (GenericDelegateBoundASC)
{
GenericDelegateBoundASC->GenericLocalConfirmCallbacks.RemoveDynamic(
this, &AGameplayAbilityTargetActor::ConfirmTargeting);
GenericDelegateBoundASC->GenericLocalCancelCallbacks.RemoveDynamic(
this, &AGameplayAbilityTargetActor::CancelTargeting);
GenericDelegateBoundASC = nullptr;
}
}
FGameplayAbilityTargetDataHandle AGGA_AbilityTargetActor_Trace::MakeTargetData(const TArray<FHitResult>& HitResults) const
{
FGameplayAbilityTargetDataHandle ReturnDataHandle;
for (int32 i = 0; i < HitResults.Num(); i++)
{
/** Note: These are cleaned up by the FGameplayAbilityTargetDataHandle (via an internal TSharedPtr) */
FGameplayAbilityTargetData_SingleTargetHit* ReturnData = new FGameplayAbilityTargetData_SingleTargetHit();
ReturnData->HitResult = HitResults[i];
ReturnDataHandle.Add(ReturnData);
}
return ReturnDataHandle;
}
TArray<FHitResult> AGGA_AbilityTargetActor_Trace::PerformTrace(AActor* InSourceActor)
{
bool bTraceComplex = false;
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(InSourceActor);
FCollisionQueryParams Params(SCENE_QUERY_STAT(AGSGATA_LineTrace), bTraceComplex);
Params.bReturnPhysicalMaterial = true;
Params.AddIgnoredActors(ActorsToIgnore);
Params.bIgnoreBlocks = bIgnoreBlockingHits;
FVector TraceStart = StartLocation.GetTargetingTransform().GetLocation();
FVector TraceEnd;
if (PrimaryPC)
{
FVector ViewStart;
FRotator ViewRot;
PrimaryPC->GetPlayerViewPoint(ViewStart, ViewRot);
TraceStart = bTraceFromPlayerViewPoint ? ViewStart : TraceStart;
}
if (bUsePersistentHitResults)
{
// Clear any blocking hit results, invalid Actors, or actors out of range
//TODO Check for visibility if we add AIPerceptionComponent in the future
for (int32 i = PersistentHitResults.Num() - 1; i >= 0; i--)
{
FHitResult& HitResult = PersistentHitResults[i];
AActor* HitActor = HitResult.GetActor();
if (HitResult.bBlockingHit || !HitActor || FVector::DistSquared(
TraceStart, HitActor->GetActorLocation()) > (MaxRange * MaxRange))
{
PersistentHitResults.RemoveAt(i);
}
}
}
TArray<FHitResult> ReturnHitResults;
for (int32 TraceIndex = 0; TraceIndex < NumberOfTraces; TraceIndex++)
{
AimWithPlayerController(InSourceActor, Params, TraceStart, TraceEnd);
//Effective on server and launching client only
// ------------------------------------------------------
SetActorLocationAndRotation(TraceEnd, SourceActor->GetActorRotation());
CurrentTraceEnd = TraceEnd;
TArray<FHitResult> TraceHitResults;
DoTrace(TraceHitResults, InSourceActor->GetWorld(), Filter, TraceStart, TraceEnd, TraceProfile.Name, Params);
for (int32 j = TraceHitResults.Num() - 1; j >= 0; j--)
{
if (MaxHitResultsPerTrace >= 0 && j + 1 > MaxHitResultsPerTrace)
{
// Trim to MaxHitResultsPerTrace
TraceHitResults.RemoveAt(j);
continue;
}
FHitResult& HitResult = TraceHitResults[j];
// Reminder: if bUsePersistentHitResults, Number of Traces = 1
if (bUsePersistentHitResults)
{
AActor* HitActor = HitResult.GetActor();
// This is looping backwards so that further objects from player are added first to the queue.
// This results in closer actors taking precedence as the further actors will get bumped out of the TArray.
if (HitActor && (!HitResult.bBlockingHit || PersistentHitResults.Num() < 1))
{
bool bActorAlreadyInPersistentHits = false;
// Make sure PersistentHitResults doesn't have this hit actor already
for (int32 k = 0; k < PersistentHitResults.Num(); k++)
{
FHitResult& PersistentHitResult = PersistentHitResults[k];
AActor* PersistentHitActor = PersistentHitResult.GetActor();
if (PersistentHitActor == HitActor)
{
bActorAlreadyInPersistentHits = true;
break;
}
}
if (bActorAlreadyInPersistentHits)
{
continue;
}
if (PersistentHitResults.Num() >= MaxHitResultsPerTrace)
{
// Treat PersistentHitResults like a queue, remove first element
PersistentHitResults.RemoveAt(0);
}
PersistentHitResults.Add(HitResult);
}
}
else
{
// ReticleActors for PersistentHitResults are handled later
int32 ReticleIndex = TraceIndex * MaxHitResultsPerTrace + j;
if (ReticleIndex < ReticleActors.Num())
{
if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActors[ReticleIndex].Get())
{
AActor* HitActor = HitResult.GetActor();
const bool bHitActor = HitActor != nullptr;
if (bHitActor && !HitResult.bBlockingHit)
{
LocalReticleActor->SetActorHiddenInGame(false);
const FVector ReticleLocation = (bHitActor && LocalReticleActor->bSnapToTargetedActor)
? HitActor->GetActorLocation()
: HitResult.Location;
LocalReticleActor->SetActorLocation(ReticleLocation);
LocalReticleActor->SetIsTargetAnActor(bHitActor);
}
else
{
LocalReticleActor->SetActorHiddenInGame(true);
}
}
}
}
} // for TraceHitResults
if (!bUsePersistentHitResults)
{
if (TraceHitResults.Num() < ReticleActors.Num())
{
// We have less hit results than ReticleActors, hide the extra ones
for (int32 j = TraceHitResults.Num(); j < ReticleActors.Num(); j++)
{
if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActors[j].Get())
{
LocalReticleActor->SetIsTargetAnActor(false);
LocalReticleActor->SetActorHiddenInGame(true);
}
}
}
}
if (TraceHitResults.Num() < 1)
{
// If there were no hits, add a default HitResult at the end of the trace
FHitResult HitResult;
// Start param could be player ViewPoint. We want HitResult to always display the StartLocation.
HitResult.TraceStart = StartLocation.GetTargetingTransform().GetLocation();
HitResult.TraceEnd = TraceEnd;
HitResult.Location = TraceEnd;
HitResult.ImpactPoint = TraceEnd;
TraceHitResults.Add(HitResult);
if (bUsePersistentHitResults && PersistentHitResults.Num() < 1)
{
PersistentHitResults.Add(HitResult);
}
}
ReturnHitResults.Append(TraceHitResults);
} // for NumberOfTraces
// Reminder: if bUsePersistentHitResults, Number of Traces = 1
if (bUsePersistentHitResults && MaxHitResultsPerTrace > 0)
{
// Handle ReticleActors
for (int32 PersistentHitResultIndex = 0; PersistentHitResultIndex < PersistentHitResults.Num();
PersistentHitResultIndex++)
{
FHitResult& HitResult = PersistentHitResults[PersistentHitResultIndex];
AActor* HitActor = HitResult.GetActor();
// Update TraceStart because old persistent HitResults will have their original TraceStart and the player could have moved since then
HitResult.TraceStart = StartLocation.GetTargetingTransform().GetLocation();
if (!ReticleActors.IsValidIndex(PersistentHitResultIndex))
{
continue;
}
if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActors[PersistentHitResultIndex].Get())
{
const bool bHitActor = HitActor != nullptr;
if (bHitActor && !HitResult.bBlockingHit)
{
LocalReticleActor->SetActorHiddenInGame(false);
const FVector ReticleLocation = (bHitActor && LocalReticleActor->bSnapToTargetedActor)
? HitActor->GetActorLocation()
: HitResult.Location;
LocalReticleActor->SetActorLocation(ReticleLocation);
LocalReticleActor->SetIsTargetAnActor(bHitActor);
}
else
{
LocalReticleActor->SetActorHiddenInGame(true);
}
}
}
if (PersistentHitResults.Num() < ReticleActors.Num())
{
// We have less hit results than ReticleActors, hide the extra ones
for (int32 PersistentHitResultIndex = PersistentHitResults.Num(); PersistentHitResultIndex < ReticleActors.
Num(); PersistentHitResultIndex++)
{
if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActors[PersistentHitResultIndex].Get())
{
LocalReticleActor->SetIsTargetAnActor(false);
LocalReticleActor->SetActorHiddenInGame(true);
}
}
}
CurrentHitResults = PersistentHitResults;
return PersistentHitResults;
}
CurrentHitResults = ReturnHitResults;
return ReturnHitResults;
}
AGameplayAbilityWorldReticle* AGGA_AbilityTargetActor_Trace::SpawnReticleActor(FVector Location, FRotator Rotation)
{
if (ReticleClass)
{
AGameplayAbilityWorldReticle* SpawnedReticleActor = GetWorld()->SpawnActor<AGameplayAbilityWorldReticle>(
ReticleClass, Location, Rotation);
if (SpawnedReticleActor)
{
SpawnedReticleActor->InitializeReticle(this, PrimaryPC, ReticleParams);
SpawnedReticleActor->SetActorHiddenInGame(true);
ReticleActors.Add(SpawnedReticleActor);
// This is to catch cases of playing on a listen server where we are using a replicated reticle actor.
// (In a client controlled player, this would only run on the client and therefor never replicate. If it runs
// on a listen server, the reticle actor may replicate. We want consistancy between client/listen server players.
// Just saying 'make the reticle actor non replicated' isnt a good answer, since we want to mix and match reticle
// actors and there may be other targeting types that want to replicate the same reticle actor class).
if (!ShouldProduceTargetDataOnServer)
{
SpawnedReticleActor->SetReplicates(false);
}
return SpawnedReticleActor;
}
}
return nullptr;
}
void AGGA_AbilityTargetActor_Trace::DestroyReticleActors()
{
for (int32 i = ReticleActors.Num() - 1; i >= 0; i--)
{
if (ReticleActors[i].IsValid())
{
ReticleActors[i].Get()->Destroy();
}
}
ReticleActors.Empty();
}
void AGGA_AbilityTargetActor_Trace::GetCurrentHitResult(TArray<FHitResult>& HitResults)
{
HitResults = CurrentHitResults;
}