팀 프로젝트 시작
24.06.03 월요일 '3D 런닝액션 게임 제작'을 주제로 팀 프로젝트가 시작되었다. 필수 요구사항 중 내가 맡은 요구사항은 다음과 같다
- 장애물 생성 로직 : 오브젝트 풀링을 이용하여 장애물이 지속적으로 생성되도록 하는 로직 구현
- 맵 요소 구성 : 실제 플레이어는 움직이지 않으나 움직이는 듯한 느낌이 들도록 맵 구성
- 점수 시스템
오늘 구현한 기능은 장애물 생성 로직, 맵 요소 구성이다. 두 파트 모두 오브젝트 풀링을 이용하였다.
오브젝트 풀링
오브젝트 풀링은 이미 많이 다루었던 코딩 기술이다. 하지만 제대로 이해하지 못해 다루는 데에 어려움을 겪었다. 앞으로도 많이 사용될 기술이기에 이번 기회에 확실히 이해하고 넘어가고자 하였다.
위 영상은 상술한 두 요구사항을 구현한 것이다. 두 요구사항 모두 오브젝트 풀링을 사용하였다. 게임 시간 내내 재생성되어야 할 두 오브젝트들이 지속적으로 생성 및 소멸한다면 메모리 할당 및 가비지 컬렉션에 따른 성능 저하를 겪을 수 있다.
생성(Instantiate)과 소멸(Destroy)은 모두 비용이 큰 작업인데, 이를 최소화하여 성능을 향상시키는 것이다. 위 기능을 구현하는 데에 사용한 스크립트를 설명한다.
// ObjectPool.cs
[System.Serializable]
public class Pool
{
public string tag;
public GameObject[] prefabs;
public int size;
}
먼저 Pool 클래스를 내부에 선언하였다. 각각 참조에 사용할 tag, 오브젝트의 prefab, 오브젝트 풀의 size를 선언한다. Unity의 Inspector 창에서 간편하게 선언할 수 있다.
위 영상의 기능을 구현하는 데에 총 2개의 Pool이 선언되었다. 하나는 Building tag를 가진 Pool이며, 다른 하나는 Impediment tag를 가진 Pool이다. prefab을 배열로 선언한 이유는 위 영상에서와 같이 장애물이 각기 다른 형태를 띄게 하기 위함이다. 영상에서는 같은 줄에 같은 Prefab이 사용되었으나 우연이다.
public List<Pool> pools = new List<Pool>();
public Dictionary<string, Queue<GameObject>> PoolDictionary;
상술한 바 총 2개의 Pool이 선언되었다 하였다. 이를 효율적으로 관리하고자 Pool의 List를 선언하여 관리하였다. 또한 Pool에서 꺼내어 쓸 Object에 접근하는 과정을 간소화하기 위해 Dictionary를 사용하여 접근한다.
private void Awake()
{
PoolDictionary = new Dictionary<string, Queue<GameObject>>();
int prefabsIndex;
foreach(var pool in pools)
{
Queue<GameObject> queue = new Queue<GameObject>();
for(int i = 0; i < pool.size; i++)
{
prefabsIndex = Random.Range(0, pool.prefabs.Length);
GameObject obj = Instantiate(pool.prefabs[prefabsIndex], transform);
obj.SetActive(false);
queue.Enqueue(obj);
}
PoolDictionary.Add(pool.tag, queue);
}
}
ObjectPool.cs의 Awake 메서드에서는 선언한 List와 Dictionary에 구성 요소를 추가하는 작업을 한다. 반복문을 사용하여 Dictionary에 queue를 추가한다. 추가된 queue는 tag값으로 찾을 수 있다.
public GameObject SpawnFromPool(string tag)
{
if (!PoolDictionary.ContainsKey(tag))
{
return null;
}
GameObject obj = PoolDictionary[tag].Dequeue();
PoolDictionary[tag].Enqueue(obj);
obj.SetActive(true);
return obj;
}
SpawnFromPool 메서드는 tag를 입력받아 Dictionary에서 해당 queue를 찾고, 그 queue에서 알맞은 Object를 배정받는 역할을 한다.
위 코드에서는 하나의 상황이 배제되었다. 바로 '배정된 Pool의 크기보다 많은 오브젝트가 생성되었을 때의 상황'이다. 하지만 이 경우는 우리가 진행할 팀 프로젝트에서 발생하지 않을 상황으로 간주하여 코드를 작성하였다.
오브젝트 풀링 기술 사용 시 주의사항
게임 성능 개선에 만능으로 보이는 이 기술도 특수한 상황에서는 오히려 성능을 악화시킬 수 있다. 위 코드에서는 Pool의 크기를 직접 지정하는데, 이 크기는 합리적인 수치로 입력되어야 한다. 극단적인 예시로 많이 사용되어야 10개 정도 사용되는 오브젝트의 오브젝트 풀 사이즈를 10000으로 지정한다면, 오히려 성능을 악화시킬 수 있게 된다. 앞서 설명하였듯이 생성은 비용이 큰 작업이다. 오브젝트 풀링 초기 작업에서 10000개의 생성 작업을 진행하고 그 오브젝트 풀에서 10개 남짓한 오브젝트만을 다룬다면 과연 합리적인 작업이라 할 수 있을까? 이렇듯 성능 개선을 위한 이 기술의 효율은 온전히 개발자에게 달려있다. 가장 합리적인 Pool의 사이즈를 찾아 지정하는 것을 통해 성능을 개선할 수 있다.
오브젝트 풀링은 수많은 오브젝트가 날아다니는 게임 특성 상 떼어놓을 수 없는 기술이다. 이 기술을 배운 직후에 사용한다고 완벽하게 효율적인 오브젝트 풀링 기술 사용이 가능하냐 하면 그렇지 않다고 당당하게 말하고 싶다. 뭐든 같겠지만 반복 숙달이 중요하다. 다양한 상황에서 이 기술을 적용해보고 더 나은 개선 방법을 찾아 보는 것도 개발 능력 향상에 매우 큰 도움이 될 것 같다.
24.06.03 Today I Learned
'Today I Learned' 카테고리의 다른 글
[TIL][Unity] 인터페이스 (0) | 2024.06.05 |
---|---|
[TIL][Unity] 싱글톤 패턴 (0) | 2024.06.04 |
[TIL][Unity] 직렬화 특강 (0) | 2024.05.30 |
[TIL][Unity] 트러블슈팅 - 움직이는 오브젝트에서 오디오 재생 (0) | 2024.05.29 |
[TIL][Unity] 트러블슈팅 - 같은 레이어 개체 간 충돌 무시 (0) | 2024.05.28 |