ZombieSpawner 이해하기 — 좀비 생성과 오브젝트 풀링
ZombieSpawner 이해하기 — 좀비 생성과 오브젝트 풀링
오늘은 ZombieSurvival 제작 과정에서 ZombieSpawner 코드를 살펴보았다.
이번 코드에서는 좀비를 생성하고, 웨이브마다 등장하는 좀비의 수를 늘리며, 죽은 좀비를 다시 사용할 수 있도록 관리하는 기능을 다룬다.
특히 List, Instantiate, SetActive, IEnumerator와 함께 오브젝트 풀링(Object Pooling) 이라는 개념이 사용되었다.
1. ZombieSpawner의 역할
ZombieSpawner는 좀비의 생성과 웨이브 진행을 관리하는 스크립트다.
주요 기능은 다음과 같다.
- 좀비가 모두 사라지면 다음 웨이브 시작
- 웨이브에 따라 생성할 좀비 수 증가
- 지정된 위치에서 무작위로 좀비 생성
- 좀비의 종류를 무작위로 선택
- 죽은 좀비를 목록에서 제거
- 죽은 좀비를 일정 시간 후 비활성화하고 재사용
이 기능들을 이해하려면 먼저 두 개의 리스트를 구분해야 한다.
private readonly List<Zombie> zombies = new List<Zombie>();
private readonly List<Zombie> zombiePool = new List<Zombie>();
| 변수 | 역할 |
|---|---|
zombies | 현재 웨이브에서 활동 중인 좀비를 관리 |
zombiePool | 비활성화되어 재사용할 수 있는 좀비를 관리 |
쉽게 말해 zombies는 현재 전투에 참여하는 좀비 목록이고, zombiePool은 나중에 다시 사용할 수 있도록 보관하는 좀비 목록이다.
여기서 readonly는 리스트 변수 자체를 다른 리스트로 교체할 수 없도록 한다. 리스트에 좀비를 추가하거나 제거하는 것은 가능하다.
2. Update() — 다음 웨이브 확인
private void Update()
{
if (zombies.Count == 0)
{
SpawnWave();
}
}
Update()는 매 프레임마다 실행되는 함수다.
zombies.Count는 현재 zombies 리스트에 들어 있는 좀비의 수를 의미한다.
따라서 zombies.Count == 0은 현재 관리 중인 좀비가 한 마리도 없다는 뜻이다.
이 조건이 참이면 SpawnWave()를 호출하여 다음 웨이브를 시작한다.
다만 이 코드는 좀비가 없으면 매 프레임마다 조건을 확인한다. 다음 웨이브가 생성되기 전에 zombies가 비어 있는 상태가 유지되면 웨이브가 중복 생성될 수 있으므로, 실제 게임에서는 웨이브 진행 상태를 별도로 관리하는 방법도 고려할 수 있다.
3. SpawnWave() — 웨이브 진행과 좀비 수 결정
private void SpawnWave()
{
wave++;
int count = Mathf.RoundToInt(wave * 1.5f);
for (int i = 0; i < count; ++i)
{
CreateZombie();
}
uIManager.SetWaveText(wave, zombies.Count);
}
이 함수는 새로운 웨이브를 시작하고 생성할 좀비의 수를 결정한다.
① 웨이브 증가
wave++;
새로운 웨이브가 시작될 때마다 wave를 1씩 증가시킨다.
처음에는 wave = 0이므로 첫 번째 웨이브가 시작되면 wave = 1이 된다.
② 생성할 좀비 수 계산
int count = Mathf.RoundToInt(wave * 1.5f);
현재 웨이브에 1.5를 곱한 뒤 가장 가까운 정수로 반올림하여 좀비 수를 결정한다.
| 웨이브 | 계산 | 생성할 좀비 수 |
|---|---|---|
| 1 | 1 × 1.5 = 1.5 | 2 |
| 2 | 2 × 1.5 = 3 | 3 |
| 3 | 3 × 1.5 = 4.5 | 5 |
| 4 | 4 × 1.5 = 6 | 6 |
| 5 | 5 × 1.5 = 7.5 | 8 |
이렇게 하면 웨이브가 진행될수록 더 많은 좀비가 등장한다.
③ 반복문으로 좀비 생성
for (int i = 0; i < count; ++i)
{
CreateZombie();
}
for 반복문을 사용하여 계산한 수만큼 CreateZombie()를 호출한다.
예를 들어 첫 번째 웨이브에서 count가 2라면 CreateZombie()가 두 번 실행된다.
마지막으로 SetWaveText()를 호출하여 현재 웨이브와 좀비 수를 UI에 표시한다.
4. CreateZombie() — 좀비 생성과 배치
Transform point = spawnPoints[Random.Range(0, spawnPoints.Length)];
ZombieData data = zombieDatas[Random.Range(0, zombieDatas.Length)];
먼저 좀비가 등장할 위치와 좀비의 종류를 무작위로 선택한다.
① 생성 위치 선택
Transform point = spawnPoints[Random.Range(0, spawnPoints.Length)];
spawnPoints는 좀비가 등장할 수 있는 위치들을 담고 있는 배열이다.
Random.Range()를 사용하면 배열에 들어 있는 위치 중 하나를 무작위로 선택할 수 있다.
예를 들어 생성 위치가 3개라면 그중 하나를 선택한다.
참고로 정수형 Random.Range(min, max)는 최솟값을 포함하고 최댓값은 포함하지 않는다.
따라서 Random.Range(0, spawnPoints.Length)는 배열의 유효한 인덱스 중 하나를 반환한다.
② 좀비 데이터 선택
ZombieData data = zombieDatas[Random.Range(0, zombieDatas.Length)];
zombieDatas에는 좀비의 종류별 데이터가 저장되어 있다.
이 코드 역시 무작위 인덱스를 선택하여 사용할 ZombieData를 가져온다.
예를 들어 일반 좀비와 강한 좀비의 데이터가 들어 있다면, 그중 하나를 선택하여 좀비를 설정할 수 있다.
③ 좀비 오브젝트 확보
Zombie zombie;
if (zombiePool.Count == 0)
{
zombie = Instantiate(zombiePrefab);
zombie.gameObject.SetActive(false);
zombiePool.Add(zombie);
}
여기서는 재사용할 수 있는 좀비가 있는지 확인한다.
zombiePool.Count == 0은 재사용할 좀비가 없다는 뜻이다.
이 경우 Instantiate()를 사용하여 새로운 좀비를 생성한다.
생성한 좀비는 우선 SetActive(false)로 비활성화한 뒤 zombiePool에 추가한다.
Instantiate()는 새로운 오브젝트를 생성하는 함수이고, SetActive(false)는 해당 게임 오브젝트를 비활성화하는 함수다.
이후 코드에서는 풀에 들어 있는 좀비를 가져와 사용한다.
zombie = zombiePool[0];
zombie.Setup(data);
zombie.transform.position = point.position;
zombie.transform.rotation = point.rotation;
zombie.gameObject.SetActive(true);
각 코드의 역할은 다음과 같다.
zombiePool[0]: 풀에 보관된 첫 번째 좀비를 가져온다.zombie.Setup(data): 선택한 데이터에 맞게 좀비를 설정한다.position: 좀비가 등장할 위치를 설정한다.rotation: 좀비가 등장할 때의 회전을 설정한다.SetActive(true): 좀비를 활성화한다.
즉, 새로 생성한 좀비든 재사용하는 좀비든 필요한 설정을 마친 뒤 활성화하는 것이다.
④ 좀비 목록 이동
zombies.Add(zombie);
zombiePool.Remove(zombie);
활성화한 좀비는 zombies에 추가하고 zombiePool에서는 제거한다.
이렇게 하면 현재 활동 중인 좀비와 재사용할 좀비를 구분할 수 있다.
예를 들어 풀에 있던 좀비 한 마리를 꺼냈다면 다음과 같이 변경된다.
| 목록 | 변경 전 | 변경 후 |
|---|---|---|
zombies | 활동 중인 좀비 1마리 | 활동 중인 좀비 2마리 |
zombiePool | 재사용 가능한 좀비 1마리 | 재사용 가능한 좀비 0마리 |
5. OnDead.AddListener() — 좀비 사망 이벤트 등록
zombie.OnDead.AddListener(() => zombies.Remove(zombie));
zombie.OnDead.AddListener(() => StartCoroutine(CoReturnToPoolZombie(zombie)));
zombie.OnDead.AddListener(() => uIManager.SetWaveText(wave, zombies.Count));
이 코드는 좀비가 죽었을 때 실행할 작업을 등록한다.
OnDead는 좀비가 사망했음을 알리는 이벤트로 사용되고, AddListener()는 해당 이벤트가 발생했을 때 실행할 동작을 추가한다.
여기서 () =>는 람다식이다. 간단히 말하면 실행할 코드를 전달하는 문법이다.
세 가지 동작은 다음과 같다.
첫 번째: 활동 중인 좀비 목록에서 제거
zombie.OnDead.AddListener(() => zombies.Remove(zombie));
좀비가 죽으면 zombies 목록에서 해당 좀비를 제거한다.
두 번째: 일정 시간이 지난 뒤 오브젝트 풀로 반환
zombie.OnDead.AddListener(() => StartCoroutine(CoReturnToPoolZombie(zombie)));
좀비가 죽으면 코루틴을 시작한다.
이 코루틴은 5초를 기다린 뒤 좀비를 비활성화하고 풀에 반환한다.
세 번째: UI 갱신
zombie.OnDead.AddListener(() => uIManager.SetWaveText(wave, zombies.Count));
좀비가 죽어서 목록에서 제거되면 현재 웨이브의 좀비 수를 UI에 다시 표시한다.
단, 이 코드는 사망 이벤트가 발생할 때마다 리스너를 등록한다. 좀비를 재사용하는 구조이므로 같은 좀비가 여러 번 생성될 때 리스너가 중복 등록될 수 있다는 점에 주의해야 한다. 실제 게임에서는 리스너 등록과 해제 시점을 관리하는 것이 좋다.
6. CoReturnToPoolZombie() — 죽은 좀비 재사용하기
private IEnumerator CoReturnToPoolZombie(Zombie zombie)
{
yield return new WaitForSeconds(5f);
zombie.gameObject.SetActive(false);
zombiePool.Add(zombie);
}
이 함수는 코루틴을 사용하여 죽은 좀비를 일정 시간 후 재사용할 수 있도록 만든다.
① 5초 대기
yield return new WaitForSeconds(5f);
좀비가 죽은 직후 바로 비활성화하지 않고 5초를 기다린다.
이 시간 동안 죽는 애니메이션이나 사망 효과를 보여줄 수 있다.
② 좀비 비활성화
zombie.gameObject.SetActive(false);
5초가 지나면 좀비 오브젝트를 비활성화한다.
비활성화하면 해당 오브젝트는 게임에서 활성 상태로 동작하지 않게 된다.
③ 오브젝트 풀에 반환
zombiePool.Add(zombie);
비활성화한 좀비를 zombiePool에 추가한다.
이제 다음에 좀비를 생성해야 할 때 새로운 오브젝트를 만드는 대신, 풀에 보관된 좀비를 다시 사용할 수 있다.
오브젝트 풀링이란?
이처럼 오브젝트를 필요할 때마다 생성하고 파괴하는 대신, 미리 만들어 둔 오브젝트를 재사용하는 방식을 오브젝트 풀링(Object Pooling) 이라고 한다.
좀비 생성
↓
zombies에 추가
↓
플레이어와 전투
↓
좀비 사망
↓
zombies에서 제거
↓
5초 대기
↓
좀비 비활성화
↓
zombiePool에 반환
↓
다음 웨이브에서 재사용
오브젝트 풀링은 반복적인 오브젝트 생성과 파괴를 줄이는 데 도움이 된다. 특히 좀비나 총알처럼 자주 생성되는 오브젝트에 유용하다.
7. 전체 실행 순서
지금까지 살펴본 내용을 종합하면 다음과 같다.
Update()에서 활동 중인 좀비가 있는지 확인한다.- 좀비가 없다면
SpawnWave()를 호출한다. wave를 증가시키고 생성할 좀비 수를 계산한다.CreateZombie()를 반복해서 호출한다.- 생성 위치와 좀비 데이터를 무작위로 선택한다.
- 오브젝트 풀에 좀비가 없다면 새로 생성하고, 있다면 기존 좀비를 재사용한다.
- 좀비를 설정하고 활성화한 뒤
zombies에 추가한다. - 좀비가 죽으면 활동 목록에서 제거하고 UI를 갱신한다.
- 5초 후 좀비를 비활성화하고
zombiePool에 반환한다. - 모든 좀비가 사라지면 다음 웨이브가 시작된다.
이번 코드를 살펴보면서 좀비 생성뿐만 아니라 웨이브 관리, 이벤트 처리, 코루틴, 오브젝트 풀링이 서로 연결되어 동작한다는 것을 알게 되었다.
특히 오브젝트 풀링은 한 번 생성한 좀비를 다시 사용할 수 있게 해 주기 때문에, 게임에서 반복적으로 생성되는 오브젝트를 효율적으로 관리하는 데 중요한 개념이라는 것을 이해하게 되었다.
다만 아직 모든 코드를 완전히 이해한 것은 아니기에 코드 리뷰와 지속적인 코드 작성 연은 필요하다.



