// 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() : 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 UGCS_TraceSystemComponent::AddTraces(const TArray& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject) { TArray Handles; for (const FGCS_TraceDefinition& Def : Definitions) { FGCS_TraceHandle Handle = AddTrace(Def, SourceComponent, SourceObject); if (Handle.IsValidHandle()) { Handles.Add(Handle); } } return Handles; } TArray UGCS_TraceSystemComponent::AddTraces(const TArray& DefinitionHandles, UPrimitiveComponent* SourceComponent, UObject* SourceObject) { TArray 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().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(); 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(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(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& 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(); 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 UGCS_TraceSystemComponent::GetTraceHandlesBySource(const UObject* SourceObject) const { TArray Handles; for (const auto& Pair : HandleToStateIdx) { if (Pair.Key.SourceObject == SourceObject) { Handles.Add(Pair.Key); } } return Handles; } TArray UGCS_TraceSystemComponent::GetTraceHandlesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject) const { TArray Handles; for (const FGameplayTag& Tag : TraceTags) { TArray TagHandles; TagToHandles.MultiFind(Tag, TagHandles); for (const FGCS_TraceHandle& Handle : TagHandles) { if (!IsValid(SourceObject) || Handle.SourceObject == SourceObject) { Handles.AddUnique(Handle); } } } return Handles; } TArray UGCS_TraceSystemComponent::StartTraces(const FGameplayTagContainer& TraceTags, const UObject* SourceObject) { TArray Handles = GetTraceHandlesByTagsAndSource(TraceTags, SourceObject); for (const FGCS_TraceHandle& Handle : Handles) { StartTrace(Handle); } return Handles; } void UGCS_TraceSystemComponent::StartTraces(const TArray& 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()->GetTraceStateAt(*StateIdx); GCS_CLOG(Verbose, "Started trace(%s).", *TraceHandle.ToDebugString()) State.ChangeExecutionState(true); OnTraceStateChanged(TraceHandle, true); } } } void UGCS_TraceSystemComponent::StopTraces(const TArray& TraceHandles) { UGCS_TraceSubsystem* Subsystem = GetWorld()->GetSubsystem(); 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()->GetTraceStateAt(*StateIdx); State.ChangeExecutionState(false); OnTraceStateChanged(TraceHandle, false); } } } TArray UGCS_TraceSystemComponent::AddTracesByDefinitions(const TArray& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject) { return AddTraces(Definitions, SourceComponent, SourceObject); } TArray UGCS_TraceSystemComponent::AddTracesByDefinitionHandles(const TArray& 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 UGCS_TraceSystemComponent::StartTracesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject) { return StartTraces(TraceTags, SourceObject); } void UGCS_TraceSystemComponent::StartTracesByHandles(const TArray& TraceHandles) { return StartTraces(TraceHandles); } void UGCS_TraceSystemComponent::StartTraceByHandle(const FGCS_TraceHandle& TraceHandle) { return StartTrace(TraceHandle); } void UGCS_TraceSystemComponent::StopTracesByHandles(const TArray& TraceHandles) { StopTraces(TraceHandles); } void UGCS_TraceSystemComponent::StopTraceByHandle(const FGCS_TraceHandle& TraceHandle) { StopTrace(TraceHandle); } void UGCS_TraceSystemComponent::RemoveTraceByHandle(const FGCS_TraceHandle& TraceHandle) { RemoveTrace(TraceHandle); } TArray UGCS_TraceSystemComponent::GetTraceHandlesByTag(FGameplayTag TraceToFind) const { TArray 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()->GetTraceStateAt(*StateIdx); return State.SourceComponent; } } return nullptr; } void UGCS_TraceSystemComponent::RemoveAllTraces() { UWorld* World = GetWorld(); if (!IsValid(World)) { return; } UGCS_TraceSubsystem* Subsystem = World->GetSubsystem(); if (!IsValid(Subsystem)) { return; } // 新增:收集所有要移除的Idx和Guid,避免边遍历边修改 TArray> ToRemove; for (const TPair& Pair : HandleToStateIdx) { if (Pair.Key.IsValidHandle()) { if (Subsystem->IsValidStateIdx(Pair.Value)) // 额外验证 { const FGCS_TraceState& State = Subsystem->GetTraceStateAt(Pair.Value); ToRemove.Add(TPair(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& 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()->GetTraceStateAt(*StateIdx); return State.ExecutionState == EGCS_TraceExecutionState::InProgress; } return false; } void UGCS_TraceSystemComponent::OnTraceHitDetected(const FGCS_TraceHandle& TraceHandle, const TArray& 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); }