第一次提交

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,93 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
#include "GCS_LogChannels.h"
#include "Collision/GCS_TraceSystemComponent.h"
#include "Components/PrimitiveComponent.h"
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceBeginPlay_Implementation()
{
ActiveTime = 0.0f;
HitActors.Empty();
}
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceEndPlay_Implementation()
{
ActiveTime = 0.0f;
bTraceActive = false;
TracePrimitiveComponent = nullptr;
TracePrimitiveComponentSocketNames.Empty();
HitActors.Empty();
}
void UDEPRECATED_GCS_CollisionTraceInstance::BroadcastHit(const FHitResult& HitResult)
{
if (!bTraceActive)
{
UE_LOG(LogGCS_Collision, Warning, TEXT("Hit while inactive,%s"), *TraceOwner->GetName());
}
}
void UDEPRECATED_GCS_CollisionTraceInstance::BroadcastStateChanged(bool bNewState)
{
OnTraceStateChanged(bNewState);
}
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceTick_Implementation(float DeltaSeconds)
{
ActiveTime += DeltaSeconds;
}
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceStateChanged_Implementation(bool bNewState)
{
bTraceActive = bNewState;
HitActors.Empty();
}
void UDEPRECATED_GCS_CollisionTraceInstance::SetTraceMeshInfo(UPrimitiveComponent* NewPrimitiveComponent, TArray<FName> PrimitiveComponentSocketNames)
{
TracePrimitiveComponent = NewPrimitiveComponent;
TracePrimitiveComponentSocketNames = PrimitiveComponentSocketNames;
}
bool UDEPRECATED_GCS_CollisionTraceInstance::CanHitActor_Implementation(const AActor* ActorToCheck) const
{
//in active trace can not hit anything. TODO make it checkf?
if (!bTraceActive)
{
return false;
}
return ActorToCheck != GetTraceSourceActor() && ActorToCheck != TraceOwner && !HitActors.Contains(ActorToCheck);
}
AActor* UDEPRECATED_GCS_CollisionTraceInstance::GetTraceSourceActor() const
{
return TracePrimitiveComponent->GetOwner();
}
void UDEPRECATED_GCS_CollisionTraceInstance::ToggleTraceState(bool bNewState)
{
if (TraceOwner && bTraceActive != bNewState)
{
BroadcastStateChanged(bNewState);
}
}
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceHit_Implementation(const FHitResult& HitResult)
{
if (HitResult.GetHitObjectHandle().IsValid())
{
if (CanHitActor(HitResult.GetActor()))
{
UE_LOG(LogGCS_Collision, VeryVerbose, TEXT("%s's Trace(%s, SourceActor:%s) hit actor(%s,Comp:%s)"), *TraceOwner->GetName(),
*(TraceGameplayTag.IsValid()?TraceGameplayTag.ToString():GetClass()->GetName()),
*GetTraceSourceActor()->GetName(),
*HitResult.GetActor()->GetName(), *(HitResult.Component.IsValid()?HitResult.GetComponent()->GetName():TEXT("Null")));
HitActors.Add(HitResult.GetActor());
BroadcastHit(HitResult);
}
}
}

View File

@@ -0,0 +1,105 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Collision/GCS_AsyncAction_CollisionTrace.h"
#include "Collision/GCS_TraceSystemComponent.h"
#include "Components/PrimitiveComponent.h"
#include "Engine/Engine.h"
UGCS_AsyncAction_CollisionTrace* UGCS_AsyncAction_CollisionTrace::SetupAndListenForCollisionTraceHit(UGCS_TraceSystemComponent* TraceSystem,
const TArray<FGCS_TraceDefinition>& TraceDefinitions,
UPrimitiveComponent* PrimitiveComponent,
UObject* OptionalSourceObject)
{
if (TraceSystem == nullptr)
{
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed a null TraceSystem"), ELogVerbosity::Error);
return nullptr;
}
if (PrimitiveComponent == nullptr)
{
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed a null PrimitiveComponent"), ELogVerbosity::Error);
return nullptr;
}
if (TraceDefinitions.IsEmpty())
{
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed empty TraceDefinitions"), ELogVerbosity::Error);
return nullptr;
}
UWorld* World = GEngine->GetWorldFromContextObject(TraceSystem, EGetWorldErrorMode::LogAndReturnNull);
if (!World)
{
return nullptr;
}
UGCS_AsyncAction_CollisionTrace* Action = NewObject<UGCS_AsyncAction_CollisionTrace>();
Action->TraceDefinitions = TraceDefinitions;
Action->TraceSystem = TraceSystem;
Action->SourceComponent = PrimitiveComponent;
Action->SourceObject = OptionalSourceObject;
Action->RegisterWithGameInstance(World);
return Action;
}
void UGCS_AsyncAction_CollisionTrace::Activate()
{
UGCS_TraceSystemComponent* TSC = TraceSystem.Get();
UPrimitiveComponent* Primitive = SourceComponent.Get();
UObject* SourceObj = SourceObject.Get();
if (TSC && Primitive)
{
TSC->OnTraceHitEvent.AddDynamic(this, &ThisClass::TraceHitCallback);
static FHitResult EmptyHitResult;
TraceHandles = TSC->AddTraces(TraceDefinitions, Primitive, SourceObj);
for (auto& Handle : TraceHandles)
{
BeforeActive.Broadcast(Handle, EmptyHitResult);
TSC->StartTrace(Handle);
}
}
else
{
SetReadyToDestroy();
}
}
void UGCS_AsyncAction_CollisionTrace::Cancel()
{
Super::Cancel();
UGCS_TraceSystemComponent* TSC = TraceSystem.Get();
UPrimitiveComponent* Primitive = SourceComponent.Get();
if (TSC && Primitive)
{
TSC->OnTraceHitEvent.RemoveAll(this);
//Deactivate traces.
for (const FGCS_TraceHandle& Handle : TraceHandles)
{
if (Handle.IsValidHandle())
{
TSC->RemoveTrace(Handle);
}
}
TraceHandles.Empty();
SourceComponent = nullptr;
TraceSystem = nullptr;
SourceObject = nullptr;
}
}
void UGCS_AsyncAction_CollisionTrace::TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
{
if (ShouldBroadcastDelegates() && TraceHandle.IsValidHandle())
{
if (TraceHandles.Contains(TraceHandle))
{
OnHit.Broadcast(TraceHandle, HitResult);
}
}
}

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Collision/GCS_TraceDelegates.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GCS_TraceDelegates)

