第一次提交
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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永远不会发生,因为我们一次性Start,Confirm,然后马上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;
|
||||
}
|
||||
Reference in New Issue
Block a user