Menu
CoddyTech

Unity의 ObjectPool

Coddy Unity 여정의 Unity 게임 아키텍처 섹션에 포함된 레슨. 50개 중 39번째.

Unity에는 UnityEngine.Pool의 풀인 ObjectPool<T>이 포함되어 있습니다. 네 개의 람다로 각 단계에서 무엇을 할지 지정하면, 풀은 여분 객체와 개수를 관리합니다. 이 굴뚝은 0.2초마다 연기를 내뿜고, 연기 한 덩이는 1초 후에 풀로 돌아갑니다:

using UnityEngine;
using UnityEngine.Pool;

public class Chimney : MonoBehaviour
{
    public Puff puffPrefab;
    private ObjectPool<Puff> pool;
    private float timer;

    void Awake()
    {
        pool = new ObjectPool<Puff>(
            createFunc: () => Instantiate(puffPrefab),
            actionOnGet: puff => puff.gameObject.SetActive(true),
            actionOnRelease: puff => puff.gameObject.SetActive(false),
            actionOnDestroy: puff => Destroy(puff.gameObject),
            collectionCheck: true,
            defaultCapacity: 10,
            maxSize: 20);
    }

    void Update()
    {
        timer += Time.deltaTime;
        if (timer >= 0.2f)
        {
            timer = 0f;
            Puff puff = pool.Get();
            puff.transform.position = transform.position + Vector3.up;
            puff.Launch(pool);
        }
    }
}
// Puff.cs
private IObjectPool<Puff> pool;
private float age;

public void Launch(IObjectPool<Puff> owner)
{
    pool = owner;
    age = 0f;
}

void Update()
{
    transform.Translate(0f, Time.deltaTime, 0f);
    age += Time.deltaTime;
    if (age >= 1f) pool.Release(this);
}

createFunc은 Get을 호출했을 때 풀이 비어 있으면 실행되고, actionOnGet은 Get이 호출될 때마다, actionOnRelease는 Release가 호출될 때마다 실행됩니다. 이미 maxSize개의 여분 객체가 풀에 있는 상태에서 연기 덩이를 반환하면, 풀은 그것을 보관하지 않습니다. 대신 actionOnDestroy가 그것을 파괴합니다. defaultCapacity는 풀 목록의 초기 크기일 뿐이므로, 미리 생성되는 객체는 없습니다. CountAll은 풀이 생성하여 여전히 소유하고 있는 연기 덩이의 개수, CountActive는 게임에서 사용 중인 개수, CountInactive는 여분 객체의 개수를 셉니다. Get은 굴뚝이 위치를 설정하기 전에 연기 덩이를 활성화합니다. 여기서는 Puff에 위치를 읽는 OnEnable이 없으므로 괜찮습니다. 연기 덩이는 풀을 IObjectPool<Puff>로 보관하는데, 이 인터페이스는 모든 Unity 풀이 구현합니다. 또한 Launch는 사용할 때마다 경과 시간을 초기화합니다.

collectionCheck가 켜져 있을 때 이미 풀에 들어 있는 연기 덩이를 반환하면 InvalidOperationException이 발생합니다: Trying to release an object that has already been released to the pool. 이 검사는 반환할 때마다 여분 객체를 검색하기 때문에 Editor에서만 실행됩니다. 빌드에서는 연기 덩이가 풀에 두 번 들어가고, 두 번의 Get이 같은 객체를 내어 줄 수 있습니다. 흔한 원인은 반환된 뒤에도 계속 실행되는 객체가 스스로를 다시 반환하는 것입니다. 그래서 actionOnRelease가 객체를 비활성화합니다.

challenge icon

챌린지

중급

불꽃놀이. 스페이스 키를 누를 때마다 발사기에서 불꽃 8개가 터져 나오고, 각 Spark는 수명이 1초 다하면 발사기의 Return(this)를 호출합니다(Spark가 준비되었습니다). 오늘은 발사기가 불꽃을 매번 인스턴스화하고 파괴합니다. Unity의 풀을 사용하세요.

  • Launcher.cs에서 Awake에 ObjectPool<Spark>을 만드세요. 풀은 sparkPrefab을 인스턴스화해 불꽃을 만들고, 불꽃을 꺼낼 때 활성화하고 돌아올 때 비활성화하며, 불꽃을 풀에서 내보낼 때 해당 게임 오브젝트를 파괴합니다.
  • 풀에는 여분의 불꽃을 최대 5개까지 보관하므로, 8개를 발사한 뒤에는 5개가 대기하고 3개가 파괴됩니다.
  • 스페이스 키를 누르면 불꽃 8개를 인스턴스화하는 대신 풀에서 가져오고, Return은 불꽃을 파괴하는 대신 풀에 반환합니다.

플레이를 누르고, 몇 차례 발사한 다음 확인을 누르세요.

직접 해보기

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;

public class Launcher : MonoBehaviour
{
    public Spark sparkPrefab;
    public int sparks = 8;

    void Awake()
    {
        // 풀을 만듭니다: 여분의 spark를 최대 5개로 제한하고 생성, 가져오기, 반환, 파괴 기능을 설정합니다
    }

    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame)
        {
            for (int i = 0; i < sparks; i++)
            {
                Spark spark = Instantiate(sparkPrefab);
                spark.transform.position = transform.position + Vector3.up;
                spark.Launch(Direction(i), this);
            }
        }
    }

    // 수명이 다한 spark
    public void Return(Spark spark)
    {
        Destroy(spark.gameObject);
    }

    // launcher를 중심으로 바깥쪽과 위쪽으로 고르게 퍼지도록 합니다
    Vector3 Direction(int i)
    {
        return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
    }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Unity 게임 아키텍처의 모든 레슨