수색…


개체 풀

가끔 게임을 만들 때 같은 유형의 많은 물체를 반복해서 만들고 파괴해야합니다. 프리 패브 (prefab)를 만들어 필요할 때마다 프리 팹 (instantfab)을 만들고 인스턴스화 (instantiate / destroy)하면됩니다. 그러나 이렇게하면 비효율적이며 게임 속도가 느려질 수 있습니다.

이 문제를 해결할 수있는 한 가지 방법은 개체 풀링입니다. 기본적으로 이것이 의미하는 것은 불필요한 인스턴스화 또는 파괴를 막을 수있을 때마다 재사용 할 객체의 풀 (양에 제한이 있거나없는)을 가지고 있다는 것입니다.

다음은 간단한 개체 풀의 예입니다.

public class ObjectPool : MonoBehaviour 
{
    public GameObject prefab;
    public int amount = 0;
    public bool populateOnStart = true;
    public bool growOverAmount = true;

    private List<GameObject> pool = new List<GameObject>();

    void Start() 
    {
        if (populateOnStart && prefab != null && amount > 0) 
        {
            for (int i = 0; i < amount; i++) 
            {
                var instance = Instantiate(Prefab);
                instance.SetActive(false);
                pool.Add(instance);
            }
        }
    }

    public GameObject Instantiate (Vector3 position, Quaternion rotation) 
    {
        foreach (var item in pool) 
        {
            if (!item.activeInHierarchy) 
            {
                item.transform.position = position;
                item.transform.rotation = rotation;
                item.SetActive( true );
                return item;
            }
        }

        if (growOverAmount) 
        {
            var instance = (GameObject)Instantiate(prefab, position, rotation);
            pool.Add(instance);
            return instance;
        }

        return null;
    }
}

먼저 변수를 살펴 보겠습니다.

public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;

private List<GameObject> pool = new List<GameObject>();
  • GameObject prefab : 이것은 객체 풀이 새로운 객체를 풀에 인스턴스화하는 데 사용할 수있는 프리 패브입니다.
  • int amount : 풀에있을 수있는 항목의 최대 크기입니다. 다른 항목을 인스턴스화하려는 경우 풀이 이미 한계에 도달하면 풀의 다른 항목이 사용됩니다.
  • bool populateOnStart : 시작시 풀 채우기를 선택할 수 있습니다. 그렇게하면 prefab의 인스턴스로 풀이 채워 지므로 Instantiate 를 처음 호출 할 때 이미 존재하는 객체를 얻을 수 있습니다
  • bool growOverAmount :이 값을 true로 설정하면 특정 시간대에 금액보다 많은 요청이있을 때마다 풀이 커질 수 있습니다. 수영장에 넣을 물건의 양을 항상 정확하게 예측할 수있는 것은 아니므로 필요할 때 수영장에 더 많이 넣을 수 있습니다.
  • List<GameObject> pool : 인스턴스화 된 / 파괴 된 모든 개체가 저장되는 풀입니다.

이제 Start 함수를 살펴 봅시다.

void Start() 
{
    if (populateOnStart && prefab != null && amount > 0) 
    {
        for (int i = 0; i < amount; i++) 
        {
            var instance = Instantiate(Prefab);
            instance.SetActive(false);
            pool.Add(instance);
        }
    }
}

시작 함수에서 시작시 목록을 채워야하는지 확인하고, prefab 이 설정되어 있고 양이 0보다 큰 경우 (그렇지 않으면 무한정 생성됩니다).

이것은 새로운 객체를 인스턴스화하고 풀에 넣는 단순한 루프입니다. 주의를 기울여야 할 것은 모든 인스턴스를 비활성으로 설정하는 것입니다. 이 방법으로 그들은 아직 게임에서 보이지 않습니다.

다음으로 Instantiate 함수가 있습니다.이 함수는 대부분의 마법이 일어나는 곳입니다.

public GameObject Instantiate (Vector3 position, Quaternion rotation) 
{
    foreach (var item in pool) 
    {
        if (!item.activeInHierarchy) 
        {
            item.transform.position = position;
            item.transform.rotation = rotation;
            item.SetActive(true);
            return item;
        }
    }

    if (growOverAmount) 
    {
        var instance = (GameObject)Instantiate(prefab, position, rotation);
        pool.Add(instance);
        return instance;
    }

    return null;
}

Instantiate 기능은 유니티의 자신과 같은 Instantiate 조립식 이미 클래스 멤버로 이상 제공되었습니다 제외하고, 기능.

Instantiate 함수의 첫 번째 단계는 현재 풀에 비활성 객체가 있는지 확인하는 것입니다. 이것은 우리가 그 객체를 재사용하여 요청자에게 돌려 줄 수 있다는 것을 의미합니다. 풀에 비활성 객체가있는 경우 위치와 회전을 설정하고 활성화 상태로 설정합니다 (활성화하지 않은 경우 실수로 재사용 할 수 있음).