View File

@@ -0,0 +1,4 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Collision/GCS_TraceEnumLibrary.h"

View File

@@ -0,0 +1,13 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Collision/GCS_TraceFunctionLibrary.h"
TArray<FGCS_TraceDefinition> UGCS_TraceFunctionLibrary::FilterTraceDefinitionsByTag(const TArray<FGCS_TraceDefinition>& Definitions, const FGameplayTag& TagToMatch)
{
return Definitions.FilterByPredicate([TagToMatch](const FGCS_TraceDefinition& Definition)
{
return Definition.TraceTag.IsValid() && Definition.CollisionShape.IsValid() && Definition.TraceTag.MatchesTag(TagToMatch);
});
}

View File

@@ -0,0 +1,249 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Collision/GCS_TraceStructLibrary.h"
#include "GCS_LogChannels.h"
#include "Components/BoxComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SphereComponent.h"
#include "TargetingSystem/TargetingPreset.h"
bool FGCS_CollisionShape::InitializeShape(const UPrimitiveComponent* SourceComponent)
{
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
return false;
}
FTransform FGCS_CollisionShape::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
return SourceComponent->GetComponentTransform();
}
FCollisionShape FGCS_CollisionShape::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
return FCollisionShape::MakeSphere(30);
}
bool FGCS_CollisionShape_Static::InitializeShape(const UPrimitiveComponent* SourceComponent)
{
return true;
}
FTransform FGCS_CollisionShape_Static::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
}
FCollisionShape FGCS_CollisionShape_Static::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
switch (ShapeType)
{
case EGCS_CollisionShapeType::Sphere:
{
return FCollisionShape::MakeSphere(Radius);
}
case EGCS_CollisionShapeType::Box:
{
return FCollisionShape::MakeBox(HalfSize);
}
case EGCS_CollisionShapeType::Capsule:
{
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
}
default:
{
return FCollisionShape::MakeSphere(Radius);
}
}
}
bool FGCS_CollisionShape_ShapeBased::InitializeShape(const UPrimitiveComponent* SourceComponent)
{
if (ShapeType == EGCS_CollisionShapeType::Sphere)
{
if (const auto SphereCollision = Cast<USphereComponent>(SourceComponent))
{
Radius = SphereCollision->GetScaledSphereRadius();
return true;
}
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires SphereComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
*SourceComponent->GetClass()->GetName())
}
if (ShapeType == EGCS_CollisionShapeType::Box)
{
if (const auto BoxCollision = Cast<UBoxComponent>(SourceComponent))
{
HalfSize = BoxCollision->GetScaledBoxExtent();
return true;
}
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires BoxComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
*SourceComponent->GetClass()->GetName())
}
if (ShapeType == EGCS_CollisionShapeType::Capsule)
{
if (const auto CapsuleCollision = Cast<UCapsuleComponent>(SourceComponent))
{
HalfHeight = CapsuleCollision->GetScaledCapsuleHalfHeight();
Radius = CapsuleCollision->GetScaledCapsuleRadius();
return true;
}
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires CapsuleComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
*SourceComponent->GetClass()->GetName())
}
return false;
}
FTransform FGCS_CollisionShape_ShapeBased::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
}
FCollisionShape FGCS_CollisionShape_ShapeBased::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
switch (ShapeType)
{
case EGCS_CollisionShapeType::Sphere:
{
return FCollisionShape::MakeSphere(Radius);
}
case EGCS_CollisionShapeType::Box:
{
return FCollisionShape::MakeBox(HalfSize);
}
case EGCS_CollisionShapeType::Capsule:
{
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
}
default:
{
return FCollisionShape::MakeSphere(Radius);
}
}
}
bool FGCS_CollisionShape_Attached::InitializeShape(const UPrimitiveComponent* SourceComponent)
{
if (!IsValid(SourceComponent) || !SourceComponent->DoesSocketExist(SocketOrBoneName))
{
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No SocketOrBone(%s) exists on mesh component! Got %s(%s)", *SocketOrBoneName.ToString(), *GetNameSafe(SourceComponent),
*SourceComponent->GetClass()->GetName())
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
return false;
}
return true;
}
FTransform FGCS_CollisionShape_Attached::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
const auto BoneTransform = SourceComponent->GetSocketTransform(SocketOrBoneName);
return FTransform(Orientation, Offset) * BoneTransform;
}
FTransform FGCS_CollisionShape_SocketBased::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
}
bool FGCS_CollisionShape_SocketBased::InitializeShape(const UPrimitiveComponent* SourceComponent)
{
if (const UMeshComponent* MeshComponent = Cast<UMeshComponent>(SourceComponent))
{
const auto SocketStartTransform = MeshComponent->GetSocketTransform(MeshSocketStart, RTS_Component);
const auto SocketStartLocation = SocketStartTransform.GetLocation();
const auto SocketEndTransform = MeshComponent->GetSocketTransform(MeshSocketEnd, RTS_Component);
const auto SocketEndLocation = SocketEndTransform.GetLocation();
const FVector CenterLocation = (SocketStartLocation + SocketEndLocation) / 2.f;
HalfHeight = FMath::Max((SocketStartLocation - SocketEndLocation).Length() / 2.f + MeshSocketLengthOffset, 1.f);
Offset = CenterLocation;
Orientation = FRotationMatrix::MakeFromZ(SocketStartLocation - SocketEndLocation).Rotator();
return true;
}
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible mesh component was found! Got %s(%s)", *GetNameSafe(SourceComponent),
*SourceComponent->GetClass()->GetName())
return false;
}
FCollisionShape FGCS_CollisionShape_SocketBased::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
{
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
}
FGCS_TraceDefinition::FGCS_TraceDefinition()
{
CollisionShape.InitializeAs(FGCS_CollisionShape_Static::StaticStruct());
}
bool FGCS_TraceDefinition::IsValidDefinition() const
{
// Base collision shape is not allowed!
return TraceTag.IsValid() && CollisionShape.IsValid() && CollisionShape.GetScriptStruct() != FGCS_CollisionShape::StaticStruct();
}
FString FGCS_TraceDefinition::ToString() const
{
return FString::Format(TEXT("{0} {1}"), {TraceTag.ToString(), GetNameSafe(CollisionShape.GetScriptStruct())});
}
bool FGCS_TraceHandle::IsValidHandle() const
{
return TraceTag.IsValid() && Guid.IsValid() && SourceObject.IsValid();
}
void FGCS_TraceState::ChangeExecutionState(const bool bNewTraceState, const bool bStopImmediate)
{
if (bNewTraceState)
{
this->ExecutionState = EGCS_TraceExecutionState::InProgress;
this->TimeSinceLastTick = 0;
this->TimeSinceActive = 0;
this->TotalTickNumDuringExecution = 0;
this->HitActors.Reset();
if (SourceComponent)
{
UpdatePreviousTransform(GetCurrentTransform());
}
}
else
{
if (this->ExecutionState == EGCS_TraceExecutionState::InProgress && !bStopImmediate)
{
this->ExecutionState = EGCS_TraceExecutionState::PendingStop;
}
else
{
this->ExecutionState = EGCS_TraceExecutionState::Stopped;
}
}
}
FTransform FGCS_TraceState::GetCurrentTransform() const
{
check(Shape.IsValid())
return Shape.Get<FGCS_CollisionShape>().GetTransform(SourceComponent.Get(), TimeSinceActive);
}
void FGCS_TraceState::UpdatePreviousTransform(const FTransform& Transform)
{
if (TransformsOverTime.Num() == 0)
{
TransformsOverTime.Add(Transform);
}
else
{
TransformsOverTime[0] = Transform;
}
}

