No se han encontrado widgets en la barra lateral

Un spawner no es nada más que un objeto que se dedica a crear otros objetos. Puede ser un dispensador de enemigos, de nubecitas, de cualquier cosa que necesitemos que sea generada de forma más o menos aleatoria, pero que sea continua.

Pongamos por ejemplo el típico endless runner, donde tenemos que esquivar enemigos y recoger premios, pues estos premios y enemigos son candidatos a ser spawneados.

Estructura básica de un spawner.

No es nada más que un objeto vació, que situamos donde queremos que se inicien los objetos a spawnear. Toda la magia pasa en un script, así que lo mas importante es cómo codificamos este script.

¿Qué características tendrá nuestro spawner?

  • Permitirá que le indiquemos los objetos a spawnear. No tienen por que ser todos iguales, así que crearemos un array que contenga los diferentes tipos de objetos a lanzar.
  • Podemos dar un tiempo a esperar entre objeto y objeto.
  • Nos dejará indicar una variación aleatoria del tiempo que pasa entre spawn. Para que el juego gane en variedad siempre va bien introducir algún random.
  • Podremos hacer que el ritmo de spawn aumente o disminuya con el tiempo. Es una forma sencilla de ir haciendo que el juego se complique con el tiempo. Aumentando los enemigos y reduciendo los bonus.
  • Se le podrá indicar un número máximo de objetos a spawnear. Para dar respuesta a juegos con la mecánica de: coge el maximo que puedas de las diez monedas que van a caer.

Como podéis ver las opciones son varias, y responden a la necesidad de tener un spawner que pueda servir para varios juegos. Yo por ahora lo he usado en tres juegos diferentes. En uno de ellos me lanza unas bolas que acaban con mi fantasma, y fantasmitas pequeños que debo salvar. En otro diferentes figuras geométricas, y en un tercero lo he utilizado para modificar las paredes t rocas de una caverna, ya lo que se spawnea son variaciones de los muros.

Podéis encontrar el segundo ejemplo en diferentes webs, o en la misma google playstore (os dejo los links al final del post).

Pero realmente hay una opción que esta por sobre de todas las otras:

Nuestro spawner podrá funcionar con objetos cacheados!!!! Es decir, no vamos a crear una instancia nueva de cada objeto, o sí. El spawner aguanta los dos métodos. La mayoría, por no decir todos los tutoriales de creación de spawners que he visto lo que hacen es un Instantiate por cada nuevo Item que se quiere lanzar. No está mal, para según qué tipo de spawn, pero cargan mucho. De sobras es conocido que el crear un nuevo objeto no es lo mas optimo, y si nuestro se juego se basa en la creación de varios objetos por minuto, o por segundo…. tendremos una clara falta de optimización.

Pero primero veremos como crear nuestro spawner usando el típico método de instanciar cada objeto, y después, vemos cómo modificarlo para que pueda usar objetos cacheados.

Creamos nuestro script de spawner para Unity, usando Instantiate.

Destripando el script de nuestro spawner.

Veamos que variables hemos definido como públicas, o ,con la etiqueta [serializefield] para que puedan ser editadas i/o informadas desde el editor de Unity.

//Elementos (tipo) a spawnear
	public GameObject[] floatingItemPatterns;
    /*
 * g-782490  
 * _timeBetweenItems: tiempo que transcurre entre objetos a lanzar
 * _randomTimeIncrease: nos pemite indicar si queremos que aumente la velocidad de spawn
 * _increaseSpawn:nos pemite indicar si queremos que aumente la velocidad de spawn
 * _minSpawnTime: De aqui no psamos, no se reduce el tiempo de spawn. 
 * _maxNumItems: Podemos indicar un número máximo de items a lanzar.    
 */
    [SerializeField]
    private float _timeBetweenItems = 5, _randomTimeIncrease = 0,
    _increaseSpawn = 0, _minSpawnTime = 0.65f, _maxNumItems = 0;

Muy sencillo. El array, floatingItemPatterns, va a contener los tipos de objeto a spawnear, es decir los prefabs. No es necesario que tenga más de un tipo de objeto diferente, pero no tenemos que cerrarnos a la posibilidad, es más yo es una de las características que suelo usar.

Después encontramos las diferentes variables que nos van a permitir cambiar el comportamiento del spawn. Le podremos indicar el tiempo que pasa entre lanzamientos (_timeBetweenItems), si queremos que este tiempo se vea incrementado por un valor random inferior al que indiquemos en la variable _randomTimeIncreases. La variable _increseSpawn lo que hace es aumentar la velocidad de spawneo cada vez que se realiza un spawn. Es decir si le damos un valor de 0.02, reducirá 0.02 segundos el tiempo de espera antes del siguiente lanzamiento. Con lo que se consigue un aumento de la velocidad de spawneo incremental. Ideal para aumentar la dificultad a medida que el player pasa rato jugando. Para que esta velocidad no acabe siendo totalmente insufrible tenemos la variable _minSpawnTime, que en ningún caso dejará que dos objetos se creen si no ha pasado el tiempo que indicamos en la variable. También podemos poner un número máximo de items a lanzar en la variable _maxNumItems.

