Como Guardar y Cargar el progreso del jugador en Unity
() translation by (you can also view the original English article)



En este tutorial vas a aprender a desarrollar un sistema de creación y gestión de partidas guardadas para tus juegos en Unity. Vamos a construir un sistema con un menú principal que permita al jugador guardar y cargar una única partida. Los conceptos desarrollados te permitirán crear un sistema de guardado que se adapte a tu videojuego.
Al finalices el tutorial, habrás aprendido:
- guardar y cargar datos del juego en Unity3D usando serialización
- utilizar variables estáticas para conservar los datos cuando cambiemos de escena
Nota: Este método para guardar y cargar datos de juego funciona en todas las plataformas excepto en el reproductor Web. Para mas información sobre guardar datos de juego en el reproductor Web, puedes echarle un vistazo a la documentación oficial en Unity Web Player y comunicación con el navegador.
Vamos a Serializar
Lo primero que vamos a hacer es crear un código que permite serializar los datos del juego — es decir, convertirlo a un formato que puede ser salvado y cargado más adelante. Para ello, vamos a crear un script en C# que llamaremos SaveLoad
Este script gestionará de todas las funcionalidades de guardar y cargar.
Referenciaremos este script desde otros scripts, así que vamos a hacer una clase estática mediante la adición de la palabra static
entre public
y class
. Borraremos la parte : MonoBehaviour
, ya que no vamos a usar el script en un GameObject
. Y puesto que ya no hereda de MonoBehaviour
, vamos a eliminar las funciones Start
y Update
.
El código resultante debe tener este aspecto:
1 |
using UnityEngine; |
2 |
using System.Collections; |
3 |
|
4 |
public static class SaveLoad { |
5 |
|
6 |
}
|
Ahora, añadiremos algunas nuevas funcionalidades a este script, justo debajo de donde dice using System.Collections;
añadimos lo siguiente:
1 |
using System.Collections.Generic; |
2 |
using System.Runtime.Serialization.Formatters.Binary; |
3 |
using System.IO; |
La primera línea nos permite utilizar listas dinámicas en C#, pero esto no es necesario para la serialización. La segunda línea es lo que nos permite usar las capacidades de serialización del sistema operativo dentro del script. En la tercera línea, IO
significa Entrada/Salida y es lo que nos permite escribir y leer desde nuestro ordenador o dispositivo móvil. En otras palabras, esta línea nos permite crear archivos y poder leerlos más tarde.
¡ Ahora estamos listos para serializar los datos!
Creando Clases Serializables
Ahora que nuestro script tiene la capacidad de serializar, vamos a tener que configurar algunas clases para que se puedan serializar. Si piensas en un juego de rol básico, como Final Fantasy, ofrece a los jugadores la habilidad de crear y cargar partidas guardadas diferentes. Así que vamos a crear un nuevo script de C# llamado Game
y definir unas variables para guardar tres objetos: un caballero (knight), un pícaro(rogue) y un mago(wizard). Cambia el código del script para parecerse a esto:
1 |
using UnityEngine; |
2 |
using System.Collections; |
3 |
|
4 |
[System.Serializable] |
5 |
public class Game { |
6 |
|
7 |
public static Game current; |
8 |
public Character knight; |
9 |
public Character rogue; |
10 |
public Character wizard; |
11 |
|
12 |
public Game () { |
13 |
knight = new Character(); |
14 |
rogue = new Character(); |
15 |
wizard = new Character(); |
16 |
}
|
17 |
|
18 |
}
|
La línea [System.Serializable]
dice a Unity que este script se puede serializar — en otras palabras, que podemos guardar todas las variables de este script. ¡ Genial! Según la documentación oficial, Unity puede serializar los tipos siguientes:
- Todos los tipos de datos básicos (como
int
,string
,float
,bool
). - Algunos tipos integrados (incluyendo
Vector2
,Vector3
,Vector4
,cuaternión
,Matrix4x4
,Color
,Rect
yLayerMask
). - Todas las clases heredan de
UnityEngine.Object
(incluyendoGameObject
,Component
,MonoBehavior
,Texture2D
yAnimationClip
). - Enums.
- Arrays y listas de un tipo serializable.
La primera variable, current
, es una referencia estática a una instancia de Game
. Cuando guardamos o cargamos un juego, vamos a establecer esta variable estática a esa instancia del juego en particular por lo que podemos hacer referencia a la "partida actual" desde cualquier parte del proyecto. Mediante el uso de funciones y variables estáticas, no tenemos que usar la funcion GetComponent()
de un gameObject
. Práctico!
¿Has visto que se está haciendo referencia a algo llamado Character
? Aún no lo hemos creado, así que vamos a crear un nuevo script que llamaremos Character
para definir esta clase.
1 |
using UnityEngine; |
2 |
using System.Collections; |
3 |
|
4 |
[System.Serializable] |
5 |
public class Character { |
6 |
|
7 |
public string name; |
8 |
|
9 |
public Character () { |
10 |
this.name = ""; |
11 |
}
|
12 |
}
|
Te estarás preguntando por qué necesitábamos una nueva clase si sólo estamos almacenando una variable string. De hecho, sólo tendríamos que reemplazar Character
por string
en el script Game
. Pero queremos mostrar la profundidad de estas estructuras: puedes guardar y cargar las clases que hacen referencia a otras clases y así sucesivamente, siempre que cada clase sea serializable.
Ahora que nuestras clases están configuradas para ser guardadas y cargadas, volvamos a nuestro script SaveLoad
para añadir la posibilidad de guardar partidas.
Guardando el estado del juego
Un menú "Load Game" por lo general muestra una lista de partidas guardadas, así que vamos a crear en savedGames
. una lista List
de tipo Game
Crear una lista estática static List
, de forma que sólo exista una lista de partidas guardadas en nuestro proyecto. El código debería parecerse a esto:
1 |
using UnityEngine; |
2 |
using System.Collections; |
3 |
using System.Collections.Generic; |
4 |
using System.Runtime.Serialization.Formatters.Binary; |
5 |
using System.IO; |
6 |
|
7 |
public static class SaveLoad { |
8 |
|
9 |
public static List<Game> savedGames = new List<Game>(); |
10 |
|
11 |
}
|
A continuación, vamos a crear una nueva función estática para guardar el juego:
1 |
public static void Save() { |
2 |
savedGames.Add(Game.current); |
3 |
BinaryFormatter bf = new BinaryFormatter(); |
4 |
FileStream file = File.Create (Application.persistentDataPath + "/savedGames.gd"); |
5 |
bf.Serialize(file, SaveLoad.savedGames); |
6 |
file.Close(); |
7 |
}
|
La línea 2 agrega nuestro juego actual a la lista de partidas guardadas. Esa lista es lo que vamos a serializar. Para ello, primero necesitamos crear un nuevo BinaryFormatter
, que gestionará el trabajo de serialización. Esto es lo que hace la línea 3 .
En la línea 4, estamos creando un FileStream
, que es esencialmente un puntero al archivo para poder enviar los datos. Utilizamos File.Create()
para crear un archivo en la ruta que pasamos como parámetro. Convenientemente, Unity cuenta con una ruta por defecto para almacenar los archivos del juego (esta ruta se actualiza según la plataforma para la que generemos el juego) que podemos referenciar con Application.persistentDataPath
.
Como estamos creando un archivo nuevo, no podemos decir solo donde está el archivo, también tenemosque indicar el nombre del archivo. Hay dos partes a este archivo:
- el nombre del archivo
- el tipo de archivo
Usaremos savedGames
para el nombre del archivo, y vamos a utilizar un tipo personalizado gd
(de "game data") para el tipo de archivo. El resultado es un archivo del juego llamado savedGames.gd
en la ruta Application.persistentDataPath
. (Más adelante, puedes guardar otros tipos de cosas con este tipo, por ejemplo, se puede guardar configuración de opciones de los usuarios como options.gd
).
Nota: Puede crear el tipo de archivo que quieras. Por ejemplo, la serie Elder Scrolls utiliza .esm
como su tipo de archivo. Se puede crear por ejemplo savedGames.QuePinQuePan
En la línea 5, estamos llamando a la funcionalidad Serialize
de BinaryFormatter
para guardar la lista savedGames
en nuestro nuevo archivo. Después de eso, en la Linea 6, tenemos que cerrar el archivo que hemos creado.
Badda bing, badda boom. Nuestros juegos se han guardado.
Cargando el estado del juego.
En la función Save
, serializa la lista de partidas guardadas en un lugar específico. Por el contrario, el código para cargar nuestros juegos debería tener este aspecto:
1 |
public static void Load() { |
2 |
if(File.Exists(Application.persistentDataPath + "/savedGames.gd")) { |
3 |
BinaryFormatter bf = new BinaryFormatter(); |
4 |
FileStream file = File.Open(Application.persistentDataPath + "/savedGames.gd", FileMode.Open); |
5 |
SaveLoad.savedGames = (List<Game>)bf.Deserialize(file); |
6 |
file.Close(); |
7 |
}
|
8 |
}
|
En la línea 2, comprobamos si existe un archivo de juego guardado. (Si no es así, no tenemos nada que cargar, obviamente.) En la línea 3, creamos un BinaryFormatter
igual que hicimos en la función Save
. En la línea 4, creamos un FileStream
, pero esta vez, para leer los datos desde el archivo. Utilizamos File.Open
indicando la ruta Application.persistentDataPath
y el nombre del archivo savedGames.gd
La línea 5 es un poco densa, así que vamos a descomprimirlo:
-
bf. Deserialize(File)
busca el archivo en la ubicación que hemos especificados anteriormente y lo deserializa. - No podemos simplemente enviar binarios en Unity y esperar que funcione, sin embargo,podemos convertir (o castear) nuestro archivo deserializado al tipo de datos que queremos que sea, que en este caso es una lista (
List
) del tipo Game. - Luego ponemos la lista como nuestra lista de partidas guardadas.
Por último, en la línea 6, cerramos el archivo de la misma manera que hicimos en la función de guardar.
Nota: El tipo de datos a los que convertimos los datos deserializados puede cambiar dependiendo de lo que se necesite en cada momento. Por ejemplo, Player.lives = (int) bf. Deserialize(File);
.
Conclusión
Nuestro script SaveLoad
ya está termiando y debe tener este aspecto:
1 |
using UnityEngine; |
2 |
using System.Collections; |
3 |
using System.Collections.Generic; |
4 |
using System.Runtime.Serialization.Formatters.Binary; |
5 |
using System.IO; |
6 |
|
7 |
public static class SaveLoad { |
8 |
|
9 |
public static List<Game> savedGames = new List<Game>(); |
10 |
|
11 |
//it's static so we can call it from anywhere
|
12 |
public static void Save() { |
13 |
SaveLoad.savedGames.Add(Game.current); |
14 |
BinaryFormatter bf = new BinaryFormatter(); |
15 |
//Application.persistentDataPath is a string, so if you wanted you can put that into debug.log if you want to know where save games are located
|
16 |
FileStream file = File.Create (Application.persistentDataPath + "/savedGames.gd"); //you can call it anything you want |
17 |
bf.Serialize(file, SaveLoad.savedGames); |
18 |
file.Close(); |
19 |
}
|
20 |
|
21 |
public static void Load() { |
22 |
if(File.Exists(Application.persistentDataPath + "/savedGames.gd")) { |
23 |
BinaryFormatter bf = new BinaryFormatter(); |
24 |
FileStream file = File.Open(Application.persistentDataPath + "/savedGames.gd", FileMode.Open); |
25 |
SaveLoad.savedGames = (List<Game>)bf.Deserialize(file); |
26 |
file.Close(); |
27 |
}
|
28 |
}
|
29 |
}
|
Estos son los fundamentos de cómo guardar y cargar en Unity. En el archivo de proyecto adjunto, encontrarás algunas otras escrituras que muestran cómo gestionar llamadas a estas funciones y cómo visualizar los datos mediante la GUI de Unity.
¡Sé el primero en conocer las nuevas traducciones–sigue @tutsplus_es en Twitter!