Suche…


Objektpool

Wenn Sie ein Spiel erstellen, müssen Sie manchmal immer wieder Objekte des gleichen Typs erstellen und zerstören. Sie können dies einfach tun, indem Sie ein Prefab erstellen und es instanziieren / zerstören, wann immer Sie dies benötigen. Dies ist jedoch ineffizient und kann Ihr Spiel verlangsamen.

Eine Möglichkeit, dieses Problem zu umgehen, ist das Pooling von Objekten. Im Grunde bedeutet dies, dass Sie einen Pool (mit oder ohne Begrenzung der Anzahl) von Objekten haben, die Sie wann immer möglich wiederverwenden können, um unnötige Instanziierung oder Zerstörung zu verhindern.

Unten sehen Sie ein Beispiel für einen einfachen Objektpool

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

Lassen Sie uns zuerst die Variablen durchgehen

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

private List<GameObject> pool = new List<GameObject>();
  • GameObject prefab : Dies ist der Prefab, den der Objektpool verwendet, um neue Objekte in den Pool zu instantiieren.
  • int amount : Dies ist die maximale Anzahl von Elementen, die sich im Pool befinden können. Wenn Sie ein anderes Element instanziieren möchten und der Pool bereits sein Limit erreicht hat, wird ein anderes Element aus dem Pool verwendet.
  • bool populateOnStart : Sie können den Pool beim Start bool populateOnStart oder nicht. Dadurch wird der Pool mit Instanzen des Prefab gefüllt, so dass Sie beim ersten Aufruf von Instantiate ein bereits vorhandenes Objekt erhalten
  • bool growOverAmount : Wenn Sie diese bool growOverAmount auf true setzen, wächst der Pool immer dann, wenn in einem bestimmten Zeitraum mehr als der Betrag angefordert wird. Sie können die Anzahl der Elemente, die Sie in Ihren Pool aufnehmen möchten, nicht immer genau vorhersagen, sodass Sie bei Bedarf mehr Pool hinzufügen können.
  • List<GameObject> pool : Dies ist der Pool, der Ort, an dem alle instanziierten / zerstörten Objekte gespeichert werden.

Schauen wir uns nun die Start Funktion an

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

In der prefab prüfen wir, ob die Liste beim Start prefab soll und ob das prefab festgelegt wurde und der Betrag größer als 0 ist (sonst würden wir unbegrenzt prefab ).

Dies ist nur eine einfache Methode, um neue Objekte zu instantiieren und in den Pool zu setzen. Zu beachten ist, dass wir alle Instanzen auf inaktiv setzen. Auf diese Weise sind sie noch nicht im Spiel sichtbar.

Als nächstes gibt es die Instantiate Funktion, bei der die meiste Magie stattfindet

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

Die Instantiate Funktion sieht genauso aus wie Unitys eigene Instantiate Funktion, es sei denn, das Prefab wurde bereits als Klassenmitglied bereitgestellt.

Der erste Schritt der Instantiate Funktion besteht darin, zu prüfen, ob sich momentan ein inaktives Objekt im Pool befindet. Dies bedeutet, dass wir das Objekt wiederverwenden und dem Anforderer zurückgeben können. Wenn sich ein inaktives Objekt im Pool befindet, setzen wir die Position und die Rotation, setzen es auf aktiv (andernfalls könnte es versehentlich wiederverwendet werden, falls Sie es vergessen, es zu aktivieren) und an den Anforderer zurückgeben.

Der zweite Schritt erfolgt nur, wenn sich keine inaktiven Elemente im Pool befinden und der Pool den ursprünglichen Betrag überschreiten darf. Was passiert, ist einfach: Eine weitere Instanz des Prefab wird erstellt und dem Pool hinzugefügt. Wenn Sie das Wachstum des Pools zulassen, können Sie die richtige Anzahl von Objekten im Pool anzeigen.

Der dritte "Schritt" erfolgt nur, wenn sich keine inaktiven Elemente im Pool befinden und der Pool nicht vergrößert werden darf. In diesem Fall erhält der Anforderer ein ungültiges GameObject, was bedeutet, dass nichts verfügbar war und ordnungsgemäß behandelt werden sollte, um NullReferenceExceptions zu verhindern.

Wichtig!

Um sicherzustellen, dass Ihre Gegenstände wieder in den Pool gelangen, sollten Sie die Spielobjekte nicht zerstören. Das einzige, was Sie tun müssen, ist, sie inaktiv zu setzen, wodurch sie für die Wiederverwendung durch den Pool verfügbar sind.

Einfacher Objektpool

Nachfolgend finden Sie ein Beispiel für einen Objektpool, in dem ein bestimmter Objekttyp gemietet und zurückgegeben werden kann. Um den Objektpool zu erstellen, sind eine Funktion für die Erstellungsfunktion und eine Aktion zum Zerstören des Objekts erforderlich, um dem Benutzer Flexibilität zu geben. Wenn ein Objekt angefordert wird, wenn der Pool leer ist, wird ein neues Objekt erstellt. Wenn der Pool Objekte enthält, werden Objekte aus dem Pool entfernt und zurückgegeben.

Objektpool

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

Verwendungsbeispiel

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

Ein weiterer einfacher Objektpool

Ein anderes Beispiel: Eine Waffe, die Kugeln abschießt.

Die Waffe fungiert als Objektpool für die von ihr erstellten Aufzählungszeichen.

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow