Come funziona il pooling in Unity

In Unity, esistono funzioni come Instantiate e Destroy per creare e distruggere gli oggetti nel gioco. Tuttavia, queste operazioni possono essere costose, specialmente se vengono eseguite molto spesso durante il gameplay. Immagina di star giocando a uno sparatutto, dove continuamente tutti i giocatori sparano contro altri nemici infiniti proiettili… sarebbe un continuo “Instantiate’n’Destroy”!
In situazioni del genere, dove bisogna creare e distruggere frequentemente oggetti, soprattutto quando si tratta dello sviluppo di videogiochi e la gestione delle risorse è fondamentale, si usa l’algoritmo di pooling.
Il pooling (da pool, “piscina di oggetti”) viene utilizzato quando vi è la necessità di gestire diversi oggetti che sono sempre gli stessi. In fase di inizializzazione dell’applicazione, l’algoritmo genera un certo numero di oggetti, che vengono attivati e disattivati quando necessario, utilizzando sempre le stesse risorse.
Implementazione del pooling
Creiamo la classe ObjectPoolingManager per gestire il pool di oggetti. Dapprima, viene istanziato un numero predefinito di oggetti e riutilizzati quando necessario. Il metodo GetPooledObject restituisce un oggetto disattivato nel pool quando richiesto.
public class ObjectPoolingManager : MonoBehaviour
{
// Dichiarazione di variabili pubbliche
public static ObjectPoolingManager instance; // Riferimento statico a se stesso per l'accesso globale
public GameObject pooledObject; // Il tipo di oggetto da poolare
public int pooledAmount = 10; // Il numero iniziale di oggetti da poolare
// Lista di oggetti poolati
private List<GameObject> pooledObjects;
// Metodo chiamato quando l'oggetto viene inizializzato
private void Awake()
{
// Verifica se c'è già un'istanza di ObjectPoolingManager
if (instance == null)
instance = this; // Se non c'è, imposta questa istanza come l'istanza singleton
else
Destroy(gameObject); // Se c'è già un'istanza, distruggi questo oggetto per evitare duplicati
}
// Metodo chiamato all'avvio
void Start()
{
pooledObjects = new List<GameObject>(); // Inizializza la lista di oggetti poolati
// Loop per creare e inizializzare gli oggetti poolati
for (int i = 0; i < pooledAmount; i++)
{
GameObject obj = Instantiate(pooledObject); // Crea un nuovo oggetto dal prefab specificato
obj.SetActive(false); // Disattiva l'oggetto appena creato
pooledObjects.Add(obj); // Aggiunge l'oggetto alla lista di oggetti poolati
}
}
// Metodo per ottenere un oggetto poolato disponibile
public GameObject GetPooledObject()
{
// Loop attraverso gli oggetti poolati
for (int i = 0; i < pooledObjects.Count; i++)
{
// Verifica se l'oggetto non è attivo nell'attuale gerarchia
if (!pooledObjects[i].activeInHierarchy)
{
return pooledObjects[i]; // Se trovato, restituisci l'oggetto
}
}
return null; // Se non ci sono oggetti disponibili, restituisci null
}
}
Utilizzo del pooling
Vediamo ora come sfruttare il pooling manager per “spawnare”1 oggetti ogni volta che vogliamo:
public class ObjectSpawner : MonoBehaviour
{
public float spawnRate = 1f; // Frequenza di spawn degli oggetti
public float despawnTime = 5f; // Tempo prima che gli oggetti spawnati vengano disattivati
void Start()
{
StartCoroutine(SpawnObjects()); // Avvia la coroutine per lo spawn degli oggetti
}
// Coroutine per lo spawn degli oggetti
IEnumerator SpawnObjects()
{
while (true) // Loop infinito per continuare lo spawn degli oggetti
{
yield return new WaitForSeconds(spawnRate); // Attendere per la durata della frequenza di spawn
GameObject obj = ObjectPoolingManager.instance.GetPooledObject(); // Ottiene il primo oggetto disponibile (non ancora attivato) dal pooling
if (obj != null) // Se è stato ottenuto un oggetto valido
{
obj.transform.position = transform.position; // Imposta la posizione dell'oggetto sulla posizione dello spawn
obj.SetActive(true); // Attiva l'oggetto
StartCoroutine(DeactivateObject(obj)); // Avvia la coroutine per disattivare l'oggetto dopo un certo periodo di tempo
}
}
}
// Coroutine per disattivare un oggetto dopo un certo periodo di tempo
IEnumerator DeactivateObject(GameObject obj)
{
yield return new WaitForSeconds(despawnTime); // Attendere per il tempo di disattivazione
obj.SetActive(false); // Disattiva l'oggetto
}
}
In sintesi, l’algoritmo di pooling permette di:
- ridurre l’utilizzo della memoria: evita il “Instantiate’n’Destroy” continuo di oggetti in gioco, risparmiando risorse di memoria;
- migliorare le prestazioni e la stabilità: elimina il ritardo causato dalla creazione dinamica degli oggetti durante il gioco.
Esempio di Pooling in Unity
Quando si parla di “spawnare” un oggetto in un gioco, si intende il processo di istanziazione di quell’oggetto all’interno del mondo virtuale del gioco. ↩︎