Gestión de datos y evitando usar Singletons

Durante el desarrollo de tu juego con Unity es muy probable que te encuentres con la duda de: “¿Cómo puedo hacer que los datos de una escena pasen a otra escena?”, es decir, como hago para que una variable de un MonoBehaviour, que reside en una escena, se pueda utilizar en otra escena distinta.

Un ejemplo práctico sería un juego donde hay una escena de selección de personajes y por otro lado una escena distinta donde se desarrolla el juego y donde el personaje que se utiliza es el escogido en la escena anterior.

En nuestro juego MathLand tuvimos una situación parecida, tenemos una pantalla de selección de nivel, en donde el jugador selecciona el nivel que va a jugar en una escena distinta. 

Los niveles de MathLand se generan en tiempo de ejecución, por lo que todos los niveles de MathLand utilizan una única escena. Al llegar a esta escena, necesitamos saber qué nivel se ha escogido.

Vamos a utilizar este caso de ejemplo. En la escena LevelSelection, el jugador escoge el nivel con id = 2 y esta selección se guarda en un script llamado LevelSelectorManager. Luego en la escena “Juego”, se tiene que generar el mapa con id = 2.

Hay muchas formas de resolver este problema, la más común es utilizar el patrón de diseño Singleton. 

No vamos a explicar que es ni como hacerlo, pero sí nos interesa que es lo que hace. Nos permite conseguir que, una vez iniciada la escena donde reside un GameObject, este mantenga su presencia entre todas las escenas, y que, si se encuentra un GameObject del mismo tipo, lo destruya y no se utilice este último.

Esto nos permite que desde la escena de juego, en el start del un script que genera mapas, podamos acceder a la variable que indica qué mapa se ha escogido. Algo como:

<code>

void Start()

{

CreateLevelWithID(LevelSelectorManager.Instance.selectedMapID);

}

void CreateLevelWithID(int mapID)

{

//….

}

</code>

Uno de los problemas más molestos de este método es que nos obliga a realizar nuestros tests siempre empezando desde la escena que reside el GameObject que tiene el script LevelSelectorManager.cs. 

Si le damos al botón de Play, al no haber una instancia del script en cuestión, dará un fallo y no se cargará el nivel.

Una posible solución a esto es tener también un GameObject con este script en la escena de juego, y dejar que el patrón singleton se encargue. Esto puede funcionar para Managers muy autocontenidos en los cuales no haya variables que estén “conectadas” a objetos de una escena en particular, pero si tiene variables de diseño ajustables, podemos caer en el problema de modificarlas en una escena y no en la otra.

Para resolver estos problemas vamos a utilizar ScriptablesObjects (SO).

Los SO se pueden utilizar para muchísimas cosas, pero ahora solo nos centraremos en cómo usarlos para pasar datos de una escena a otra.

Al igual que cualquier archivo que tienes en tu Ventana de Assets (Audios, Texturas, etc.), un SO es un archivo que contiene la información que tu le indiques que quieres que guarde: int, float, strings, etc.. Además, al igual que un archivo, los SO pueden ser arrastrados a una variable pública en el Inspector para obtener su referencia.

Al crear una instancia de un SO, se genera un archivo en tu ventana Assets que representa esta instancia. Debido a que los SO actúan como archivos, cualquier cambio al valor de las variables será definitivo, es decir, no revertirá su valor en cambios de escenas, al entrar y salir de Play o incluso al cerrar y abrir Unity.

Debido a esta persistencia y a la facilidad para obtener una referencia del SO, podemos utilizar el SO para guardar el id del nivel seleccionado.

Para crear una SO tienes que crear un nuevo script, hacer que herede de ScriptableObject (no de MonoBehaviour) y añadirle un atributo a la clase (CreateAssetMenu) para que puedas crearlo como un archivo en tu ventana de Assets. 

<code>

using UnityEngine;

[CreateAssetMenu(fileName = «GameStatus», menuName = «Didactoons/GameStatusSO»)]

public class GameStatusSO : ScriptableObject

{

public int selectedLevelID;

}

</code>

Nota: No hace falta que tenga el sufijo SO, se lo añado para identificar de forma más rápida los SO.

Habiendo creado este script, puedes crear una instancia de este SO yendo a Assets > Create > Didactoons > GameStatusSO

Por último, solo quedaría obtener una referencia a la instancia de este SO en nuestro LevelSelectorManager, y por otro lado, colocar la misma instancia como referencia en el script que genera los mapas. Quedando así:

<code>

public GameStatusSO gameStatus;

void Start()

{

CreateLevelWithID(gameStatus.selectedMapID);

}

void CreateLevelWithID(int mapID)

{

//….

}

</code>

Si aplicas este método para pasar información entre escena, verás que te facilitará la vida. Tendrás tus escenas más limpias y ahorras muchísimo tiempo a la hora de testear tu juego. Para probar un mapa nuevo solo tienes que cambiar el id del nivel y darle al botón Play en la escena de Juego, no tienes que irte hasta la escena anterior para que se cargue el Singleton.

julio 31, 2020