第一次提交
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Collision/GCS_TraceEnumLibrary.h"
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user