본문 바로가기
유니티 공부

Unity Object Pool(오브젝트 풀) 설명과 사용예시

by g-builder 2024. 10. 23.

유니티에서 Object Pool(오브젝트 풀)은 객체를 효율적으로 관리하기 위한 디자인 패턴 중 하나이다. 게임에서 많은 오브젝트가 생성되고 파괴되는 상황에서 성능 저하를 방지하기 위해 사용한다. 오브젝트 풀링은 미리 일정 수의 오브젝트를 생성해두고, 필요할 때 꺼내서 사용한 후 다시 풀에 반환하는 방식으로 동작한다.

Object Pool의 주요 개념

  1. 오브젝트의 재사용: 게임 중에 자주 생성되고 파괴되는 오브젝트(총알, 적 캐릭터, 파티클 등)를 미리 풀에 만들어 두고, 필요할 때 사용하고 반환한다. 이렇게 하면 매번 오브젝트를 새로 생성하거나 파괴하는 비용을 절감할 수 있다.
  2. 성능 최적화: Instantiate와 Destroy 함수는 유니티에서 상대적으로 무거운 작업이다. 특히 짧은 시간에 여러 번 호출되면 메모리 관리와 성능에 부담이 된다. Object Pool을 사용하면 메모리 할당 및 해제 작업을 줄여 게임 성능을 크게 개선할 수 있다.
  3. 미리 생성: 게임이 시작될 때 오브젝트를 미리 일정량 생성해 두고, 풀에 필요한 오브젝트가 없을 때만 추가로 생성한다. 이 과정에서 일정 수의 오브젝트가 항상 준비되어 있으면 성능 이슈를 줄일 수 있다.
  4. 비활성화 및 활성화: 풀에 반환된 오브젝트는 게임 씬에서 보이지 않도록 비활성화 상태로 두고, 다시 사용할 때 활성화하는 방식으로 동작한다.

Object Pool 사용 예시 (GetObject부분 아래에 수정있음)

총알을 발사하는 게임을 예로 들어보면, 총알이 발사될 때마다 새로 생성하고 파괴하는 대신, 일정 수의 총알을 미리 생성해 두고, 발사 시 풀에서 가져와 사용한 후 다시 풀에 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System.Collections.Generic;
using UnityEngine;
 
public class ObjectPool : MonoBehaviour
{
    public GameObject objectPrefab;  // 미리 생성할 오브젝트의 프리팹
    public int poolSize = 10;  // 풀의 초기 크기
    private List<GameObject> pool;  // 풀에 저장된 오브젝트 리스트
 
    void Start()
    {
        pool = new List<GameObject>();
 
        // 미리 오브젝트를 생성하여 풀에 추가
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(objectPrefab);
            obj.SetActive(false);
            pool.Add(obj);
        }
    }
 
    // 풀에서 오브젝트를 가져오기
    public GameObject GetObject()
    {
        foreach (GameObject obj in pool)
        {
            if (!obj.activeInHierarchy)
            {
                obj.SetActive(true);
                return obj;
            }
        }
 
        // 풀에 활성화 가능한 오브젝트가 없으면 새로 생성하여 추가
        GameObject newObj = Instantiate(objectPrefab);
        newObj.SetActive(true);
        pool.Add(newObj);
        return newObj;
    }
 
    // 오브젝트를 풀에 반환하기
    public void ReturnObject(GameObject obj)
    {
        obj.SetActive(false);
    }
}
 
cs

 

 

예를 들어, 총알을 발사하는 게임을 생각해보면, 플레이어가 총을 쏠 때마다 새로 총알을 생성하지 않고 풀에서 꺼내서 사용하는 방식이다. 그런 다음 총알이 적에 맞거나 화면 밖으로 나가면 다시 풀에 반환한다.

플레이어가 발사 버튼을 누르면, GetObject()를 사용하여 풀에서 비활성화된 총알 오브젝트를 가져와 활성화하고, 적에게 맞거나 화면 밖으로 나가면 ReturnObject(GameObject obj)를 사용해 다시 풀로 돌려보낸다.

1. GetObject() 사용

플레이어가 발사 버튼을 누르면, 아래처럼 GetObject()를 호출해 총알을 풀에서 꺼내 활성화하고, 발사 위치에 배치한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Player : MonoBehaviour
{
    public ObjectPool bulletPool;  // 총알을 관리하는 오브젝트 풀
 
    void Update()
    {
        // 플레이어가 발사 버튼을 눌렀을 때
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Fire();
        }
    }
 
    void Fire()
    {
        // 풀에서 비활성화된 총알 오브젝트를 가져와 활성화
        GameObject bullet = bulletPool.GetObject();
 
        // 총알의 위치를 플레이어 앞쪽으로 설정
        bullet.transform.position = transform.position + transform.forward;
 
        // 총알에 발사 방향을 설정 (예: 속도 설정)
        bullet.GetComponent<Rigidbody>().velocity = transform.forward * 10f;
    }
}
 
 

 

