miércoles, 12 de marzo de 2014

Unity3D Scripting: Object Pool

Una vez mas volvemos con un tutorial breve y sencillo de Unity3D, realmente de aquí podemos sacar mucho, muchísimo código, pero quiero dar un ejemplo sencillo para optimizar recursos dentro de nuestro videojuego y que realmente tiene mucha mas ciencia que esto pero servirá a mas de una persona o al menos ayudará a entenderlo.

¿Que es un Object Pool?
Object Pool (Piscina de objetos) es un patrón de diseño para el desarrollo de software. El patrón object pool es un patrón de diseño de software que usa un conjnto de objetos inicializados preparados para su uso. Esto es más efectivo normalmente que creando y destruyento los objetos bajo demanda. Un cliente del pool le pedirá un objeto para realizar las operaciones con el objeto. Cuando el cliente termina retorna el objeto al pool para que lo retenga hasta que vuelva a necesitar usarse. Es decir, los objetos no se crean (salvo la primera vez) ni se destruyen, simplemente se van reciclando.
¿Para que sirve?
El ejemplo mas claro es en un videojuego que haya disparos, imagina un videojuego de naves, cada disparo sería una instancia de una bala nueva, pues en vez de estar constantemente instanciando balas nuevas, las tienes inactivas fuera de la pantalla y las reciclas una y otra vez.
¿Por qué?
Porque es mas rápido acceder a un contenido que ya está cargado en memoria que buscar un sítio en la memoria donde almacenarlo, almacenarlo en ese sitio y luego leerlo.
Dicho todo esto vamos con una pequeña muestra de lo que podríamos hacer.

Primero he creado esta estructura de objetos para hacer la prueba:

Objetos de prueba
 Una vez creada la estructura empezamos con el código:

using UnityEngine;
using System.Collections;

public class PoolManager : MonoBehaviour {
 public bool NewShoot(GameObject shooter, string nameOfType)
 {
  Transform t = transform.FindChild(nameOfType);

  for (int i = 0;i<t.childCount;i++)
  {
   if(!t.GetChild(i).gameObject.activeSelf)
   {
    t.GetChild(i).gameObject.SetActive(true);
    return true;
   }
  }
  return false;
 }

 public void NewBullet(string nameOfType)
 {
  Transform t = transform.FindChild(nameOfType);

  GameObject g = Instantiate(t.GetChild(0).gameObject) as GameObject;
  g.transform.parent = t;
 }
}
Bien, este script para probarlo se necesita hacer llamada a sus métodos públicos. voy a explicar ambos paso por paso:

public bool NewShoot(GameObject shooter, string nameOfType)
En este caso estamos haciendo un nuevo disparo, le decimos que objeto es el que está disparando y el tipo de bala que va a disparar, ya que en este "Object Pool" he creado mis propias categorías que ahora enseñaré.

Se le podría estar pasando por ejemplo el objeto nave y el tipo de bala "normal".

Transform t = transform.FindChild(nameOfType);
esta variable 't' se le asignará al típo de bala que hemos buscado, de manera que será pues bala de tipo normal, o de tipo especial o como la hayamos nombrado.

for (int i = 0;i<t.childCount;i++)
Aquí vamos a recorrer todas las balas que tengamos de ese tipo

if(!t.GetChild(i).gameObject.activeSelf)
Comprobamos que la bala NO esté activa, de ser así pues cumplirá la condición y se ejecutarán las líneas siguientes.

t.GetChild(i).gameObject.SetActive(true);
return true;
Activará la bala que ha encontrado inactiva y retornará true avisando a quien ha llamado a esta función que efectivamente ha podido habilitar una bala y dejará de buscar en ese bucle.

¿Que pasa si nos quedamos sin balas que activar?
En el hipotético caso de que no sepamos cuantas balas como máximo va a poder usar esa nave o ese pistolero o cualquier objeto instanciable no pasa nada, seguímos con el código.


return false;
En caso de que no haya encontrado ninguna retornará false y aquí tenemos esta otra función para estos casos.

public void NewBullet(string nameOfType)
Un método para crear una nueva bala del típo que le digamos en ese string llamado nameOfType.

Transform t = transform.FindChild(nameOfType);
Buscamos de nuevo el objeto del tipo que queremos crear la bala

GameObject g = Instantiate(t.GetChild(0).gameObject) as GameObject;
Instanciamos una bala nueva, esta bala o cualquier objeto que instanciemos será instanciada en la raíz de la lista de objetos, pero nosotros, para usar este gestor de objetos deberíamos de conservar la jerarquía que hemos visto en la imagen de arriba así que para asignarle un "padre" a este nuevo objeto instanciado usaremos lo siguiente

g.transform.parent = t;
Y así este objeto quedará guardado en nuestra jerarquía y tendremos una bala mas a la que acceder en caso de necesitar mas.

Evidentemente esto es aplicable a cualquier cosa que tengamos que generar sobre la marcha y vayamos a usarlo reiteradas veces, y por supuesto es aplicable a cualquier videojuego e incluso cualquier software en general.

Si, este código tiene bastantes carencias pero lo hice en una tarde para hacerme el apaño, quizás algún día lo mejore y publique aquí algunas mejoras.

Por último, espero que haya servido de ejemplo para entenderlo un poco mejor y si quieres uno realmente bastante mas completo hay varios por internet, algunos de pago y otros gratuitos que realmente los he visto bastante decentes.

No hay comentarios:

Publicar un comentario