Veamos ahora las variables privadas, internas pero necesarias para el funcionamiento del script y la función Awake, que es la que se ejecuta cuando se crea el script.

    private int _rnd = 0; //Elemento a spawnear random entre los del array. 
	private int _lengthFP; //Longitud del array de elementos
	private float _timeWaitNew = 1;
	private float _itemsSpawned = 0; //Elementos spawneados. 
	private void Awake()
	{
        //Calculamos el tiempo de lanzamiento. 
		_timeWaitNew = _timeBetweenItems + Random.Range(0, _randomTimeIncrease); 
        //Calculamos longitud del array de items que podemos lanzar
        _lengthFP = floatingItemPatterns.Length;

	}

Como podemos ver la función es muy sencilla, tan solo calcula cuando va a ser el primer spawneo, sumando a _timeBetweenItems un valor random de como maximo _randomTimeIncrease. Si no queremos que se produzca este incremento rando, tan solo deberemos informar un 0 como valor de _randomTimeIncrease.

Ta es hora de ver donde pasa toda la magia… la función Update! Que recordemos que es la que se ejecuta en cada frame.

// Update is called once per frame
	void Update () {

        if (_timeWaitNew <= 0 && (_maxNumItems == 0 || _maxNumItems > _itemsSpawned))
        {
            lanza();

            if (_increaseSpawn >= 0 && _timeBetweenItems > _minSpawnTime)
            {
                _timeBetweenItems -= _increaseSpawn;
            }
			_timeWaitNew = _timeBetweenItems;
            if (_randomTimeIncrease > 0f)
            {
                _timeWaitNew += Random.Range(0, _randomTimeIncrease);
            }
            if (_timeWaitNew < _minSpawnTime)
            {
                _timeWaitNew = _minSpawnTime;
            }
			_itemsSpawned += 1;
		}
		else
		{
			_timeWaitNew -= Time.deltaTime;
		}
		
	}

Para lanzar un nuevo objeto, se tienen que dar dos condiciones, como vemos en el primer If. el tiempo de espera tiene que ser menor o igual a 0, y el número máximo de objetos a spawnear tiene que ser 0, o en el caso de que no sea cero, tiene que ser mayor que el número de objetos spawneados. Si se cumple estas dos condiciones llamamos a la función Lanza, que será la responsable de lanzar los nuevos objetos.

Un vez lanzado el objeto tenemos que volver a informar la variable _timeWaitNew con el tiempo que tiene que esperar a lanzar otro objeto. Pero este tiempo, recordemos puede ser modificado de dos formas, un número aleatorio o un decremento en cada iteración. De aquí los if’s que modifican el calculo de la variable _timeWaitNew. Finalmente incrementamos en 1 el número de items lanzados.

Veamos el script de Spawn Entero:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spawner : MonoBehaviour {
    
	//Elementos (tipo) a spawnear
	public GameObject[] floatingItemPatterns;
    /*
 * g-782490  
 * _timeBetweenItems: tiempo que transcurre entre objetos a lanzar
 * _randomTimeIncrease: nos pemite indicar si queremos que aumente la velocidad de spawn
 * _increaseSpawn:nos pemite indicar si queremos que aumente la velocidad de spawn
 * _minSpawnTime: De aqui no psamos, no se reduce el tiempo de spawn. 
 * _maxNumItems: Podemos indicar un número máximo de items a lanzar.    
 */
    [SerializeField]
    private float _timeBetweenItems = 5, _randomTimeIncrease = 0,
    _increaseSpawn = 0, _minSpawnTime = 0.65f, _maxNumItems = 0;
    [SerializeField]
    private int _poolObjects = 0; //Cuantos objetos creamos previamente para tener cacheados

    private int _rnd = 0; //Elemento a spawnear random entre los del array. 
	private int _lengthFP; //Longitud del array de elementos
	private float _timeWaitNew = 1;
	private float _itemsSpawned = 0; //Elementos spawneados. 
	private void Awake()
	{
        //Calculamos el tiempo de lanzamiento. 
		_timeWaitNew = _timeBetweenItems + Random.Range(0, _randomTimeIncrease); 
        //Calculamos longitud del array de items que podemos lanzar
        _lengthFP = floatingItemPatterns.Length;
	}

	void Update () {
		if (!GameManager._gm._paused)
		{
            if (_timeWaitNew <= 0 && (_maxNumItems == 0 || _maxNumItems > _itemsSpawned))
            {
                lanza();

                if (_increaseSpawn >= 0 && _timeBetweenItems > _minSpawnTime)
                {
                    _timeBetweenItems -= _increaseSpawn;
                }
				_timeWaitNew = _timeBetweenItems;
                if (_randomTimeIncrease > 0f)
                {
                    _timeWaitNew += Random.Range(0, _randomTimeIncrease);
                }
                if (_timeWaitNew < _minSpawnTime)
                {
                    _timeWaitNew = _minSpawnTime;
                }
				_itemsSpawned += 1;
			}
			else
			{
				_timeWaitNew -= Time.deltaTime;
			}
		}
	}
    
	void lanza(){
		
		_rnd = Random.Range(0, _lengthFP);			
		Instantiate(floatingItemPatterns[_rnd], transform.position, Quaternion.identity);
	
	}
}