View File

@@ -0,0 +1,485 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Collision/GCS_TraceSubsystem.h"
#include "GCS_LogChannels.h"
#include "KismetTraceUtils.h"
#include "Async/ParallelFor.h"
#include "Components/StaticMeshComponent.h"
#include "Collision/GCS_TraceSystemComponent.h"
#include "Kismet/KismetMathLibrary.h"
#define LOCTEXT_NAMESPACE "UGCS_CollisionTraceSubsystem"
// CVars
namespace GenericCombatSystemCVars
{
#if ENABLE_DRAW_DEBUG
static bool bEnableTraceDebugging = false;
FAutoConsoleVariableRef CvarForceEnableTraceDebugging(
TEXT("gcs.debug.EnableTraceDebugging"),
bEnableTraceDebugging,
TEXT("Toggles whether enable draw debugs for all traces. (Enabled: true, Disabled: false)"));
static float OverrideTraceDebuggingLifeTime = 0.f;
FAutoConsoleVariableRef CvarOverrideTraceDebuggingLifeTime(
TEXT("gcs.debug.OverrideTraceDebuggingLifeTime"),
OverrideTraceDebuggingLifeTime,
TEXT("Overrides the draws life time to ease the trace debugging"));
#endif
}
void UGCS_TraceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
this->TraceStates.Reserve(4068);
}
void UGCS_TraceSubsystem::Tick(float DeltaTime)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::Tick"), STAT_UGCS_TraceSubsystem_Tick, STATGROUP_GCS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
TickIdx++;
// Lock removals so we don't get any modifications to the array while we are iterating
RemovalLock = true;
PreTraceTick(DeltaTime);
PrepareSubTicks(DeltaTime);
PerformSubTicks(DeltaTime);
PostTraceTick();
// Reverse iterate, remove pending removals
RemovalLock = false;
for (int Idx = TraceStates.Num() - 1; Idx >= 0; Idx--)
{
const auto& TraceState = TraceStates[Idx];
if (!TraceStates.IsValidIndex(Idx))
{
continue;
}
if (TraceState.IsPendingRemoval)
{
RemoveTraceStateAt(Idx, TraceState.Handle.Guid);
}
}
}
void UGCS_TraceSubsystem::RemoveTraceState(const int Idx, const FGuid Guid)
{
if (RemovalLock)
{
if (TraceStates.IsValidIndex(Idx) && TraceStates[Idx].Handle.Guid == Guid)
{
TraceStates[Idx].IsPendingRemoval = true;
}
}
else
{
RemoveTraceStateAt(Idx, Guid);
}
}
int32 UGCS_TraceSubsystem::AddTraceState()
{
FScopeLock ScopeLock(&CriticalSection);
return TraceStates.AddDefaulted();
}
bool UGCS_TraceSubsystem::IsValidStateIdx(int32 StateIdx) const
{
return TraceStates.IsValidIndex(StateIdx);
}
FGCS_TraceState& UGCS_TraceSubsystem::GetTraceStateAt(const int Index)
{
return TraceStates[Index];
}
void UGCS_TraceSubsystem::RemoveTraceStateAt(const int Idx, const FGuid Guid)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::RemoveTraceStateAt"), STAT_UGCS_TraceSubsystem_RemoveTraceStateAt, STATGROUP_GCS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
FScopeLock ScopeLock(&CriticalSection);
if (TraceStates.IsValidIndex(Idx) && TraceStates[Idx].Handle.Guid == Guid)
{
// 移除并交换
TraceStates.RemoveAtSwap(Idx);
// If a state was moved to Idx, update its corresponding handle in the OwningSystem
if (TraceStates.IsValidIndex(Idx))
{
FGCS_TraceState& MovedState = TraceStates[Idx];
if (IsValid(MovedState.OwningSystem))
{
// Update the Component's HandleToStateIdx map
MovedState.OwningSystem->HandleToStateIdx.Remove(MovedState.Handle); // Remove old mapping
MovedState.OwningSystem->HandleToStateIdx.Add(MovedState.Handle, Idx); // Add new mapping
}
// mark pending removal if owning system become invalid.
else
{
MovedState.IsPendingRemoval = true;
}
}
}
}
void UGCS_TraceSubsystem::PreTraceTick(const float DeltaTime)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PreTraceTick"), STAT_UGCS_TraceSubsystem_PreTraceTick, STATGROUP_GCS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
{
if (!TraceStates.IsValidIndex(Idx))
{
return;
}
auto& TraceState = TraceStates[Idx];
if (TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
{
return;
}
check(TraceState.bShouldTickThisFrame == false)
if (!IsValid(TraceState.SourceComponent))
{
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
TraceState.bShouldTickThisFrame = false;
return;
}
const FTransform& CurrentTransform = TraceState.GetCurrentTransform();
if (TraceState.TransformsOverTime.IsEmpty())
{
TraceState.TransformsOverTime.Add(CurrentTransform);
}
if (TraceState.ExecutionState == EGCS_TraceExecutionState::PendingStop)
{
TraceState.bShouldTickThisFrame = true;
TraceState.TransformsOverTime.Add(CurrentTransform);
return;
}
switch (TraceState.TickPolicy)
{
case EGCS_TraceTickType::Default:
TraceState.TransformsOverTime.Add(CurrentTransform);
TraceState.bShouldTickThisFrame = true;
return;
case EGCS_TraceTickType::DistanceBased:
{
const FVector& PreviousLocation = TraceState.TransformsOverTime[0].GetLocation();
const FVector& CurrentLocation = CurrentTransform.GetLocation();
const FRotator& PreviousRotation = TraceState.TransformsOverTime[0].GetRotation().Rotator();
const FRotator& CurrentRotation = CurrentTransform.GetRotation().Rotator();
bool bDistanceThresholdMet = (PreviousLocation - CurrentLocation).Length() >= TraceState.TickInterval;
bool bAngleThresholdMet = FMath::Abs(PreviousRotation.Yaw - CurrentRotation.Yaw) >= TraceState.AngleThreshold ||
FMath::Abs(PreviousRotation.Pitch - CurrentRotation.Pitch) >= TraceState.AngleThreshold ||
FMath::Abs(PreviousRotation.Roll - CurrentRotation.Roll) >= TraceState.AngleThreshold;
if (bDistanceThresholdMet || bAngleThresholdMet)
{
TraceState.TransformsOverTime.Add(CurrentTransform);
TraceState.bShouldTickThisFrame = true;
}
return;
}
case EGCS_TraceTickType::FixedFrameRate:
TraceState.TimeSinceLastTick += DeltaTime;
int32 SubTickCountPreview = FMath::FloorToInt(TraceState.TimeSinceLastTick / TraceState.TickInterval);
if (SubTickCountPreview > 0)
{
TraceState.TransformsOverTime.Add(CurrentTransform);
TraceState.bShouldTickThisFrame = true;
}
}
});
}
void UGCS_TraceSubsystem::PostTraceTick()
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PostTraceTick"), STAT_UGCS_TraceSubsystem_PostTraceTick, STATGROUP_GCS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
{
auto& TraceState = TraceStates[Idx];
if (!TraceState.bShouldTickThisFrame)
{
return;
}
// Reset time since last tick.
if (TraceState.TickPolicy == EGCS_TraceTickType::FixedFrameRate)
{
int32 SubTickCount = TraceState.SubTicks.Num();
TraceState.TimeSinceLastTick -= SubTickCount * TraceState.TickInterval;
TraceState.TimeSinceLastTick = FMath::Max(TraceState.TimeSinceLastTick, 0.0f);
}
else
{
TraceState.TimeSinceLastTick = 0;
}
TraceState.bShouldTickThisFrame = false;
if (TraceState.ExecutionState == EGCS_TraceExecutionState::PendingStop)
{
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
TraceState.TransformsOverTime.Empty();
TraceState.TimeSinceActive = 0;
}
else
{
if (!TraceState.TransformsOverTime.IsEmpty())
{
TraceState.TransformsOverTime.RemoveAtSwap(0);
}
}
});
}
void UGCS_TraceSubsystem::PrepareSubTicks(const float DeltaTime)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PrepareSubTicks"), STAT_UGCS_TraceSubsystem_PrepareSubTicks, STATGROUP_GCS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
{
if (!TraceStates.IsValidIndex(Idx))
{
return;
}
auto& TraceState = TraceStates[Idx];
TraceState.SubTicks.Reset();
if (TraceState.bShouldTickThisFrame)
{
int32 SubTickCount = 1;
if (TraceState.TickPolicy == EGCS_TraceTickType::DistanceBased)
{
SubTickCount = FMath::CeilToInt((TraceState.TransformsOverTime[0].GetLocation() - TraceState.TransformsOverTime[1].GetLocation()).Length() / TraceState.TickInterval);
}
else if (TraceState.TickPolicy == EGCS_TraceTickType::FixedFrameRate)
{
SubTickCount = FMath::FloorToInt(TraceState.TimeSinceLastTick / TraceState.TickInterval);
}
SubTickCount = FMath::Min(10, SubTickCount);
if (TraceState.TransformsOverTime.Num() > 1)
{
const float SubTickRatio = 1.0 / SubTickCount;
const FTransform CurrentTransform = TraceState.TransformsOverTime.Last();
const FTransform PreviousTransform = TraceState.TransformsOverTime[0];
TraceState.CollisionShapeOverTime = TraceState.Shape.Get<FGCS_CollisionShape>().GetDynamicCollisionShape(TraceState.SourceComponent, TraceState.TimeSinceActive);
for (int32 i = 0; i < SubTickCount; i++)
{
FGCS_TraceSubTick SubTick{};
SubTick.StartTransform = UKismetMathLibrary::TLerp(PreviousTransform, CurrentTransform, SubTickRatio * i, ELerpInterpolationMode::DualQuatInterp);
SubTick.EndTransform = UKismetMathLibrary::TLerp(PreviousTransform, CurrentTransform, SubTickRatio * (i + 1), ELerpInterpolationMode::DualQuatInterp);
SubTick.AverageTransform = UKismetMathLibrary::TLerp(SubTick.StartTransform, SubTick.EndTransform, 0.5, ELerpInterpolationMode::DualQuatInterp);
TraceState.SubTicks.Add(SubTick);
}
}
else
{
GCS_LOG(Warning, "Trace [%s] has less than 2 transforms in TransformsOverTime array!", *TraceState.Handle.TraceTag.ToString())
}
}
if (TraceState.ExecutionState == EGCS_TraceExecutionState::InProgress)
{
TraceState.TimeSinceActive += DeltaTime;
}
});
}
void UGCS_TraceSubsystem::PerformSubTicks(const float DeltaTime)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PerformSubTicks"), STAT_UGCS_TraceSubsystem_PerformSubTicks, STATGROUP_GCS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
for (int32 Idx = 0; Idx < TraceStates.Num(); Idx++)
{
if (!TraceStates.IsValidIndex(Idx))
{
continue;
}
auto& TraceState = TraceStates[Idx];
if (TraceState.bShouldTickThisFrame)
{
for (int32 SubTickIdx = 0; SubTickIdx < TraceState.SubTicks.Num(); SubTickIdx++)
{
const FGCS_TraceSubTick& SubTick = TraceState.SubTicks[SubTickIdx];
// Respect cancellations by user-defined code immediately.
if (TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
{
break;
}
TraceState.TotalTickNumDuringExecution += 1;
FTraceDelegate Delegate = FTraceDelegate::CreateUObject(this, &UGCS_TraceSubsystem::HandleTraceResults, Idx, TickIdx, TraceState.TimeSinceActive);
PerformAsyncTrace(SubTick.StartTransform, SubTick.EndTransform, SubTick.AverageTransform, TraceState.World, TraceState.SweepSetting,
TraceState.CollisionShapeOverTime,
TraceState.CollisionParams,
TraceState.ResponseParams,
TraceState.ObjectQueryParams, &Delegate);
}
}
}
}
void UGCS_TraceSubsystem::HandleTraceResults(const FTraceHandle& InTraceHandle, FTraceDatum& InTraceDatum, int32 InTraceStateIdx, uint32 InTickIdx, float InShapeTime)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::HandleTraceResults"), STAT_UGCS_TraceSubsystem_HandleTraceResults, STATGROUP_GCS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
if (!TraceStates.IsValidIndex(InTraceStateIdx))
{
return;
}
auto& TraceState = TraceStates[InTraceStateIdx];
if (!IsValid(TraceState.OwningSystem) || TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
{
TraceState.TimeSinceActive = 0;
return;
}
// GCS_LOG(Warning, "Trace [%s] has ticked %d within %f s", *TraceState.Handle.TraceTag.ToString(), TraceState.TotalTickNumDuringExecution, TraceState.TimeSinceActive)
#if ENABLE_DRAW_DEBUG
if (GenericCombatSystemCVars::bEnableTraceDebugging)
{
const EDrawDebugTrace::Type DrawDebugType = GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime > 0 ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::ForOneFrame;
const float DrawDebugTime = GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime > 0
? GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime
: (DrawDebugType == EDrawDebugTrace::ForOneFrame ? 0.0f : 0.5f);
const bool bHasAuthority = TraceState.SourceComponent->GetOwner()->HasAuthority();
DrawDebug(InTraceDatum.Start,
InTraceDatum.End,
InTraceDatum.Rot,
InTraceDatum.OutHits, TraceState.Shape.Get<FGCS_CollisionShape>().GetDynamicCollisionShape(TraceState.SourceComponent, InShapeTime), TraceState.World,
DrawDebugType,
DrawDebugTime,
bHasAuthority ? FLinearColor::Red : FLinearColor::White,
bHasAuthority ? FLinearColor::Green : FLinearColor::Black);
// GCS_OWNED_CLOG(TraceState.SourceComponent, Display, "Did collision trace: start(%s) end(%s)", *InTraceDatum.Start.ToCompactString(), *InTraceDatum.End.ToCompactString())
}
#endif // ENABLE_DRAW_DEBUG
TArray<FHitResult> FilteredHits;
for (const FHitResult& NewHit : InTraceDatum.OutHits)
{
if (!TraceState.HitActors.Contains(NewHit.GetActor()))
{
FilteredHits.Add(NewHit);
TraceState.HitActors.Add(NewHit.GetActor());
}
}
if (FilteredHits.Num() > 0)
{
TraceState.OwningSystem->OnTraceHitDetected(TraceState.Handle, FilteredHits, TraceState.TimeSinceLastTick, InTickIdx);
}
}
void UGCS_TraceSubsystem::PerformAsyncTrace(const FTransform& StartTransform, const FTransform& EndTransform, const FTransform& AverageTransform, UWorld* World,
const FGCS_TraceSweepSetting& TraceSettings, const FCollisionShape& CollisionShape, const FCollisionQueryParams& CollisionParams,
const FCollisionResponseParams& CollisionResponseParams,
const FCollisionObjectQueryParams& ObjectQueryParams, const FTraceDelegate* InDelegate)
{
switch (TraceSettings.SweepType)
{
case EGCS_TraceSweepType::ByChannel:
World->AsyncSweepByChannel(
EAsyncTraceType::Multi,
StartTransform.GetLocation(),
EndTransform.GetLocation(),
AverageTransform.GetRotation(),
TraceSettings.TraceChannel,
CollisionShape,
CollisionParams,
CollisionResponseParams, InDelegate);
break;
case EGCS_TraceSweepType::ByObject:
World->AsyncSweepByObjectType(
EAsyncTraceType::Multi,
StartTransform.GetLocation(),
EndTransform.GetLocation(),
AverageTransform.GetRotation(),
ObjectQueryParams,
CollisionShape,
CollisionParams, InDelegate);
break;
case EGCS_TraceSweepType::ByProfile:
World->AsyncSweepByProfile(
EAsyncTraceType::Multi,
StartTransform.GetLocation(),
EndTransform.GetLocation(),
AverageTransform.GetRotation(),
TraceSettings.ProfileName,
CollisionShape,
CollisionParams, InDelegate);
break;
}
}
#if ENABLE_DRAW_DEBUG
void UGCS_TraceSubsystem::DrawDebug(const FVector& StartLocation, const FVector& EndLocation, const FQuat& Orientation, TArray<FHitResult> Hits, const FCollisionShape& CollisionShape,
const UWorld* World,
const EDrawDebugTrace::Type DrawDebugType, float DrawDebugTime, const FLinearColor& DrawDebugColor,
const FLinearColor& DrawDebugHitColor)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::DrawDebug"), STAT_UGCS_TraceSubsystem_DrawDebug, STATGROUP_GCS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
// We have to manually find if there is a blocking hit.
bool bHasBlockingHit = false;
for (const FHitResult& HitResult : Hits)
{
if (HitResult.bBlockingHit)
{
bHasBlockingHit = true;
break;
}
}
switch (CollisionShape.ShapeType)
{
case ECollisionShape::Sphere:
DrawDebugSphereTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetSphereRadius(), DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
break;
case ECollisionShape::Capsule:
DrawDebugCapsuleTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetCapsuleRadius(), CollisionShape.GetCapsuleHalfHeight(), Orientation.Rotator(), DrawDebugType,
bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
break;
case ECollisionShape::Box:
DrawDebugBoxTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetBox(), Orientation.Rotator(), DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor,
DrawDebugTime);
break;
default:
case ECollisionShape::Line:
DrawDebugLineTraceMulti(World, StartLocation, EndLocation, DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
break;
}
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,489 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Collision/GCS_TraceSystemComponent.h"
#include "GCS_LogChannels.h"
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
#include "Collision/GCS_TraceSubsystem.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/PrimitiveComponent.h"
#include "Utility/GCS_CombatFunctionLibrary.h"
UGCS_TraceSystemComponent::UGCS_TraceSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(false);
}
UGCS_TraceSystemComponent* UGCS_TraceSystemComponent::GetTraceSystemComponent(const AActor* Actor)
{
return IsValid(Actor) ? Actor->FindComponentByClass<UGCS_TraceSystemComponent>() : nullptr;
}
bool UGCS_TraceSystemComponent::FindTraceSystemComponent(const AActor* Actor, UGCS_TraceSystemComponent*& Component)
{
Component = GetTraceSystemComponent(Actor);
return Component != nullptr;
}
void UGCS_TraceSystemComponent::OnInitialize_Implementation()
{
if (UMeshComponent* PrimitiveComp = UGCS_CombatFunctionLibrary::GetMainMeshComponent(GetOwner()))
{
AddTraces(TraceDefinitions, PrimitiveComp, GetOwner());
}
bInitialized = true;
}
void UGCS_TraceSystemComponent::OnDeinitialize_Implementation()
{
if (bInitialized)
{
RemoveAllTraces();
bInitialized = false;
}
}
// Called when the game starts
void UGCS_TraceSystemComponent::BeginPlay()
{
if (bAutoInitialize)
{
OnInitialize();
}
Super::BeginPlay();
}
void UGCS_TraceSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
OnDeinitialize();
}
void UGCS_TraceSystemComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
{
Super::OnComponentDestroyed(bDestroyingHierarchy);
OnDestroyedEvent.Broadcast();
OnDeinitialize();
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTraces(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
{
TArray<FGCS_TraceHandle> Handles;
for (const FGCS_TraceDefinition& Def : Definitions)
{
FGCS_TraceHandle Handle = AddTrace(Def, SourceComponent, SourceObject);
if (Handle.IsValidHandle())
{
Handles.Add(Handle);
}
}
return Handles;
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTraces(const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
{
TArray<FGCS_TraceHandle> Handles;
for (const FDataTableRowHandle& DefHandle : DefinitionHandles)
{
FGCS_TraceHandle Handle = AddTrace(DefHandle, SourceComponent, SourceObject);
if (Handle.IsValidHandle())
{
Handles.Add(Handle);
}
}
return Handles;
}
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTrace(const FGCS_TraceDefinition& TraceDefinition, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
{
if (!TraceDefinition.IsValidDefinition())
{
GCS_CLOG(Warning, "Try adding invalid trace definition!")
return FGCS_TraceHandle();
}
if (!IsValid(SourceObject))
{
GCS_CLOG(Warning, "Missing source object for trace definition:%s", *TraceDefinition.ToString())
return FGCS_TraceHandle();
}
if (!IsValid(SourceComponent))
{
GCS_CLOG(Warning, "Missing source component for trace definition:%s", *TraceDefinition.ToString())
return FGCS_TraceHandle();
}
FInstancedStruct Shape = TraceDefinition.CollisionShape;
bool bWasInitialized = Shape.GetMutable<FGCS_CollisionShape>().InitializeShape(SourceComponent);
if (!bWasInitialized)
{
return FGCS_TraceHandle();
}
UWorld* World = GetWorld();
if (!IsValid(World))
{
GCS_CLOG(Error, "Invalid world context!")
return FGCS_TraceHandle();
}
UGCS_TraceSubsystem* Subsystem = World->GetSubsystem<UGCS_TraceSubsystem>();
if (!IsValid(Subsystem))
{
GCS_CLOG(Error, "Failed to get TraceSubsystem!")
return FGCS_TraceHandle();
}
int32 StateIdx = Subsystem->AddTraceState();
FGCS_TraceHandle Handle = {TraceDefinition.TraceTag, FGuid::NewGuid(), SourceObject};
float TickInterval = 0;
float AngleThreshold = TraceDefinition.AngleTickThreshold;
if (TraceDefinition.TraceTickType == EGCS_TraceTickType::DistanceBased)
{
TickInterval = TraceDefinition.DistanceTickThreshold;
}
else if (TraceDefinition.TraceTickType == EGCS_TraceTickType::FixedFrameRate)
{
TickInterval = 1.0f / static_cast<float>(TraceDefinition.FixedTickFrameRate);
}
auto& TraceState = Subsystem->GetTraceStateAt(StateIdx);
TraceState.World = GetWorld();
TraceState.SourceComponent = SourceComponent;
TraceState.OwningSystem = this;
TraceState.SweepSetting = TraceDefinition.SweepSetting;
TraceState.Shape = Shape;
TraceState.Handle = Handle;
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
TraceState.TimeSinceLastTick = 0;
TraceState.TimeSinceActive = 0;
TraceState.TickPolicy = TraceDefinition.TraceTickType;
TraceState.TickInterval = TickInterval;
TraceState.AngleThreshold = AngleThreshold;
TraceState.bShouldTickThisFrame = false;
if (AActor* Owner = GetOwner())
{
TraceState.CollisionParams.AddIgnoredActor(Owner);
}
TraceState.CollisionParams.bTraceComplex = TraceDefinition.SweepSetting.bTraceComplex;
TraceState.CollisionParams.bReturnPhysicalMaterial = true;
for (const auto& ObjType : TraceDefinition.SweepSetting.ObjectTypes)
{
TraceState.ObjectQueryParams.AddObjectTypesToQuery(ObjType);
}
HandleToStateIdx.Add(Handle, StateIdx);
TagToHandles.Add(TraceDefinition.TraceTag, Handle);
GCS_CLOG(Verbose, "Added trace(%s) with source component:%s", *Handle.ToDebugString(), *GetNameSafe(SourceComponent))
return Handle;
}
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTrace(const FDataTableRowHandle& TraceDefinitionHandle, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
{
if (!TraceDefinitionHandle.IsNull())
{
if (FGCS_TraceDefinition* TraceDefinition = TraceDefinitionHandle.GetRow<FGCS_TraceDefinition>(TEXT("AddTrace")))
{
AddTrace(*TraceDefinition, SourceComponent, SourceObject);
}
else
{
GCS_CLOG(Warning, "definition handle doesn't point to valid TraceDefinition Table. %s", *TraceDefinitionHandle.ToDebugString())
}
}
GCS_CLOG(Warning, "Passed in invalid trace definition handle! %s", *TraceDefinitionHandle.ToDebugString())
return FGCS_TraceHandle();
}
void UGCS_TraceSystemComponent::RemoveTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
{
for (const FGCS_TraceHandle& TraceHandle : TraceHandles)
{
RemoveTrace(TraceHandle);
}
}
void UGCS_TraceSystemComponent::RemoveTrace(const FGCS_TraceHandle& TraceHandle)
{
if (TraceHandle.IsValidHandle())
{
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
{
UGCS_TraceSubsystem* Subsystem = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>();
if (IsValid(Subsystem) && Subsystem->IsValidStateIdx(*StateIdx))
{
auto& State = Subsystem->GetTraceStateAt(*StateIdx);
State.ChangeExecutionState(false);
OnTraceStateChanged(TraceHandle, false);
Subsystem->RemoveTraceState(*StateIdx, TraceHandle.Guid);
GCS_CLOG(Verbose, "Removed trace(%s)", *TraceHandle.ToDebugString())
}
HandleToStateIdx.Remove(TraceHandle);
TagToHandles.RemoveSingle(TraceHandle.TraceTag, TraceHandle);
}
}
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesBySource(const UObject* SourceObject) const
{
TArray<FGCS_TraceHandle> Handles;
for (const auto& Pair : HandleToStateIdx)
{
if (Pair.Key.SourceObject == SourceObject)
{
Handles.Add(Pair.Key);
}
}
return Handles;
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject) const
{
TArray<FGCS_TraceHandle> Handles;
for (const FGameplayTag& Tag : TraceTags)
{
TArray<FGCS_TraceHandle> TagHandles;
TagToHandles.MultiFind(Tag, TagHandles);
for (const FGCS_TraceHandle& Handle : TagHandles)
{
if (!IsValid(SourceObject) || Handle.SourceObject == SourceObject)
{
Handles.AddUnique(Handle);
}
}
}
return Handles;
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::StartTraces(const FGameplayTagContainer& TraceTags, const UObject* SourceObject)
{
TArray<FGCS_TraceHandle> Handles = GetTraceHandlesByTagsAndSource(TraceTags, SourceObject);
for (const FGCS_TraceHandle& Handle : Handles)
{
StartTrace(Handle);
}
return Handles;
}
void UGCS_TraceSystemComponent::StartTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
{
for (const FGCS_TraceHandle& Handle : TraceHandles)
{
StartTrace(Handle);
}
}
void UGCS_TraceSystemComponent::StartTrace(const FGCS_TraceHandle& TraceHandle)
{
if (TraceHandle.IsValidHandle())
{
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
{
auto& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
GCS_CLOG(Verbose, "Started trace(%s).", *TraceHandle.ToDebugString())
State.ChangeExecutionState(true);
OnTraceStateChanged(TraceHandle, true);
}
}
}
void UGCS_TraceSystemComponent::StopTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
{
UGCS_TraceSubsystem* Subsystem = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>();
for (const FGCS_TraceHandle& TraceHandle : TraceHandles)
{
if (TraceHandle.IsValidHandle())
{
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
{
auto& State = Subsystem->GetTraceStateAt(*StateIdx);
GCS_CLOG(Verbose, "Stopped trace(%s).", *TraceHandle.ToDebugString())
State.ChangeExecutionState(false);
OnTraceStateChanged(TraceHandle, false);
}
}
}
}
void UGCS_TraceSystemComponent::StopTrace(const FGCS_TraceHandle& TraceHandle)
{
if (TraceHandle.IsValidHandle())
{
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
{
auto& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
State.ChangeExecutionState(false);
OnTraceStateChanged(TraceHandle, false);
}
}
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTracesByDefinitions(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent,
UObject* SourceObject)
{
return AddTraces(Definitions, SourceComponent, SourceObject);
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTracesByDefinitionHandles(const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent,
UObject* SourceObject)
{
return AddTraces(DefinitionHandles, SourceComponent, SourceObject);
}
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTraceByDefinition(const FGCS_TraceDefinition& Definition, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
{
return AddTrace(Definition, SourceComponent, SourceObject);
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::StartTracesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject)
{
return StartTraces(TraceTags, SourceObject);
}
void UGCS_TraceSystemComponent::StartTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles)
{
return StartTraces(TraceHandles);
}
void UGCS_TraceSystemComponent::StartTraceByHandle(const FGCS_TraceHandle& TraceHandle)
{
return StartTrace(TraceHandle);
}
void UGCS_TraceSystemComponent::StopTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles)
{
StopTraces(TraceHandles);
}
void UGCS_TraceSystemComponent::StopTraceByHandle(const FGCS_TraceHandle& TraceHandle)
{
StopTrace(TraceHandle);
}
void UGCS_TraceSystemComponent::RemoveTraceByHandle(const FGCS_TraceHandle& TraceHandle)
{
RemoveTrace(TraceHandle);
}
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesByTag(FGameplayTag TraceToFind) const
{
TArray<FGCS_TraceHandle> Handles;
TagToHandles.MultiFind(TraceToFind, Handles);
return Handles;
}
AActor* UGCS_TraceSystemComponent::GetTraceSourceActor(const FGCS_TraceHandle& TraceHandle) const
{
if (UPrimitiveComponent* SourceComponent = GetTraceSourceComponent(TraceHandle))
{
return SourceComponent->GetOwner();
}
return nullptr;
}
UPrimitiveComponent* UGCS_TraceSystemComponent::GetTraceSourceComponent(const FGCS_TraceHandle& TraceHandle) const
{
if (TraceHandle.IsValidHandle())
{
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
{
FGCS_TraceState& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
return State.SourceComponent;
}
}
return nullptr;
}
void UGCS_TraceSystemComponent::RemoveAllTraces()
{
UWorld* World = GetWorld();
if (!IsValid(World))
{
return;
}
UGCS_TraceSubsystem* Subsystem = World->GetSubsystem<UGCS_TraceSubsystem>();
if (!IsValid(Subsystem))
{
return;
}
// 新增收集所有要移除的Idx和Guid避免边遍历边修改
TArray<TPair<int32, FGuid>> ToRemove;
for (const TPair<FGCS_TraceHandle, int32>& Pair : HandleToStateIdx)
{
if (Pair.Key.IsValidHandle())
{
if (Subsystem->IsValidStateIdx(Pair.Value)) // 额外验证
{
const FGCS_TraceState& State = Subsystem->GetTraceStateAt(Pair.Value);
ToRemove.Add(TPair<int32, FGuid>(Pair.Value, Pair.Key.Guid));
}
}
}
// 先停止所有状态
for (const auto& Pending : ToRemove)
{
if (Subsystem->IsValidStateIdx(Pending.Key))
{
auto& State = Subsystem->GetTraceStateAt(Pending.Key);
State.ChangeExecutionState(false);
}
}
// 然后批量移除
for (const auto& Pending : ToRemove)
{
Subsystem->RemoveTraceState(Pending.Key, Pending.Value);
}
// 清空映射
for (const TPair<FGCS_TraceHandle, int32>& Pair : HandleToStateIdx)
{
TagToHandles.RemoveSingle(Pair.Key.TraceTag, Pair.Key);
}
HandleToStateIdx.Empty();
}
bool UGCS_TraceSystemComponent::IsTraceActive(const FGCS_TraceHandle& TraceHandle) const
{
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
{
const FGCS_TraceState& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
return State.ExecutionState == EGCS_TraceExecutionState::InProgress;
}
return false;
}
void UGCS_TraceSystemComponent::OnTraceHitDetected(const FGCS_TraceHandle& TraceHandle, const TArray<FHitResult>& HitResults, const float DeltaTime, const uint32 TickIdx)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSystemComponent::OnTraceHitDetected"), UGCS_TraceSystemComponent_OnTraceHitDetected, STATGROUP_GCS)
FScopeLock ScopedLock(&TraceDoneScopeLock);
for (const FHitResult& HitResult : HitResults)
{
// GCS_CLOG(Verbose, "Trace(%s) hit:%s at location(%s)", *TraceHandle.ToDebugString(), *GetNameSafe(HitResult.GetActor()), *HitResult.Location.ToString())
GCS_CLOG(Verbose, "Trace(%s) hit:%s", *TraceHandle.ToDebugString(), *HitResult.ToString())
OnTraceHitEvent.Broadcast(TraceHandle, HitResult);
}
}
void UGCS_TraceSystemComponent::OnTraceStateChanged(const FGCS_TraceHandle& TraceHandle, bool bNewState)
{
if (bNewState)
{
OnTraceStartedEvent.Broadcast(TraceHandle);
}
else
{
OnTraceStoppedEvent.Broadcast(TraceHandle);
}
OnTraceStateChangedEvent.Broadcast(TraceHandle, bNewState);
}