2. ReturnObject(GameObject obj) 사용 (아래에 수정있음)

총알이 적에게 맞거나 화면 밖으로 나가면 **ReturnObject()**를 호출해 총알을 다시 풀로 반환한다. 예를 들어, 총알이 적에게 맞았을 때의 처리:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Bullet : MonoBehaviour
{
    private ObjectPool bulletPool;
 
    void Start()
    {
        bulletPool = GameObject.Find("BulletPool").GetComponent<ObjectPool>();
    }
 
    // 총알이 적에게 맞았을 때 호출되는 함수
    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Enemy")
        {
            // 총알을 풀로 반환
            bulletPool.ReturnObject(this.gameObject);
        }
    }
 
    // 총알이 화면 밖으로 나가면 호출되는 함수
    void OnBecameInvisible()
    {
        // 총알을 풀로 반환
        bulletPool.ReturnObject(this.gameObject);
    }
}
 
 

예시 흐름 요약

  1. GetObject():
    • 플레이어가 발사 버튼을 누르면, 오브젝트 풀에서 비활성화된 총알을 가져와 활성화한다.
    • 총알의 위치와 속도를 설정해 발사한다.

     

  2. ReturnObject(GameObject obj):
    • 총알이 적에 맞거나 화면 밖으로 나가면, 오브젝트 풀로 총알을 반환해 다시 비활성화한다.

이 패턴을 사용하면 매번 오브젝트를 새로 생성하고 파괴하지 않으므로 성능이 향상된다. 특히 짧은 시간 안에 많은 오브젝트를 생성해야 하는 상황에서 유용하다.

 

 

  • 메모리 관리: 메모리 할당과 해제 작업을 줄여 메모리 관리 효율이 높아진다.
  • 성능 향상: 오브젝트를 미리 생성해두면 실시간으로 오브젝트를 생성/파괴하는 부하를 줄일 수 있다.
  • 유연성: 다양한 종류의 오브젝트에 대해 풀을 관리할 수 있어 재사용성이 높다.

결론 - Object Pool을 사용하면 게임의 성능을 최적화하면서 자주 사용하는 오브젝트들을 효율적으로 관리할 수 있다.

 

 

※추가사항

"현재는 불렛을 생성만 하고 있어서 불렛의 스타트 부분에서 풀을 찾아 와야 하지만 불렛을 생성시에 초기화 메서드를 활용하여 풀의 참조를 넘겨주면 find를 하지 않고 사용할수 있어요"

라고 스파르타 캠프의 튜터님께서 조언을 남겨주셨다!!

 

현재는 Bullet 클래스에서 Start 메서드로 풀을 찾고 있지만, GetObject에서 Bullet 오브젝트를 가져올 때 초기화 메서드를 호출하여 풀의 참조를 넘겨줄 수 있다. 이를 통해 Find 호출을 줄여 성능을 개선할 수 있다. 

 

Bullet 클래스에서 초기화 메서드를 작성한다. ReturnObject(GameObject obj) 사용 예제를 수정했다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Bullet : MonoBehaviour
{
    private ObjectPool bulletPool;
 
    // 초기화 메서드
    public void Initialize(ObjectPool pool)
    {
        bulletPool = pool;
    }
 
    // 총알이 적에게 맞았을 때 호출되는 함수
    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Enemy")
        {
            bulletPool.ReturnObject(this.gameObject);
        }
    }
 
    // 총알이 화면 밖으로 나가면 호출되는 함수
    void OnBecameInvisible()
    {
        bulletPool.ReturnObject(this.gameObject);
    }
}
 
cs

 

ObjectPool 클래스에서 GetObject 메서드를 수정하여 생성된 Bullet 오브젝트에 풀의 참조를 전달한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public GameObject GetObject()
{
    foreach (GameObject obj in pool)
    {
        if (!obj.activeInHierarchy)
        {
            obj.SetActive(true);
            return obj;
        }
    }
 
    GameObject newObj = Instantiate(objectPrefab);
    newObj.SetActive(true);
    pool.Add(newObj);
 
    // 불렛 오브젝트 초기화
    Bullet bullet = newObj.GetComponent<Bullet>();
    if (bullet != null)
    {
        bullet.Initialize(this);
    }
 
    return newObj;
}
 
cs

 

이제 GetObject가 불렛을 생성할 때 Initialize 메서드를 통해 풀의 참조를 전달하므로 Start 메서드에서 Find를 사용하지 않아도 된다.