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.
Sfida
MedioFuochi 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 unObjectPool<Spark>in Awake. Crea una scintilla istanziandosparkPrefab, 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
Returnrestituisce 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);
}
}
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
1Componenti, non gerarchie
Componenti piccoliUno script base per i nemiciComponenti astrattiComponenti che collaboranoRiepilogo: torretta modulare4Dati serializzati
Classi serializzabiliListe nell'InspectorEnum nell'InspectorRiepilogo: tabella del bottino7Macchine a stati
Gli stati come classiUna macchina a statiTransizioniIntelligenze dei nemiciRiepilogo: stati della porta10Progetto: Sopravvissuto nell’arena
L’eroeTipi di nemici5ScriptableObjects
Asset di datiStatistiche delle armiImpostazioni condiviseOndate come datiRiepilogo: database degli oggetti3Eventi
Eventi C# in UnityIscriviti in OnEnableCampi UnityEventGli eventi guidano l'interfaccia utenteRiepilogo: popup dell'obiettivo6Salvare con JSON
JsonUtilitySalva la partitaCaricamento e valori predefinitiRiepilogo: slot di salvataggio9Pooling degli oggetti
Perché usare un pool di oggettiUn pool con una codaObjectPool di UnityRiepilogo: pool di proiettili