현재 아이템 스폰 로직이 레벨1에 미리 배치된 SpawnVolume 수에 따라 아이템을 균등분배 스폰하도록 구현이 돼있었지만 레벨2, 3에서도 SpawnVolume을 배치하게 되면 아이템 수가 원하던대로 분배가 되지 않는 문제가 발생하여 이를 레벨 별로 시작할 때 SpawnVolume도 스폰하고 레벨이 끝나면 스폰된 SpawnVolume을 없애려 한다.
레벨 별로 아이템을 스폰할 영역에 SpawnVolume을 배치하고 각 SpawnVolume에 ActorTag를 달아줘서 레벨에 맞는 Tag를 가진 SpawnVolume을 또 새로운 배열 LevelVolumes에 담는다.
// JinGameState.h
TArray<AActor*> SpawnedActors; // 스폰된 아이템을 담을 배열
TArray<AActor*> FoundVolumes; // 맵에 배치된 모든 스폰볼륨을 담을 배열
TArray<ASpawnVolume*> LevelVolumes; // 레벨에 맞는 스폰볼륨을 담을 배열
레벨 1에 배치된 SpawnVolume은 Tag 1, 레벨 2에 배치된 SpawnVolume은 Tag 2를 달아줬다.


액터 태그는 숫자(1, 2)만 적어도 항상 FName으로 인식이 된다 해서 FName 변수를 선언해서 CurrentLevelIndex + 1를 문자열 형태로 받도록 해서 FName 변수에 담았다.
그리고 현재 배치된 SpawnVolume을 담은 FoundVolumes 배열을 순회하여 레벨에 맞는 태그(FName 변수)를 가진 SpawnVolume을 LevelVolumes 배열에 추가하는 로직을 구현했다.

// JinGameState.cpp
// StartLevel 함수
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundVolumes);
const FName LevelTag = *FString::Printf(TEXT("%d"), CurrentLevelIndex + 1);
for (auto& SpawnVolume : FoundVolumes)
{
if (SpawnVolume && SpawnVolume->ActorHasTag(LevelTag))
{
if(ASpawnVolume* Volume = Cast<ASpawnVolume>(SpawnVolume))
{
LevelVolumes.Add(Volume);
}
}
}
그 후 기존에 FoundVolumes의 원소 개수로 판단하던 로직을 LevelVolumes로 판단하고 LevelVolumes에 담겨 있는 SpawnVolume에서만 스폰하도록 수정했다.
// JinGameState.cpp
// StartLevel 함수
int32 size = LevelVolumes.Num(); // 수정
if (size > 0)
{
ItemToSpawn = FMath::DivideAndRoundUp(40 + (CurrentLevelIndex * 20), size) * size;
SpawnedActors.Reserve(ItemToSpawn);
for(int32 Volume = 0; Volume < size; ++Volume)
{
ASpawnVolume* SpawnVolume = Cast<ASpawnVolume>(LevelVolumes[Volume]); // 수정
if (SpawnVolume)
{
for (int32 i = 0; i < ItemToSpawn / size; ++i)
{
AActor* SpawnedActor = SpawnVolume->SpawnRandomItem();
if (SpawnedActor)
{
SpawnedActors.Add(SpawnedActor);
if (SpawnedActor->IsA(ACoinItem::StaticClass()))
{
++SpawnedCoinCount;
}
}
}
}
}
}
테스트를 해보니 레벨이 넘어가고 LevelVolumes 배열을 초기화 하지 않아서 레벨 2로 넘어갈 때 레벨 1에 배치된 SpawnVolume도 LevelVolumes 배열에 있어서 레벨 2에서 아이템이 스폰이 아주 조금 되는 문제가 발견됐다.
그래서 EndLevel 함수에서 LevelVolumes 배열에 있는 SpawnVolume을 모두 Destroy하고 초기화 하는 로직을 구현했다.
// JinGameState.cpp
// EndLevel 함수
if (CurrentLevelIndex >= MaxLevels)
{
OnGameOver();
return;
}
else
{
for (auto& Item : SpawnedActors)
{
if(Item && !Item->IsPendingKillPending()) // 이 Item이 Destroy()가 호출돼서 곧 사라질 상태인지 확인하는 함수
{
Item->Destroy();
}
}
for (auto& SpawnVolume : LevelVolumes)
{
if (SpawnVolume)
{
SpawnVolume->Destroy();
}
}
SpawnedActors.Empty(); // 스폰된 아이템 배열 초기화
LevelVolumes.Empty(); // 레벨 볼륨 초기화
이렇게 하니 원하던대로 레벨1에 배치된 SpawnVolume 4개에 각 10개의 아이템씩 스폰이 되고 레벨 2에서는 60개의 아이템이 스폰이 됐다.
그리고 스폰이 될 위치에 어떤 액터가 이미 있다면 안겹쳐지도록 스폰이 되는 로직을 구현했다.
이 코드는 언리얼에서 기본적으로 제공하는 기능이고 그냥 GPT에 검색해서 뜬 코드를 복붙했다.(아래 코드의 Params)
// SpawnVolume.cpp
AActor* ASpawnVolume::SpawnItem(TSubclassOf<AActor> ItemClass)
{
if (!ItemClass) return nullptr;
FActorSpawnParameters Params;
Params.SpawnCollisionHandlingOverride =
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;
AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(
ItemClass, // 스폰할 객체
GetRandomPointInVolume(), // 위치 FVector
SpawningBox->GetComponentRotation(), // 회전 FRotator
Params
);
return SpawnedActor;
}
레벨이 넘어갈때마다 전 레벨에서 스폰된 아이템들이 모두 없어지고 현재 레벨에 아이템이 개수에 맞게 스폰되는 모습을 찍은 영상이다.
이제 레벨 3만 구현하면 완성이 될 것 같다.
'개인 프로젝트(과제)' 카테고리의 다른 글
| 8번과제 장애물 만들기 (0) | 2025.10.09 |
|---|---|
| 8번과제 맵 만들기 - 레벨3 (0) | 2025.10.08 |
| 8번과제 맵 만들기 - 레벨2 (0) | 2025.10.06 |
| 8번과제 맵 만들기 - 레벨 1 (0) | 2025.10.02 |
| 8번 과제(게임 루프 및 UI 재설계하기) 필수 과제 (0) | 2025.10.01 |