UE/BN Project
Spawn Squad Interface
코어른(진)
2025. 2. 18. 09:48
Why?
나는 Perception자극을 Squad단위로 전파하길 원한다.
이는 Team단위에서 더 쪼개진 단위로, 각 Actor 무리들은 Squad ID를 가진다.
이때, 이 Squad를 Spawn하는 단계에서 문제점이 발생했다.
NewActor->SpawnCollisionHandlingMethod =
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
이런 방식으로 Spawn을 할 때, Z값에 대해서도 Adjust가 적용되어, Actor위에 Actor가 올라타있는 경우가 생기고, 만약 Step On을 허용하지 않는다면, Spawn단계에서 팅겨져 나가는 경우가 발생한다.
따라서 나는 지정된 위치에서 XY방향으로 고르게 퍼져나가며 Spawn하는 방법을 생각해야했다.
How?
기본적인 아이디어는, Spawn위치로부터 나선형으로 뻗어나가며 생성할 위치를 잡는 것이다.
이때, Spawn할 위치에 Collision이 존재한다면 생성할 수 없으므로, World의 OverlapMultiByChannel을 이용하여 해당 위치에 Capsule에 대한 Collision검사를 수행한다.
추가적으로 Spawner의 위치가 여러 Capsule의 Height에 대응되야 하므로, World의 LineTraceSingleByChannel를 이용하여 바닥으로부터 생성할 위치의 Z값을 구한다.
CheckLocation.Z = Hit.Location.Z + CollisionShape.GetCapsuleHalfHeight();
Code
namespace SpawnerStatics
{
int MaxXTarget = 20;
int MaxYTarget = 20;
double SpawnPadding = 5.f;
double MaxGroundDist = 100.f;
}
void UBN_SquadSpawnerStatics::SpiralProcess(FVector& CurrentLocation, int& XTarget, int& XCur, int& YTarget, int& YCur, bool& bIsXTurn, double GridSize)
{
if (bIsXTurn)
{
if (XTarget < 0)
{
CurrentLocation.X -= GridSize;
XCur--;
}
else
{
CurrentLocation.X += GridSize;
XCur++;
}
if (XCur == XTarget)
{
bIsXTurn = false;
XCur = 0;
if(XTarget < 0)
{
XTarget = XTarget * -1 + 1;
}
else
{
XTarget = XTarget * -1 - 1;
}
}
}
else
{
if (YTarget < 0)
{
CurrentLocation.Y -= GridSize;
YCur--;
}
else
{
CurrentLocation.Y += GridSize;
YCur++;
}
if (YCur == YTarget)
{
bIsXTurn = true;
YCur = 0;
if(YTarget < 0)
{
YTarget = YTarget * -1 + 1;
}
else
{
YTarget = YTarget * -1 - 1;
}
}
}
}
UBN_Squad* UBN_SquadSpawnerStatics::SpawnSquad(const TScriptInterface<IBN_SquadSpawnerInterface>& SpawnerInterface)
{
if (SpawnerInterface)
{
UWorld* Loc_World = SpawnerInterface.GetObject()->GetWorld();
const FBN_SquadSpawnInfos& SpawnInfos = SpawnerInterface->GetSpawnInfos();
UBN_Squad* NewSquad = NewObject<UBN_Squad>(Loc_World);
FVector CurrentLocation = SpawnInfos.SpawnLocation;
for (const FBN_SquadSpawnInfo& SpawnInfo : SpawnInfos.Arr_SpawnInfos)
{
// 생성 위치 체크
int XTarget = 1;
int XCur = 0;
int YTarget = 1;
int YCur = 0;
bool bIsXTurn = true;
FCollisionShape CollisionShape = GetDefault<ACharacter>(SpawnInfo.ActorClass)->GetCapsuleComponent()
->GetCollisionShape();
static double Padding = 20.f;
double GridSize = CollisionShape.GetCapsuleRadius() * 2 + SpawnerStatics::SpawnPadding;
bool bIsTooFar = false;
FVector CheckLocation;
for (int i = 0; i < SpawnInfo.SpawnCount; ++i)
{
while (true)
{
TArray<FOverlapResult> HitResults;
FCollisionQueryParams Params;
// 바닥체크
FHitResult Hit;
Loc_World->LineTraceSingleByChannel(
Hit,
CurrentLocation,
CurrentLocation - FVector(0,0, SpawnerStatics::MaxGroundDist),
ECC_Visibility
);
if(!Hit.bBlockingHit)
{
bIsTooFar = true;
break;
}
CheckLocation = CurrentLocation;
CheckLocation.Z = Hit.Location.Z + CollisionShape.GetCapsuleHalfHeight();
Loc_World->OverlapMultiByChannel(
HitResults,
CheckLocation,
FQuat::Identity,
ECC_Visibility,
CollisionShape,
Params
);
if (!HitResults.IsEmpty())
{
SpiralProcess(CurrentLocation, XTarget, XCur, YTarget, YCur, bIsXTurn, GridSize);
// 너무 멀어질 경우
if(XTarget >= SpawnerStatics::MaxXTarget || YTarget >= SpawnerStatics::MaxYTarget)
{
bIsTooFar = true;
break;
}
continue;
}
break;
} // end of while
if(bIsTooFar)
{
continue;
}
FTransform SpawnTransform;
SpawnTransform.SetLocation(CheckLocation);
ABN_Character* NewActor = Loc_World->SpawnActorDeferred<ABN_Character>(
SpawnInfo.ActorClass, SpawnTransform);
NewActor->SpawnCollisionHandlingMethod =
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
NewActor->SetSquad(NewSquad);
NewSquad->AddSquad(NewActor);
NewActor->FinishSpawning(SpawnTransform);
if (AAIController* Loc_Controller = NewActor->GetController<AAIController>())
{
Loc_Controller->SetGenericTeamId(SpawnInfo.TeamId);
}
// 생성이 되었으므로 그 자리를 피해야 한다.
SpiralProcess(CurrentLocation, XTarget, XCur, YTarget, YCur, bIsXTurn, GridSize);
}
}
return NewSquad;
}
return nullptr;
}