Sök…


Objekt Pool

Ibland när du skapar ett spel måste du skapa och förstöra många föremål av samma typ om och om igen. Du kan helt enkelt göra detta genom att göra en prefab och instansera / förstöra detta när du behöver. Att göra detta är dock ineffektivt och kan bromsa ditt spel.

Ett sätt att ta itu med denna fråga är objektpooler. I grund och botten vad detta betyder är att du har en pool (med eller utan gräns för mängden) av objekt som du kommer att återanvända när du kan för att förhindra onödig instans eller förstöra.

Nedan är ett exempel på en enkel 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;
    }
}

Låt oss gå igenom variablerna först

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

private List<GameObject> pool = new List<GameObject>();
  • GameObject prefab : detta är den prefab som objektpoolen kommer att använda för att instansera nya objekt i poolen.
  • int amount : Detta är det maximala antalet objekt som kan finnas i poolen. Om du vill instansera en annan artikel och poolen redan har nått sin gräns kommer ett annat objekt från poolen att användas.
  • bool populateOnStart : du kan välja att fylla poolen vid start eller inte. Om du gör det kommer du att fylla i poolen med förekomst av prefab så att första gången du ringer Instantiate får du ett redan befintligt objekt
  • bool growOverAmount : bool growOverAmount ställer in detta så kan poolen växa när mer än beloppet begärs inom en viss tidsram. Du kan inte alltid förutsäga antalet objekt som ska läggas i din pool så det kommer att lägga till mer till din pool när det behövs.
  • List<GameObject> pool : det här är poolen, platsen där alla dina instanserade / förstörda objekt lagras.

Låt oss nu kolla in Start funktionen

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

I startfunktionen kontrollerar vi om vi ska fylla listan vid start och gör det om prefab har ställts in och beloppet är större än 0 (annars skulle vi skapa obestämd tid).

Detta är bara en enkel för att slinga nya objekt och sätta dem i poolen. En sak att vara uppmärksam på är att vi ställer in alla instanser till inaktiva. På det här sättet är de inte synliga i spelet ännu.

Nästa är det Instantiate funktionen, där det mesta av magin händer

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 funktionen ser ut precis som Unitys egen Instantiate funktion, förutom att prefaben redan har tillhandahållits ovan som klassmedlem.

Det första steget i Instantiate funktionen är att kontrollera om det finns ett inaktivt objekt i poolen just nu. Detta innebär att vi kan återanvända det objektet och ge tillbaka det till den som begär det. Om det finns ett inaktivt objekt i poolen ställer vi in position och rotation, ställer in det till att vara aktivt (annars kan det återanvändas av misstag om du glömmer att aktivera det) och returnera det till begäraren.

Det andra steget händer bara om det inte finns några inaktiva föremål i poolen och poolen får växa över det ursprungliga beloppet. Vad som händer är enkelt: en annan instans av prefaben skapas och läggs till i poolen. Att tillåta tillväxt av poolen hjälper dig att ha rätt mängd föremål i poolen.

Det tredje "steget" händer bara om det inte finns några inaktiva föremål i poolen och poolen inte får växa. När detta inträffar får begäraren ett null GameObject vilket innebär att ingenting var tillgängligt och bör hanteras korrekt för att förhindra NullReferenceExceptions .

Viktig!

För att se till att dina föremål kommer tillbaka i poolen bör du inte förstöra spelobjekten. Det enda du behöver göra är att ställa in dem till inaktiva och det kommer att göra dem tillgängliga för återanvändning genom poolen.

Enkel objekt pool

Nedan är ett exempel på en objektpool som tillåter hyra och återlämna av en given objekttyp. För att skapa objektpoolen krävs en Func för create-funktionen och en åtgärd för att förstöra objektet för att ge användaren flexibilitet. När du begär ett objekt när poolen är tom skapas ett nytt objekt och vid begäran när poolen har objekt tas objekt bort från poolen och returneras.

Objekt Pool

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

Exempel på användning

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

En annan enkel objektpool

Ett annat exempel: ett vapen som skjuter ut kulor.

Vapnet fungerar som en objektpool för de kulor som den skapar.

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow