// Copyright 2025 https://yuewu.dev/en All Rights Reserved. #include "Bullet/GCS_BulletSubsystem.h" #include "GCS_LogChannels.h" #include "Bullet/GCS_BulletInstance.h" #include "Engine/World.h" #include "Kismet/KismetMathLibrary.h" UGCS_BulletSubsystem* UGCS_BulletSubsystem::Get(const UWorld* World) { if (IsValid(World)) { return World->GetSubsystem(); } return nullptr; } TArray UGCS_BulletSubsystem::SpawnBullets(const FGCS_BulletSpawnParameters& SpawnParameters) { TArray RetInstances{}; FGCS_BulletDefinition Definition; if (SpawnParameters.DefinitionHandle.IsNull() || !LoadBulletDefinition(SpawnParameters.DefinitionHandle, Definition)) { return RetInstances; } RetInstances = GetOrCreateBulletInstances(SpawnParameters, Definition); for (int i = 0; i < RetInstances.Num(); ++i) { AGCS_BulletInstance* Instance = RetInstances[i]; Instance->bServerInitiated = GetWorld()->GetNetMode() < NM_Client; Instance->bIsLocalPredicting = SpawnParameters.bIsLocalPredicting; if (SpawnParameters.OverrideBulletIds.IsValidIndex(i)) { Instance->SetBulletId(SpawnParameters.OverrideBulletIds[i]); } else { Instance->SetBulletId(FGuid::NewGuid()); } if (SpawnParameters.ParentId.IsValid() && BulletInstances.Contains(SpawnParameters.ParentId)) { Instance->SetParentBulletId(SpawnParameters.ParentId); // if (AGCS_BulletInstance* ParentBulletInstance = BulletInstances[SpawnParameters.ParentId]) // { // if (ParentBulletInstance->Definition.bUseSharedHitList) // { // Instance->HitActors = ParentBulletInstance->HitActors; // } // } } if (SpawnParameters.Request) { Instance->Request = SpawnParameters.Request; } if (SpawnParameters.Owner) { Instance->SetOwner(SpawnParameters.Owner); } Instance->SetDefinitionHandle(SpawnParameters.DefinitionHandle); BulletInstances.Emplace(Instance->BulletId, Instance); } for (int i = 0; i < RetInstances.Num(); ++i) { FRotator RotationAdjustment(Definition.LaunchElevationAngle, Definition.LaunchAngle + Definition.LaunchAngleInterval * i, 0); // Start with the spawn transform // FTransform ModifiedTransform = SpawnParameters.SpawnTransform; // Compose the rotations: apply adjustment relative to original rotation // FRotator FinalRotation = UKismetMathLibrary::ComposeRotators(ModifiedTransform.Rotator(), RotationAdjustment); // ModifiedTransform.SetRotation(FinalRotation.Quaternion()); RetInstances[i]->SetActorTransform(FTransform(RotationAdjustment, FVector::ZeroVector) * SpawnParameters.SpawnTransform, false, nullptr, ETeleportType::ResetPhysics); } //batch beginplay. for (AGCS_BulletInstance* Instance : RetInstances) { Instance->OnBulletBeginPlay(); } return RetInstances; } TArray UGCS_BulletSubsystem::GetIdsFromBullets(TArray Instances) { TArray Ids; for (AGCS_BulletInstance* BulletInstance : Instances) { Ids.Add(BulletInstance->BulletId); } return Ids; } TArray UGCS_BulletSubsystem::GetOrCreateBulletInstances(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition) { TArray OutInstances; static int32 MaxAllowedLoops = 30; int32 Counter = 0; while (OutInstances.Num() < Definition.BulletCount) { if (AGCS_BulletInstance* Instance = TakeBulletFromPool(Definition.BulletActorClass.LoadSynchronous())) { OutInstances.Add(Instance); } else if (AGCS_BulletInstance* Instance2 = CreateBulletInstance(SpawnParameters, Definition)) { OutInstances.Add(Instance2); } Counter++; if (Counter >= MaxAllowedLoops) { UE_LOG(LogGCS, Warning, TEXT("BulletSubsystem reach max allowed bullet spawn loops(%d)."), MaxAllowedLoops); break; } } return OutInstances; } AGCS_BulletInstance* UGCS_BulletSubsystem::TakeBulletFromPool(TSubclassOf BulletClass) { int32 Found = INDEX_NONE; for (int i = 0; i < BulletPools.Num(); i++) { if (BulletPools[i].GetClass() == BulletClass) { Found = i; break; } } if (Found != INDEX_NONE) { AGCS_BulletInstance* FoundInstance = BulletPools[Found]; UE_LOG(LogGCS, Verbose, TEXT("Taking bullet(%s) from pool."), *BulletClass->GetName()); BulletPools.RemoveAtSwap(Found); return FoundInstance; } return nullptr; } void UGCS_BulletSubsystem::DestroyBullet(FGuid BulletId) { if (BulletInstances.Contains(BulletId)) { AGCS_BulletInstance* BulletToRemove = BulletInstances[BulletId]; BulletToRemove->SetDefinitionHandle(FDataTableRowHandle()); BulletToRemove->SetOwner(nullptr); BulletInstances.Remove(BulletId); BulletPools.Add(BulletToRemove); UE_LOG(LogGCS, Verbose, TEXT("Return bullet(%s) back to pool."), *BulletToRemove->GetClass()->GetName()); } } AGCS_BulletInstance* UGCS_BulletSubsystem::CreateBulletInstance(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition) { if (Definition.BulletActorClass.IsNull()) { UE_LOG(LogGCS, Error, TEXT("Failed to create bullet instance for definition(%s),missing BulletActorClass!!!"), *SpawnParameters.DefinitionHandle.ToDebugString()); return nullptr; } UClass* BulletClass = Definition.BulletActorClass.LoadSynchronous(); check(BulletClass); FActorSpawnParameters ActorSpawnParameters; ActorSpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; AGCS_BulletInstance* NewInstance = GetWorld()->SpawnActor(BulletClass, FTransform::Identity, ActorSpawnParameters); if (NewInstance) { UE_LOG(LogGCS, Verbose, TEXT("Create new bullet instance for class(%s)"), *BulletClass->GetName()); return NewInstance; } UE_LOG(LogGCS, Error, TEXT("Failed to create new bullet instance for class(%s)"), *BulletClass->GetName()); return nullptr; } bool UGCS_BulletSubsystem::LoadBulletDefinition(const FDataTableRowHandle& Handle, FGCS_BulletDefinition& OutDefinition) { if (FGCS_BulletDefinition* Definition = Handle.GetRow(TEXT("LoadBulletDefinition"))) { OutDefinition = *Definition; return true; } return false; }