Como veis, la función lanza es muy sencilla, decide, de forma random cuales de los objetos del array (que nosotros hemos informado en el editor) lanzar y crea una instancia de ese objeto.

Modificamos el script para que pueda usar un pool de objetos cacheados.

Bueno, vamos a modificar nuestro script, va a ser mas sencillo de lo que parece, pero al mismo tiempo, crearemos también un script que vamos a tener que incorporar a los objetos que se lancen. ¿Por qué? Pues porque prefiero que cada objeto pueda tener su propio comportamiento, y se lo definiremos a el. Nuestro spawner se ocupará tan solo de usar los que estan disponibles. Es el objeto el que debe saber cuando desactivarse y volver a ponerse disponible.

Vemos primero el script que llevaran nuestros objetos lanzados o creados por el spawner.

public class CleanMemory : MonoBehaviour {

	[SerializeField]
	private float destroyAfter = 2.0f;
	public bool _bPartOfPool = false; 
	private Vector3 _posInicial; 

	private void Start()
	{
		if (_bPartOfPool )
		{
			_posInicial = transform.position;
			StartCoroutine(returnPositionOriginal(destroyAfter));
		}
		else
		{
			Destroy(gameObject, destroyAfter);
		}
	}

	public void DestroyObject(){
		if (_bPartOfPool && destroyAfter > 0)
		{
			gameObject.transform.position = _posInicial;
            gameObject.SetActive(false);
		}
		else
		{
			Destroy(gameObject);
		}
	}

	IEnumerator returnPositionOriginal(float pTimeWait)
    {
        yield return new WaitForSeconds(pTimeWait);
        gameObject.transform.position = _posInicial;
		gameObject.SetActive(false);
    }

}

Como podéis ver este script lo que hace es destruir nuestro objeto después de los segundos que le indiquemos. Al mismo tiempo nos da la posibilidad de decirle, mediante la variable _bPartOfPoool, si es un objeto que puede pertenecer a algún pool, es decir, si debe ser reutilizado. En este caso, en la función Start se guarda la posición en la que se crea y se programa la llamada a la función returnPositionOriginal, donde el objeto vuelve a su posición y se desactiva, esperando que el spawner vuelva a activarlo.

La función DestroyObject, también modifica su comportamiento dependiendo si el objeto forma parte, o no, de un pool de objetos reutilizables. Si no forma parte se destruye, y si forma parte de un pool, pues se desactiva y vuelve a su posición original.

Resumen.

Ya lo tenemos!!!! Esto es todo. Vemos los que hemos creado:

  • Un objeto vacío. Serà el spawner.
  • Un script que lanzara los objetos. Este script se lo asignamos al objeto vacío.
  • Un script para los objetos lanzados!

Nada más, un par de scripts y ya podemos empezar a hacer juegos que se basen en recoger o esquivar objetos como locos. O bueno, casi cualquier juego necesita un spawner.

Vídeo disponible:

Guía para iniciarse en el desarrollo de juegos con Unity.

El mundo del desarrollo de videojuegos es muy complicado, no es sencillo, requiere de mucho esfuerzo y de dar un Read more

Contador de puntos para Unity.

Casi todos los juegos tienen algún tipo de puntuación, pero los Arcades: todos! Si estamos haciendo un Arcade tenemos que Read more

¿Donde publicar tu juego para obtener feedback y/o monetizarlo?

Análisis de las diferentes opciones que tenemos para publicar nuestros juegos en la WEB.

Configurando el IDE de Unity e importando Assets para nuestro primer proyecto.

Ahora que ya tenemos instalado Unity, vamos a crear un pequeño proyecto. Aprovecharemos para conocer un poco el IDE, estructurarlo Read more

Por Martra

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *