오브젝트 풀링
자주 생성되고 삭제되는 GameObject를 미리 만들어두고 재사용하는 방식.
Instantiate()와 Destroy()를 반복하면 성능 저하가 생기기 때문에,
쿠키런처럼 골드, 몬스터, 이펙트 등이 자주 등장하는 게임에선 풀링이 필수다.
오브젝트 풀링이란?

오브젝트를 담아두는 오브젝트 풀을 생성한다.

필요할 때 오브젝트 풀에 있는 오브젝트를 빌린다.

오브젝트 풀은 모든 오브젝트를 다 빌려줘서 더이상 빌려줄게 없을 때에만
새로운 오브젝트를 생성해서 꺼내준다.

다 쓰면 다시 오브젝트 풀에 반환한다. (오브젝트를 파괴하지 않음)
출처 : 베르의 게임개발 유튜브
📌 풀링을 사용하지 않을 때 발생하는 문제
1. 가비지 컬렉팅으로 인한 프레임 드랍
2. 메모리 파편화

- 메모리 공간이 있는데도 파편화 때문에 오브젝트를 배치할 수 없는 경우가 생긴다.
(128 byte object를 2개 배치할 수 있는 공간이 있는데도 파편화 때문에 1개밖에 배치하지 못한다.)
코인 아이템 맵에 배치하기
풀링을 이용해서 코인 오브젝트를 맵에 배치해보자.
PoolManager.cs
using System.Collections.Generic;
using UnityEngine;
// 오브젝트(아이템) 풀링 시스템
public enum ObjectType {smallCoin, bigCoin, coinBundle}
public class PoolManager : MonoBehaviour
{
[System.Serializable]
public class PoolItem
{
public ObjectType type;
public GameObject prefab;
public int size;
}
public List<PoolItem> items;
private Dictionary<ObjectType, Queue<GameObject>> pools = new();
private void Awake()
{
// pools 초기화
foreach (var item in items)
{
Queue<GameObject> queue = new();
for (int i = 0; i < item.size; i++)
{
GameObject obj = Instantiate(item.prefab);
obj.SetActive(false);
queue.Enqueue(obj);
}
pools[item.type] = queue;
}
}
// pools 에 있는 오브젝트 가져다 쓰기
public GameObject GetFromPool(ObjectType type, Vector3 position)
{
if (!pools.ContainsKey(type))
{
Debug.Log("pools: null");
return null;
}
Queue<GameObject> queue = pools[type];
GameObject obj = queue.Count > 0 ? queue.Dequeue() : Instantiate(GetPrefab(type));
obj.transform.position = position;
obj.SetActive(true);
Debug.Log("pools: 있음");
return obj;
}
// 쓴 오브젝트 다시 pools 에 반환하기
public void ReturnToPool(ObjectType type, GameObject obj)
{
obj.SetActive(false);
if (!pools.ContainsKey(type)) pools[type] = new Queue<GameObject>();
pools[type].Enqueue(obj);
}
private GameObject GetPrefab(ObjectType type)
{
foreach (var item in items)
{
if (item.type == type)
return item.prefab;
}
return null;
}
}
Spawner.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour
{
public PoolManager pool;
// SmallCoin이 생성될 위치
private Vector3[] spawnPositions_small = new Vector3[]
{
new Vector3(7f, -3.5f, 0),
new Vector3(8f, -3.5f, 0),
new Vector3(11f, -3.5f, 0),
new Vector3(12f, -3.5f, 0),
};
void Start()
{
foreach (var pos in spawnPositions_small)
{
pool.GetFromPool(ObjectType.smallCoin, pos);
}
}
}
[System.Serializable]
클래스나 구조체의 필드를 인스펙터에 표시 가능하게 만들어주는 특성(attribute)
public class PoolManager : MonoBehaviour
{
[System.Serializable]
public class PoolItem
{
public ObjectType type;
public GameObject prefab;
public int size;
}
public List<PoolItem> items
Dictionary<...>
Queue
public enum ObjectType {smallCoin, bigCoin, coinBundle}
private Dictionary<ObjectType, Queue<GameObject>> pools = new();
- Dictionary<...>
→ "키"를 주면 "값" 을 돌려주는 구조. (빨강(키) - 사과(값)) - ObjectType
→ 키의 자료형. enum - Queue<GameObject>
→ 값의 자료형. ObjectType별로 GameObject를 모아놓은 큐(줄 서는 구조)이다. - pools
→ 딕셔너리의 이름(변수명). ObjectType에 맞는 오브젝트 풀을 저장. - = new();
→ 빈 딕셔너리로 초기화하는 문법. C# 9.0 이상에서는 타입을 다시 쓰지 않아도 된다.
enum
일반적으로 클래스 바깥에서 정의한다.
public enum ObjectType {Bullet,Enemy,Coin}
다른 클래스에서 불러올때는 아래와 같이 쓰면 된다.
ObjectType myType = ObjectType.Coin;
하지만 클래스 안에서 정의하면
MyClass.ObjectType myType = MyClass.ObjectType.Enemy;
앞에 클래스 이름을 붙여야 한다.
그래서 재사용이 많은 enum은 클래스 밖에 두는 게 좋다.
풀 초기화
foreach (var item in items)
{
Queue<GameObject> queue = new();
for (int i = 0; i < item.size; i++)
{
GameObject obj = Instantiate(item.prefab);
obj.SetActive(false);
queue.Enqueue(obj);
}
pools[item.type] = queue;
}
- Queue<GameObject> queue = new();
→ 오브젝트를 담을 큐(줄)를 새로 만든다. - GameObject obj = Instantiate(item.prefab);
→ obj에 프리팹을 생성한다. - queue.Enqueue(obj);
→ 생성한 오브젝트를 큐에 넣는다. - pools[item.type] = queue;
→ 해당 타입(ObjectType)(키)에 대한 큐(값)를 pools 딕셔너리에 등록한다.
transform.translate
게임 오브젝트를 현재 위치에서 상대적으로 이동시키는 함수.
transform.Translate(x, y, z);
질문
project창 - Prefabs폴더에 있는 BigCoin 프리팹.
BigCoin 프리팹에 달려있는 CoinController에 Effect를 넣으려고 하는데 적용이 안된다.
Type mismatch 됐다고 뜸.
=> ✅ Effect가 Hierarchy창에 있어서 그렇다.
Effect를 프리팹으로 만든 후 넣어주면 잘 적용된다.
Target에 플레이어 오브젝트를 연결할 땐 스크립트를 통해서 해주면 된다.