Patrón de diseño Object Pooling en Unity
Tags: videogame, unity, csharp
Published: 2019-09-19 06:12:32

---
title: Patrón de diseño Object Pooling en Unity
published: true
description: Este patrón de diseño nos permitirá mejorar el performance de nuestro juego en el caso que necesitemos reutilizar un objeto especifico.
tags: videogame, unity, c-sharp
---

##¿Qué es el Object Pooling?

Imagina cualquier videojuego del genero shooter, ya sea en primera persona o tercera persona; hay un objeto que tienen en común tanto el jugador como los enemigos, que es su armamento, específicamente el uso de __balas__ para las diferentes armas. Hay varias maneras de plantear el disparo en este genero, y en __Unity__ se puede hacer con _raycast_ o spawnear un objeto, a manera de _prefab_, y agregarle una fuerza y una dirección.

Ahora, si optamos por utilizar el crear objetos que tendrán el comportamiento de la bala, lo más normal es que cuando se presione el botón de disparo tengamos un método para crear una bala, esto a través del método _Instantiate_ que nos provee unity, al cual deberemos asignar que objeto se quiere crear, asignar una posición y rotación.


```csharp
public GameObject bulletPrefab; //El objeto bala que se va a crear

public Transform spawnPoint;    //El lugar donde saldrá la bala, cañón de arma

//Este método se manda llamar en cada frame
public void Update()
{
     if(Input.GetMouseButtonDown(0))
     {
        Instantiate(bulletPrefab, spawnPoint.position, spawnPoint.rotation);
     }
}
```


Hay que dejar en claro que este método _Instantiate()_ nos va a estar creando un __nuevo objeto__ cada vez que lo mandemos llamar, en este caso cuando el jugador presione el botón izquierdo del mouse; haciendo los ajustes necesarios, los enemigos, IA, tendrían algo similar para cuando ellos disparen al jugador, lo cual nos va a __generar muchos objetos en escena__, que vendrían a ser los objetos del tipo bala, que estarán consumiendo memoria y más cuando no determinemos en que momento esa bala no la necesitemos en tiempo de ejecución, esto puede ocurrir en dos escenarios, la bala impacta sobre un objeto que puede recibir daño, sea jugador, enemigos, barriles explosivos, etc., o impactar sobre una pared, un carro o alguna parte del decorado del nivel, y lo que se suele hacer es lo siguiente:


```csharp
//script que se encuentra en el objeto bala

void OnCollisionEnter(Collision col)
{
    if(col.gameObject.CompareTag("Enemy"))
    {
       Destroy(gameObject, 0f); //Método que destruye a la bala
    } 
}
```


Como se muestra en el código de arriba, en cuanto la bala detecte una colisión y se cumpla la condición se destruirá, el método _Destroy()_ quitará de memoria el objeto que reciba como parámetro, hacer esto un par de veces no afecta; en nuestro ejemplo del shooter digamos que nos encontráramos con 10 enemigos cada uno tendrá sus propias balas que pueden tener de referencia el mismo prefab del tipo bala, cuando cada enemigo dispare mandara llamar el método _Instantiate()_ y a su vez el jugador también lo hará para responder al ataque, entonces estamos hablando de que 11 objetos (10 enemigos y 1 jugador) estarán creando nuevos objetos del tipo bala cada X segundos dependiendo de la velocidad de disparo de cada quien, haciendo crecer la cantidad de objetos que se muestran en escena y cada uno tendrá su propio comportamiento y su propia detección de colisión para destruirse cuando llegue el momento. Ya podemos empezar a imaginar la enorme cantidad de procesos que se estarán llevando, ya que el _Instantiate()_ y el _Destroy_ tiene un costo relativamente elevado y al hacerlo X cantidad de veces de de forma rápida puede causar estragos.

Es aquí donde el object pool puede ayudarnos a mejorar el rendimiento de nuestro juego, la idea es tener una "alberca" de un determinado tipo de objetos con una cantidad finita de cuantos existirán desde que inicia el juego, se basa que en vez de __destruir y crear__ objetos, los vamos a estar reutilizando y cuando no los necesitemos los mandaremos a una alberca.


##Un patrón de diseño muy fácil de implementar

Vamos a empezar por definir una clase que estará controlando la alberca de objetos:

```csharp
public class PoolManagerObjects : MonoBehaviour
{
   public GameObject objPrefab;  //El objeto que vamos a estar reutilizando
   
   public int poolSize;          //Cuantos objetos se necesitaran

   private Queue<GameObject> objPool; //La "alberca" donde estarán los objetos

}
```

Utilizamos una cola, __Queue__, ya que su funcionamiento de primero en entrar, primero en salir viene perfecto para este patrón de diseño, aquí va estar la referencia a los objetos que podremos estar reutilizando en cuanto lo necesitemos.

Inicializamos la alberca:


