Szukaj…


Pula obiektów

Czasami, kiedy tworzysz grę, musisz ciągle tworzyć i niszczyć wiele obiektów tego samego typu. Możesz to po prostu zrobić, tworząc prefabrykat i tworzyć go / niszczyć, kiedy tylko zajdzie taka potrzeba, jednak jest to nieefektywne i może spowolnić grę.

Jednym ze sposobów obejścia tego problemu jest łączenie obiektów. Zasadniczo oznacza to, że masz pulę (z ograniczeniem lub bez ograniczenia) obiektów, których zamierzasz ponownie użyć, gdy tylko możesz, aby zapobiec niepotrzebnemu tworzeniu lub niszczeniu.

Poniżej znajduje się przykład prostej puli obiektów

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;
    }
}

Najpierw przejrzyjmy zmienne

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

private List<GameObject> pool = new List<GameObject>();
  • GameObject prefab : jest to prefabryk, którego użyje pula obiektów, aby utworzyć instancję nowych obiektów w puli.
  • int amount : Jest to maksymalna kwota przedmiotów, które mogą być w basenie. Jeśli chcesz utworzyć instancję innego elementu, a pula osiągnęła już limit, zostanie użyty inny element z puli.
  • bool populateOnStart : możesz zapełnić pulę przy starcie lub nie. Spowoduje to wypełnienie puli wystąpieniami prefabrykatu, dzięki czemu przy pierwszym wywołaniu Instantiate otrzymasz już istniejący obiekt
  • bool growOverAmount : Ustawienie wartości true powoduje, że pula rośnie, ilekroć w określonym czasie zażąda się więcej niż kwota. Nie zawsze możesz dokładnie przewidzieć liczbę przedmiotów, które chcesz umieścić w puli, więc w razie potrzeby zwiększy to pulę.
  • List<GameObject> pool : jest to pula, miejsce, w którym przechowywane są wszystkie utworzone / zniszczone obiekty.

Sprawdźmy teraz funkcję 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);
        }
    }
}

W funkcji start sprawdzamy, czy powinniśmy wypełnić listę na starcie i robimy to, jeśli prefab został ustawiony, a ilość jest większa niż 0 (w przeciwnym razie tworzylibyśmy w nieskończoność).

Jest to prosta pętla do tworzenia nowych obiektów i umieszczania ich w puli. Należy zwrócić uwagę na to, że wszystkie instancje są nieaktywne. W ten sposób nie są jeszcze widoczne w grze.

Następnie jest funkcja Instantiate , w której dzieje się większość magii

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;
}

Funkcja tworzenia Instantiate wygląda tak samo, jak własna funkcja tworzenia Instantiate Unity, z tym wyjątkiem, że prefabrykacja została już podana powyżej jako element klasy.

Pierwszym krokiem funkcji tworzenia Instantiate jest sprawdzenie, czy w puli jest teraz nieaktywny obiekt. Oznacza to, że możemy ponownie użyć tego obiektu i zwrócić go żądającemu. Jeśli w puli znajduje się nieaktywny obiekt, ustawiamy pozycję i obrót, ustawiamy go jako aktywny (w przeciwnym razie mógłby zostać ponownie użyty przypadkowo, jeśli zapomnisz go aktywować) i zwróć go do requestera.

Drugi krok ma miejsce tylko wtedy, gdy w puli nie ma nieaktywnych przedmiotów, a pula może rosnąć powyżej początkowej kwoty. To, co się dzieje, jest proste: kolejne wystąpienie prefabrykatu jest tworzone i dodawane do puli. Zezwolenie na wzrost puli pomaga w utrzymaniu odpowiedniej ilości obiektów w puli.

Trzeci „krok” ma miejsce tylko wtedy, gdy w puli nie ma nieaktywnych przedmiotów, a pula nie może rosnąć. Gdy tak się stanie, requester otrzyma zerowy obiekt GameObject, co oznacza, że nic nie było dostępne i powinno być odpowiednio obsługiwane, aby zapobiec NullReferenceExceptions .

Ważny!

Aby upewnić się, że twoje przedmioty wrócą do puli, nie powinieneś niszczyć obiektów gry. Jedyne, co musisz zrobić, to ustawić je jako nieaktywne, co umożliwi ich ponowne użycie w puli.

Prosta pula obiektów

Poniżej znajduje się przykład puli obiektów, która umożliwia wynajem i zwrot danego typu obiektu. Aby utworzyć pulę obiektów, Func dla funkcji tworzenia i Akcja niszczenia obiektu są wymagane, aby dać użytkownikowi elastyczność. Po zażądaniu obiektu, gdy pula jest pusta, zostanie utworzony nowy obiekt, a po zapytaniu, kiedy pula ma obiekty, obiekty są usuwane z puli i zwracane.

Pula obiektów

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;
        }
    }
}

Przykładowe użycie

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.
    }
}

Kolejna prosta pula obiektów

Kolejny przykład: broń strzelająca pociskami.

Broń działa jak pula obiektów dla pocisków, które tworzy.

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow