Menu
CoddyTech

ObjectPool di Unity

Fa parte della sezione Architettura dei giochi in Unity del percorso Unity di Coddy. Lezione 39 di 50.

Unity include un pool, ObjectPool<T> in UnityEngine.Pool. Gli dici cosa fare a ogni passaggio con quattro lambda e lui tiene da parte gli oggetti in eccesso e ne conta il numero. Questo camino emette uno sbuffo di fumo ogni 0.2 secondi e ogni sbuffo viene restituito dopo un secondo:

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 viene eseguita quando Get trova il pool vuoto; actionOnGet viene eseguita a ogni Get e actionOnRelease a ogni Release. Quando uno sbuffo viene restituito mentre maxSize oggetti in eccesso sono già in attesa, il pool non lo conserva: actionOnDestroy lo distrugge. defaultCapacity è solo la dimensione iniziale dell'elenco del pool, quindi non viene creato nulla in anticipo. CountAll conta gli sbuffi creati dal pool e ancora in suo possesso, CountActive quelli presenti nel gioco e CountInactive quelli in attesa. Get attiva lo sbuffo prima che il camino lo posizioni: qui va bene perché Puff non ha un OnEnable che legge la sua posizione. Lo sbuffo mantiene il pool come IObjectPool<Puff>, l'interfaccia implementata da ogni pool di Unity, e Launch azzera la sua età a ogni utilizzo.

Con collectionCheck attivo, restituire uno sbuffo che è già nel pool genera un'eccezione InvalidOperationException: Trying to release an object that has already been released to the pool. Il controllo viene eseguito solo nell'Editor, perché cerca tra gli oggetti in attesa a ogni Release; in una build lo sbuffo rimarrebbe due volte nel pool e due Get restituirebbero lo stesso oggetto. Una causa comune è un oggetto che continua a essere eseguito dopo la restituzione e si restituisce di nuovo: ecco perché actionOnRelease lo disattiva.

challenge icon

Sfida

Medio

Fuochi d'artificio. Ogni pressione della barra spaziatrice lancia 8 scintille dal lanciatore, e ogni Spark chiama Return(this) del lanciatore quando termina il suo secondo di vita (Spark è pronto). Oggi il lanciatore istanzia e distrugge ogni scintilla. Usa il pool di Unity:

  • In Launcher.cs, crea un ObjectPool<Spark> in Awake. Crea una scintilla istanziando sparkPrefab, la attiva quando viene prelevata e la disattiva quando viene restituita, e distrugge l'oggetto della scintilla quando la elimina.
  • Il pool conserva al massimo 5 scintille di riserva, quindi dopo una raffica di 8, 5 restano in attesa e 3 vengono distrutte.
  • La barra spaziatrice preleva le sue 8 scintille dal pool invece di istanziarle, e Return restituisce la scintilla al pool invece di distruggerla.

Premi Play, spara qualche raffica, poi premi Check.

Provalo tu

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

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

    void Awake()
    {
        // Crea il pool: crea, ottieni, rilascia e distruggi, con al massimo 5 scintille di riserva
    }

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

    // Una scintilla che ha esaurito il suo ciclo di vita
    public void Return(Spark spark)
    {
        Destroy(spark.gameObject);
    }

    // Verso l'esterno e verso l'alto, distribuite uniformemente attorno al lanciatore
    Vector3 Direction(int i)
    {
        return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
    }
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Architettura dei giochi in Unity