```csharp
public class PoolManagerObjects : MonoBehaviour
{
   public GameObject objPrefab;  //El objeto que vamos a estar reutilizando
   
   public int poolSize;          //Cuantos objetos se necesitaran

   private Queue<GameObject> objPool; //La "alberca" donde estarán los objetos

   void Start()
   {
     objPool = new Queue<GameObject>();  //Inicializamos la cola
     
     for (int i = 0; i < poolSize; i++) //Vamos a llenar la alberca en base al tamaño
     {
         //Instanciamos el objeto y lo guardamos en una varible temporal    
         GameObject newObj = Instantiate(objPrefab);  
         objPool.Enqueue(newObj);   //Lo añadimos a la cola con Enqueue
         newObj .SetActive(false);    //Lo desactivamos ya que en ese momento no se requiere
     }
   }
}
```


Bien, haciendo uso del ciclo for, llenaremos la alberca con una determinada cantidad de objetos en base a la variable _poolSize_. Es verdad que estamos haciendo uso del _Instantiate()_ pero solo se va a realizar una cantidad finita de veces y al inicio del juego, razón de que esto pase en el método _Start()_.

Ahora, vamos a crear dos métodos que vendrán a sustituir el método _Instantiate()_ y _Destroy()_.


```csharp
public class PoolManagerObjects : MonoBehaviour
{
   public GameObject objPrefab;  //El objeto que vamos a estar reutilizando
   
   public int poolSize;          //Cuantos objetos se necesitaran

   private Queue<GameObject> objPool; //La "alberca" donde estarán los objetos

   void Start()
   {
     objPool = new Queue<GameObject>();  //Inicializamos la cola
     
     for (int i = 0; i < poolSize; i++) //Vamos a llenar la alberca en base al tamaño
     {
         //Instanciamos el objeto y lo guardamos en una varible temporal    
         GameObject newObj = Instantiate(objPrefab);  
         objPool.Enqueue(newObj);   //Lo añadimos a la cola con Enqueue
         newObj .SetActive(false);    //Lo desactivamos ya que en ese momento no se requiere
     }
   }

   public GameObject GetObjFromPool(Vector3 newPosition, Quaternion newRotation)
   {
        //Se obtiene el 1er objeto disponible en la cola
        GameObject newObj = objPool.Dequeue();
        //Activamos el objeto, se activa su comportamiento
        newObj.SetActive(true);        
        //Le damos la posición y rotación, en donde se necesita que este
        newObj.transform.SetPositionAndRotation(newPosition, newRotation);    

        return newObj;
   }

   public void ReturnObjToPool(GameObject go)
   {
        go.SetActive(false);    //Lo desactivamos
        objPool.Enqueue(go); //Lo volvemos a añadir a la cola para reutilizarlo
   }

}
```


Para terminar, el método __GetObjFromPool()__ sustituye al _Instantiate()_, pero en este caso solo nos interesa asignar la posición y rotación del lugar donde necesitemos el objeto de la alberca, eso si, tendremos que tener acceso a la clase __PoolManagerObjects__ para hacer uso del mismo. Ojo aquí, que el método nos va a retornar el objeto.

Esto ocurre de igual forma con el método __ReturnObjToPool()__ que sustituye al _Destroy()_, recibe de parámetro el objeto que queremos regresar a la alberca, se desactiva y se vuelve a encolar.

Volviendo al ejemplo cuando se presiona el botón del mouse, quedaría de la siguiente manera:

```csharp
public PoolManagerObjects poolManager; //Hay que asignar desde el inspector el objeto que tenga el script PoolManagerObjects 

public Transform spawnPoint;    //El lugar donde saldrá la bala, cañón de arma

//Este método se manda llamar en cada frame
public void Update()
{
     if(Input.GetMouseButtonDown(0))
     {
        //Tenemos una referencia del objeto que tomamos de la alberca, 
       // esto puede ayudarnos para modificar valores de la bala, acceder a sus componentes, etc.
        GameObject theBullet = poolManager.GetObjFromPool(spawnPoint.position, spawnPoint.Rotation);
     }
}
```


Y para el caso cuando ya no necesitemos el objeto:


```csharp
//script que se encuentra en el objeto bala

public PoolManagerObjects poolManager; //Hay que asignar desde el inspector el objeto que tenga el script PoolManagerObjects 

void OnCollisionEnter(Collision col)
{
    if(col.gameObject.CompareTag("Enemy"))
    {
       //En cuanto exista una colisión y se cumpla la condición del if
       // mandaremos llamar el método para regresar la bala a la alberca y la podamos reutilizar
       poolManager.ReturnObjToPool(this.gameObject);
    } 
}
```

Entonces, estamos mejorando bastante el rendimiento de nuestro juego, ya que pasamos de que 11 objetos (enemigos y jugador), estén creando y destruyendo X cantidad de balas, a que todos tengan acceso a una alberca con una cantidad finita de objetos y todos estén utilizando los mismos objetos en cuanto lo requieran.

Se puede mejorar bastante este patrón, añadiendo un par de elementos para que se vuelva más útil y permita utilizar distintas albercas con objetos diferentes cada una.

Espero quede entendible este patrón, cualquier comentario que ayude a mejorar es bienvenido.


__Notas importante__: 
-A la variable poolSize, debemos darle un valor considerable para que no llegue a pasar el error de que se estén utilizando todos los objetos y la alberca este vacía, hay que poner un valor no muy alto pero tampoco tan bajo.