unity3d
Pooling di oggetti
Ricerca…
Pool di oggetti
A volte quando realizzi un gioco devi creare e distruggere molti oggetti dello stesso tipo più e più volte. Puoi farlo semplicemente creando un prefabbricato e istanziato / distruggi questo quando ne hai bisogno, tuttavia, farlo è inefficiente e può rallentare il tuo gioco.
Un modo per aggirare questo problema è il pool di oggetti. Fondamentalmente ciò significa che hai una piscina (con o senza un limite per la quantità) di oggetti che dovrai riutilizzare ogni volta che puoi per evitare un'inutile istanziazione o distruzione.
Di seguito è riportato un esempio di un pool di oggetti semplice
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;
}
}
Passiamo prima alle variabili
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
-
GameObject prefab
: questo è il prefabbricato che il pool di oggetti utilizzerà per istanziare nuovi oggetti nel pool. -
int amount
: questa è la quantità massima di articoli che possono essere nel pool. Se si desidera creare un'istanza di un altro elemento e il pool ha già raggiunto il limite, verrà utilizzato un altro elemento del pool. -
bool populateOnStart
: puoi scegliere di popolare il pool all'avvio oppure no. Così facendo riempirai il pool con istanze del prefabbricato in modo che la prima volta che chiamiInstantiate
otterrai un oggetto già esistente -
bool growOverAmount
: l'impostazione su true consente al pool di crescere ogni volta che l'importo è richiesto in un determinato intervallo di tempo. Non si è sempre in grado di prevedere con precisione la quantità di articoli da inserire nella propria piscina, in modo tale da aggiungere di più alla propria piscina quando necessario. -
List<GameObject> pool
: questo è il pool, il luogo in cui sono memorizzati tutti gli oggetti istanziati / distrutti.
Ora diamo un'occhiata alla funzione Start
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);
}
}
}
Nella funzione di avvio controlliamo se dovremmo compilare la lista all'avvio e farlo se il prefab
è stato impostato e la quantità è maggiore di 0 (altrimenti la creazione sarebbe indefinita).
Questo è solo un semplice ciclo per istanziare nuovi oggetti e metterli in piscina. Una cosa a cui prestare attenzione è che impostiamo tutte le istanze su inattive. In questo modo non sono ancora visibili nel gioco.
Successivamente, c'è la funzione di Instantiate
, che è dove la maggior parte della magia accade
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;
}
La funzione Instantiate
la stessa funzione di Instantiate
Unity, ad eccezione del fatto che il prefabbricato è già stato fornito sopra come membro della classe.
Il primo passo della funzione Instantiate
è controllare se c'è un oggetto inattivo nel pool in questo momento. Ciò significa che possiamo riutilizzare quell'oggetto e restituirlo al richiedente. Se è presente un oggetto inattivo nel pool, impostiamo la posizione e la rotazione, impostandolo come attivo (altrimenti potrebbe essere riutilizzato per errore se si dimentica di attivarlo) e restituirlo al richiedente.
Il secondo passaggio avviene solo se non ci sono elementi inattivi nel pool e il pool può crescere oltre l'importo iniziale. Quello che succede è semplice: un'altra istanza del prefabbricato viene creata e aggiunta al pool. Permettere la crescita della piscina ti aiuta ad avere la giusta quantità di oggetti nella piscina.
Il terzo "passo" si verifica solo se non ci sono elementi inattivi nel pool e il pool non è autorizzato a crescere. Quando ciò accade, il richiedente riceverà un GameObject null, il che significa che non è disponibile nulla e che dovrebbe essere gestito correttamente per prevenire NullReferenceExceptions
.
Importante!
Per assicurarsi che le voci tornare in piscina non si deve distruggere gli oggetti di gioco. L'unica cosa che devi fare è impostarli su inattivo e questo li renderà disponibili per il riutilizzo attraverso il pool.
Pool di oggetti semplice
Di seguito è riportato un esempio di un pool di oggetti che consente di affittare e restituire un determinato tipo di oggetto. Per creare il pool di oggetti è necessario utilizzare Func per la funzione di creazione e un'azione per distruggere l'oggetto per garantire all'utente flessibilità. Alla richiesta di un oggetto quando il pool è vuoto verrà creato un nuovo oggetto e su richiesta quando il pool ha oggetti, gli oggetti verranno rimossi dal pool e restituiti.
Pool di oggetti
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;
}
}
}
Esempio di utilizzo
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.
}
}
Un altro pool di oggetti semplice
Un altro esempio: un'arma che spara proiettili.
L'arma funge da pool di oggetti per i proiettili che crea.
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);
}
}