두 번째 단계는 풀에 비활성 항목이없고 풀이 초기 값 이상으로 커질 수있는 경우에만 발생합니다. 어떤 일이 발생하는지 간단합니다 : prefab의 다른 인스턴스가 생성되어 풀에 추가됩니다. 수영장의 성장을 허용하면 수영장에 적당한 양의 물체를 보유 할 수 있습니다.

풀에는 비활성 항목이없는 경우 세 번째 "단계"에만 발생과 풀은 증가 할 수 없습니다. 이 경우 리퀘 스터는 사용할 수있는 것이 없으며 NullReferenceExceptions 을 방지하기 위해 적절히 처리해야하는 null GameObject를 받게됩니다.

중대한!

아이템이 수영장으로 다시 들어가게하려면 게임 오브젝트를 파괴해서는 안됩니다 . 당신이해야 할 유일한 일은 그것들을 비활성 상태로 설정하고 수영장을 통해 재사용 할 수있게 할 것입니다.

단순 개체 풀

다음은 주어진 객체 유형을 임대하고 반환 할 수있는 객체 풀의 예입니다. 객체 풀을 생성하려면 create function을위한 Func과 객체를 파기하는 Action이 필요하다. 풀이 비어있을 때 객체를 요청하면 새로운 객체가 생성되고 풀에 객체가 있으면 객체가 풀에서 제거되어 반환됩니다.

개체 풀

public class ResourcePool<T> where T : class
{
    private readonly List<T> objectPool = new List<T>();
    private readonly Action<T> cleanUpAction;
    private readonly Func<T> createAction;

    public ResourcePool(Action<T> cleanUpAction, Func<T> createAction)
    {
        this.cleanUpAction = cleanUpAction;
        this.createAction = createAction;
    }

    public void Return(T resource)
    {
        this.objectPool.Add(resource);
    }

    private void PurgeSingleResource()
    {
        var resource = this.Rent();
        this.cleanUpAction(resource);
    }

    public void TrimResourcesBy(int count)
    {
        count = Math.Min(count, this.objectPool.Count);
        for (int i = 0; i < count; i++)
        {
            this.PurgeSingleResource();
        }
    }

    public T Rent()
    {
        int count = this.objectPool.Count;
        if (count == 0)
        {
            Debug.Log("Creating new object.");
            return this.createAction();
        }
        else
        {
            Debug.Log("Retrieving existing object.");
            T resource = this.objectPool[count-1];
            this.objectPool.RemoveAt(count-1);
            return resource;
        }
    }
}

샘플 사용법

public class Test : MonoBehaviour
{
    private ResourcePool<GameObject> objectPool;

    [SerializeField]
    private GameObject enemyPrefab;

    void Start()
    {
        this.objectPool = new ResourcePool<GameObject>(Destroy,() => Instantiate(this.enemyPrefab) );
    }

    void Update()
    {
        // To get existing object or create new from pool
        var newEnemy = this.objectPool.Rent();
        // To return object to pool
        this.objectPool.Return(newEnemy);
        // In this example the message 'Creating new object' should only be seen on the frame call
        // after that the same object in the pool will be returned.
    }
}

또 다른 간단한 개체 풀

또 다른 예 : 총알을 발사하는 무기.

무기는 생성 된 총알에 대한 개체 풀 역할을합니다.

public class Weapon : MonoBehaviour {
    
    // The Bullet prefab that the Weapon will create
    public Bullet bulletPrefab;

    // This List is our object pool, which starts out empty
    private List<Bullet> availableBullets = new List<Bullet>();

    // The Transform that will act as the Bullet starting position
    public Transform bulletInstantiationPoint;

    // To spawn a new Bullet, this method either grabs an available Bullet from the pool,
    // otherwise Instantiates a new Bullet
    public Bullet CreateBullet () {
        Bullet newBullet = null;

        // If a Bullet is available in the pool, take the first one and make it active
        if (availableBullets.Count > 0) {
            newBullet = availableBullets[availableBullets.Count - 1];

            // Remove the Bullet from the pool
            availableBullets.RemoveAt(availableBullets.Count - 1);

            // Set the Bullet's position and make its GameObject active
            newBullet.transform.position = bulletInstantiationPoint.position;
            newBullet.gameObject.SetActive(true);
        }
        // If no Bullets are available in the pool, Instantiate a new Bullet
        else {
            newBullet newObject = Instantiate(bulletPrefab, bulletInstantiationPoint.position, Quaternion.identity);

            // Set the Bullet's Weapon so we know which pool to return to later on
            newBullet.weapon = this;
        }
        
        return newBullet;
    }

}

public class Bullet : MonoBehaviour {
    
    public Weapon weapon;

    // When Bullet collides with something, rather than Destroying it, we return it to the pool
    public void ReturnToPool () {
        // Add Bullet to the pool
        weapon.availableBullets.Add(this);

        // Disable the Bullet's GameObject so it's hidden from view
        gameObject.SetActive(false);
    }

}


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow