캐주얼 슈팅 액션 대전 게임 (모바일) - 10 오브젝트 풀 개선
이전에 만들었던 오브젝트 풀은 리스트 한 개를 사용하여 bullet과 hitEffect 모두를 넣어놨다.
만약 나중에 추가적으로 관리해야 할 오브젝트들이 늘어난다면 리스트 안에서 특정 오브젝트를 찾는데 많은 연산을 해야할 지도 모른다.
예를 들어 리스트 안에 Bullet이 10개 HitEffect가 10개 StunEffect가 10개 있다고 해보자 필자는 리스트에 이 오브젝트들을 차곡차곡 넣어놨기 때문에 StunEffect를 가져다가 쓰려면 최소 21번은 리스트를 뒤져야 한다.
그래서 이 점을 개선하기 위해 오브젝트별로 리스트를 나눠놔야할 필요성을 느꼈다. 물론 지금은 관리해야 될 오브젝트들이 몇개 되지 않아서 이렇게 써도 퍼포먼스 적인 측면에서 큰 타격이 없을 것이다. 하지만 회사에 들어간다거나 혼자 게임을 개발할 경우 이러한 부분까지 신경을 써줘야 하기 때문에 미리 경험을 쌓는 느낌으로 만들어 봤다.
첫 번째로 시도한 방법은 List안에 List를 둔 것이다(2차원 리스트). 오브젝트 별로 관리하는 리스트가 있고 모든 리스트를 가지고 있는 리스트가 있는 형식이다. 이렇게 할 경우 이중 for문을 돌려야 하지만 코드가 보기 쉽고 간략화 된다는 장점이 있었다(전체적인 코드 길이가 짧아짐). 확장성 부분에서 두 번째 방법보다 괜찮은 것 같다.
1. 2차원 리스트
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MemoryPool : MonoBehaviour
{
public MemoryPool instance { get; private set; }
public GameObject bullet;
public GameObject hitEffect;
public enum ObjectType { Bullet, HitEffect }
private void Awake()
{
InitEnv(bullet, ObjectType.Bullet);
InitEnv(hitEffect, ObjectType.HitEffect);
}
[Serializable]
public class PoolItem
{
public bool isActive;
public GameObject gameObect;
}
private int increaseCount = 5; //오브젝트가 부족할 때 Instantiate()로 추가 생성되는 오브젝트 개수
private int maxCount; //현재 리스트에 등록되어 있는 오브젝트 개수
private int activeCount; //현재 게임에 사용되고 있는 활성화 오브젝트 개수
private GameObject bulletObject; //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
private GameObject hitEffectObject; //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
public List<List<PoolItem>> wholePoolItemList; //관리되는 모든 오브젝트를 저장하는 리스트
public List<PoolItem> bulletList;
public List<PoolItem> hitEffectList;
public MemoryPool() { }
public void InitEnv(GameObject poolObject, ObjectType objectType)
{
maxCount = 0;
activeCount = 0;
switch (objectType)
{
case ObjectType.Bullet: this.bulletObject = poolObject; break;
case ObjectType.HitEffect: this.hitEffectObject = poolObject; break;
default: break;
}
if(wholePoolItemList == null)
{
wholePoolItemList = new List<List<PoolItem>>();
wholePoolItemList.Add(bulletList);
wholePoolItemList.Add(hitEffectList);
}
InstantiateObjects(objectType);
}
// increraseCount 단위로 오브젝트를 생성
public void InstantiateObjects(ObjectType objectType)
{
maxCount += increaseCount;
for( int i =0; i < increaseCount; i++ )
{
PoolItem poolItem = new PoolItem();
poolItem.isActive = false;
switch (objectType)
{
case ObjectType.Bullet: poolItem.gameObect = GameObject.Instantiate(bulletObject); break;
case ObjectType.HitEffect: poolItem.gameObect = GameObject.Instantiate(hitEffectObject); break;
default: break;
}
poolItem.gameObect.transform.parent = this.transform;
poolItem.gameObect.SetActive(false);
switch (objectType)
{
case ObjectType.Bullet: wholePoolItemList[0].Add(poolItem); break;
case ObjectType.HitEffect: wholePoolItemList[1].Add(poolItem); break;
default: break;
}
}
}
// 현재 관리중인 (활성 / 비활성) 모든 오브젝트를 삭제
public void DestroyObjects()
{
if (wholePoolItemList == null)
return;
int wCount = wholePoolItemList.Count;
int bCount = bulletList.Count;
int hCount = hitEffectList.Count;
for(int i = 0; i < wCount; i++)
{
for (int j = 0; j < bCount; j++)
{
GameObject.Destroy(wholePoolItemList[i][j].gameObect);
}
for (int k = 0; k < hCount; k++)
{
GameObject.Destroy(wholePoolItemList[i][k].gameObect);
}
}
wholePoolItemList.Clear();
}
// poolItemList에 저장되어 있는 오브젝트를 활성화 해서 사용
// 현재 모든 오브젝트가 사용중이면 InstantiateObjects()로 추가 생성
public GameObject ActivePoolItem(ObjectType objectType)
{
if (wholePoolItemList == null)
return null;
if (activeCount == maxCount)
InstantiateObjects(objectType);
int wCount = wholePoolItemList.Count;
int whichListCount = 0;
if (objectType == ObjectType.Bullet) { wCount = 0; whichListCount = bulletList.Count; }
else if (objectType == ObjectType.HitEffect) { wCount = 1; whichListCount = hitEffectList.Count; }
for (int i = 0; i < whichListCount; i++)
{
PoolItem poolItem = wholePoolItemList[wCount][i];
if(poolItem.isActive == false)
{
activeCount++;
poolItem.isActive = true;
poolItem.gameObect.SetActive(true);
return poolItem.gameObect;
}
}
return null;
}
// 현재 사용이 완료된 오브젝트를 비활성화 상태로 설정
public void DeactivatePoolItem(GameObject usedObject)
{
if (wholePoolItemList == null || usedObject == null)
return;
int wCount = wholePoolItemList.Count;
int whichListCount = 0;
if (usedObject.CompareTag("Bullet")) { wCount = 0; whichListCount = bulletList.Count; }
else if (usedObject.CompareTag("HitEffect")) { wCount = 1; whichListCount = hitEffectList.Count; }
for (int i = 0; i < whichListCount; i++)
{
PoolItem poolItem = wholePoolItemList[wCount][i];
if(poolItem.gameObect == usedObject)
{
activeCount--;
poolItem.isActive = false;
poolItem.gameObect.SetActive(false);
return;
}
}
}
// 게임에서 사용중인 모든 오브젝트를 비활성화 상태로 설정
public void DeactiveAllPoolItems()
{
if (wholePoolItemList == null)
return;
int wCount = wholePoolItemList.Count;
int bCount = bulletList.Count;
int hCount = hitEffectList.Count;
for (int i = 0; i < wCount; i++)
{
for (int j = 0; j < bCount; j++)
{
PoolItem poolItem = wholePoolItemList[i][j];
if (poolItem.gameObect != null && poolItem.isActive == true)
{
poolItem.isActive = false;
poolItem.gameObect.SetActive(false);
}
}
for (int k = 0; k < hCount; k++)
{
PoolItem poolItem = wholePoolItemList[i][k];
if (poolItem.gameObect != null && poolItem.isActive == true)
{
poolItem.isActive = false;
poolItem.gameObect.SetActive(false);
}
}
}
activeCount = 0;
}
}
두 번째로 시도한 방법은 전체적으로 관리하는 리스트를 빼고 각 오브젝트의 리스트만 남겨 두는 것이었다.
난이도는 이쪽이 쉽긴 한데 확장성을 봤을 때는 조금 떨어지는 것 같다. 코드의 중복성이 있다.
2. 1차원 리스트
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MemoryPool : MonoBehaviour
{
public MemoryPool instance { get; private set; }
public GameObject bullet;
public GameObject hitEffect;
public enum ObjectType { Bullet, HitEffect }
private void Awake()
{
InitEnv(bullet, ObjectType.Bullet);
InitEnv(hitEffect, ObjectType.HitEffect);
}
[Serializable]
public class PoolItem
{
public bool isActive;
public GameObject gameObect;
}
private int increaseCount = 5; //오브젝트가 부족할 때 Instantiate()로 추가 생성되는 오브젝트 개수
private int maxCount; //현재 리스트에 등록되어 있는 오브젝트 개수
private int activeCount; //현재 게임에 사용되고 있는 활성화 오브젝트 개수
private GameObject bulletObject; //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
private GameObject hitEffectObject; //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
public List<PoolItem> bulletList;
public List<PoolItem> hitEffectList;
public MemoryPool() { }
public MemoryPool(GameObject poolObject)
{
maxCount = 0;
activeCount = 0;
this.bulletObject = poolObject;
//wholePoolItemList = new List<List<PoolItem>>();
//InstantiateObjects();
}
public void InitEnv(GameObject poolObject, ObjectType objectType)
{
maxCount = 0;
activeCount = 0;
switch (objectType)
{
case ObjectType.Bullet: this.bulletObject = poolObject; break;
case ObjectType.HitEffect: this.hitEffectObject = poolObject; break;
default: break;
}
if(bulletList == null)
bulletList = new List<PoolItem>();
if (hitEffectList == null)
hitEffectList = new List<PoolItem>();
InstantiateObjects(objectType);
}
// increraseCount 단위로 오브젝트를 생성
public void InstantiateObjects(ObjectType objectType)
{
maxCount += increaseCount;
for( int i =0; i < increaseCount; i++ )
{
PoolItem poolItem = new PoolItem();
poolItem.isActive = false;
switch (objectType)
{
case ObjectType.Bullet: poolItem.gameObect = GameObject.Instantiate(bulletObject); break;
case ObjectType.HitEffect: poolItem.gameObect = GameObject.Instantiate(hitEffectObject); break;
default: break;
}
poolItem.gameObect.transform.parent = this.transform;
poolItem.gameObect.SetActive(false);
switch (objectType)
{
case ObjectType.Bullet: bulletList.Add(poolItem); break;
case ObjectType.HitEffect: hitEffectList.Add(poolItem); break;
default: break;
}
}
}
// 현재 관리중인 (활성 / 비활성) 모든 오브젝트를 삭제
public void DestroyObjects()
{
if (bulletList == null && hitEffectList == null)
return;
int bCount = bulletList.Count;
int hCount = hitEffectList.Count;
for (int i = 0; i < bCount; i++)
{
GameObject.Destroy(bulletList[i].gameObect);
}
for (int i = 0; i < hCount; i++)
{
GameObject.Destroy(hitEffectList[i].gameObect);
}
bulletList.Clear();
hitEffectList.Clear();
}
// poolItemList에 저장되어 있는 오브젝트를 활성화 해서 사용
// 현재 모든 오브젝트가 사용중이면 InstantiateObjects()로 추가 생성
public GameObject ActivePoolItem(ObjectType objectType)
{
if (bulletList == null && hitEffectList == null)
return null;
if (activeCount == maxCount)
InstantiateObjects(objectType);
int whichListCount = 0;
if (objectType == ObjectType.Bullet)
{
whichListCount = bulletList.Count;
for (int i = 0; i < whichListCount; i++)
{
PoolItem poolItem = bulletList[i];
if (poolItem.isActive == false)
{
activeCount++;
poolItem.isActive = true;
poolItem.gameObect.SetActive(true);
return poolItem.gameObect;
}
}
}
else if (objectType == ObjectType.HitEffect)
{
whichListCount = hitEffectList.Count;
for (int i = 0; i < whichListCount; i++)
{
PoolItem poolItem = hitEffectList[i];
if (poolItem.isActive == false)
{
activeCount++;
poolItem.isActive = true;
poolItem.gameObect.SetActive(true);
return poolItem.gameObect;
}
}
}
return null;
}
// 현재 사용이 완료된 오브젝트를 비활성화 상태로 설정
public void DeactivatePoolItem(GameObject usedObject)
{
if ((bulletList == null && hitEffectList == null) || usedObject == null)
return;
int whichListCount = 0;
if (usedObject.CompareTag("Bullet"))
{
whichListCount = bulletList.Count;
for (int i = 0; i < whichListCount; i++)
{
PoolItem poolItem = bulletList[i];
if (poolItem.gameObect == usedObject)
{
activeCount--;
poolItem.isActive = false;
poolItem.gameObect.SetActive(false);
return;
}
}
}
else if (usedObject.CompareTag("HitEffect"))
{
whichListCount = hitEffectList.Count;
for (int i = 0; i < whichListCount; i++)
{
PoolItem poolItem = hitEffectList[i];
if (poolItem.gameObect == usedObject)
{
activeCount--;
poolItem.isActive = false;
poolItem.gameObect.SetActive(false);
return;
}
}
}
}
// 게임에서 사용중인 모든 오브젝트를 비활성화 상태로 설정
public void DeactiveAllPoolItems()
{
if (bulletList == null && hitEffectList == null)
return;
int bCount = bulletList.Count;
int hCount = hitEffectList.Count;
for (int i = 0; i < bCount; i++)
{
PoolItem poolItem = bulletList[bCount];
if (poolItem.gameObect != null && poolItem.isActive == true)
{
poolItem.isActive = false;
poolItem.gameObect.SetActive(false);
}
}
for (int k = 0; k < hCount; k++)
{
PoolItem poolItem = hitEffectList[hCount];
if (poolItem.gameObect != null && poolItem.isActive == true)
{
poolItem.isActive = false;
poolItem.gameObect.SetActive(false);
}
}
activeCount = 0;
}
}
느낀점: 우선 재미있었다. 빵을 먹으려다가 배달음식을 시킬 때 피자와 치킨 사이에서 고민하는 느낌이었다.
인터넷 검색으로 알게 된 사실인데 시간 복잡도는 첫 번째가 n2으로 더 안 좋다는 걸 알 수 있었다(이중 for문). 하지만 확장성을 봤을 때는 첫 번째가 더 좋기 때문에 아직 추가하지 못한 이펙트들이 있으니 첫 번째 방법으로 채택하겠다.
