Pattern Object Pool: come ottimizzare le prestazioni di un gioco mobile

Giuseppe Toto
Forgiatore di materia virtuale
4 min readJul 9, 2013

--

unity 3d prototipo

L’idea che c’è dietro il pattern Object Pool è molto semplice: a volte ci si trova a dover creare/distruggere un insieme di oggetti di una determina classe. L’operazione di creazione/distruggere continuamente oggetti potrebbe essere al quanto onerosa rispetto a invece riusare lo stesso oggetto. Questo discorso è ancora più importante se il nostro algoritmo deve girare su dispositivi di bassa potenza come gli smartphone/tablet. Questo discorso in realtà non è sempre valido poiché in altre situazione tenere una serie di oggetti in memoria può essere più costoso. Quindi bisogna saper valutare bene quando utilizzare questa tecnica.

Per fare una analogia: immaginate un burattinaio che deve tenere uno spettacolo. In una scatola ha già tutti i suoi burattini, pronti per essere estratti e usati sul palcoscenico all’occorrenza. Quando poi un burattino deve scomparire dalla scena lo ripone nella scatola.

Per farvi invece un esempio più vicino alla programmazione, vi descrivo un tipico scenario in cui mi sto imbattendo in questi giorni nello sviluppo di un prototipo di videogioco per mobile su Unity 3D:

[iframe src=”http://www.youtube.com/embed/MmfbsUB02Ts" width=”640" height=”360"]

Nel video potete vedere una serie di uccellini che vengono generati sul lato destro dello schermo …per poi essere distrutti quando superano il lato sinistro.

Nel campo dei videogiochi creare/distruggere un oggetto richiede un certo sforzo computazionale per la sua renderizzazione. Infatti istanziare un gameObject Unity significa generare una mesh, una texture, una materiale e così via.Tutti elementi necessari per rappresentare un modello 3d o un personaggio interattivo. Quindi il costo di creazione dell’oggetto, in questo caso, è molto alto. Per tanto a questo punto può essere utile riusare lo stesso oggetto e farlo scomparire quando non serve più per poi riutilizzarlo all’occorrenza.

Vi lascio il codice sorgente in c# di una classe che implementa il pattern Object Pool.

Come usarlo:

  1. Associare lo script ObjectPool.cs ad un gameObject presente sulla scena
  2. Nell’array objectPrefabs inserire tutti i prefabs che volete riutilizzare
  3. Nella variabile amountToBuffer specificate il buffer di oggetti che volete che siano già istanziati al primo avvio dello script (di default è 3). Questo significa che se nella vostra scatola ci saranno già 3 istanze pronte all’uso…se il vostro script avrà utilizzato già tutte le 3 istanze disponibili, alla prossima richiesta verrà genererà una quarta istanza e inserita nella scatola quando poi non sarà più utile allo scopo.
  4. Ogni volta che volete prelevare un’istanza dalla scatola dovete usare la funzione ObjectPool.instance.GetObjectForType(objectType,onlyPooled). La funziona restituisce l’oggetto che volete riusare.
  5. Il primo parametro è una stringa e dovete specificare il nome del prefab che volete recuperare.
  6. il secondo parametro è un booleano che se true restituisce NULL nel caso non ci sono più oggetti da poter prelevare dalla scatola…se false, nel caso non ci sono più istanze disponibile, istanzia una nuovo oggetto e lo restituisce.
  7. quando un oggetto non vi serve più, assicuratevi di inserirlo nuovamente nella scatola con la funzione PoolObject(gameObject)

Il codice e l’articolo è stato preso spunto da questo post (mi sono occupato di correggere un piccolo bug e di adattarlo ad Unity 4)

[code lang=”csharp”]
/* ESEMPIO DI UTILIZZO */
/*….*/
//recupero un oggetto dalla scatola il cui prefab si chiama “Character”.
//Se non disponibile, questo viene istanziato
GameObject obj = ObjectPool.instance.GetObjectForType(“Character”,false);
obj.transform.position=position;
/*….*/
//quando un oggetto non mi serve più lo ripongo nella scatola
ObjectPool.instance.PoolObject(obj);
[/code]
[code lang=”csharp”]
public class ObjectPool : MonoBehaviour

{

public static ObjectPool instance;

///
// The object prefabs which the pool can handle.
///
public GameObject[] objectPrefabs;
///
/// The pooled objects currently available.
///
public List[] pooledObjects;
///
/// The amount of objects of each type to buffer.
///
public int[] amountToBuffer;

public int defaultBufferAmount = 3;

///

/// The container object that we will keep unused pooled objects so we dont clog up the editor with objects.

///
protected GameObject containerObject;
void Awake () {
instance = this;
}

// Use this for initialization

void Start () {

containerObject = new GameObject(“ObjectPool”);

//Loop through the object prefabs and make a new list for each one.

//We do this because the pool can only support prefabs set to it in the editor,

//so we can assume the lists of pooled objects are in the same order as object prefabs in the array

pooledObjects = new List[objectPrefabs.Length];

int i = 0;

foreach ( GameObject objectPrefab in objectPrefabs ){

pooledObjects[i] = new List();
int bufferAmount;
if(i < amountToBuffer.Length)
bufferAmount = amountToBuffer[i];
else
bufferAmount = defaultBufferAmount;
for ( int n=0; n<bufferAmount; n++) {

GameObject newObj = Instantiate(objectPrefab) as GameObject;

newObj.name = objectPrefab.name;
PoolObject(newObj);
}

i++;

}

}

///

/// Gets a new object for the name type provided. If no object type exists or if onlypooled is true and there is no objects of that type in the pool
/// then null will be returned.

///
///

/// The object for type.

///

///

/// Object type.

///

///

/// If true, it will only return an object if there is one currently pooled.

///

public GameObject GetObjectForType ( string objectType , bool onlyPooled ){
for(int i=0; i<objectPrefabs.Length; i++){ GameObject prefab = objectPrefabs[i]; if(prefab.name == objectType){ if(pooledObjects[i].Count > 0){

GameObject pooledObject = pooledObjects[i][0];

pooledObjects[i].RemoveAt(0);

pooledObject.transform.parent = null;

pooledObject.SetActive(true);

return pooledObject;

} else if(!onlyPooled) {
GameObject tmp = Instantiate(objectPrefabs[i]) as GameObject;
tmp.name=objectPrefabs[i].name;
return tmp;

}

break;

}

}

//If we have gotten here either there was no object of the specified type or non were left in the pool with onlyPooled set to true

return null;

}

///

/// Pools the object specified. Will not be pooled if there is no prefab of that type.

///
///

/// Object to be pooled.

///

public void PoolObject ( GameObject obj )

{
for ( int i=0; i<objectPrefabs.Length; i++)
{
if(objectPrefabs[i].name == obj.name)

{
// Debug.Log(obj.name);
obj.SetActive(false);
obj.transform.parent = containerObject.transform;

pooledObjects[i].Add(obj);

return;

}

}

}

[/code]

--

--