Datos persistentes: Cómo guardar los estados y ajustes del juego

Guardar los datos es fundamental para cualquier juego. Tanto si necesita guardar puntuaciones altas, preferencias o el estado de un juego, Unity ofrece una gran variedad de métodos: desde PlayerPrefs hasta serializar datos, cifrarlos y escribirlos en un archivo.
Actualizado el 23 de junio de 2021: Como parte de Unite Now 2020, he creado una sesión con consejos sobre la persistencia de datos en Unity. Cubre algunas de las formas más comunes de guardar y cargar datos en su proyecto Unity, pero no es en absoluto una lista exhaustiva. Es decir, hay más formas de serializar datos de las que necesitará nunca, y cada enfoque resuelve un problema concreto y viene acompañado de su propio conjunto de puntos fuertes y débiles. Esta entrada de blog cubrirá los mismos métodos comunes que traté en la sesión de Unite Now.
Los PlayerPrefs no están hechos para guardar estados del juego. Sin embargo, son útiles, así que hablaremos de ellas. Puede utilizar PlayerPrefs para almacenar las preferencias de un reproductor entre sesiones, como los ajustes de calidad, el volumen de audio u otros datos no esenciales. Los PlayerPrefs se almacenan en algún lugar de su dispositivo, separados de su proyecto. La ubicación exacta varía en función de su sistema operativo, pero normalmente se trata de algún lugar accesible globalmente y gestionado por su SO. Los datos almacenados están en simples pares clave-valor. Debido a su facilidad de acceso, no están a salvo de los usuarios que deseen abrirlos y modificarlos, y pueden borrarse por accidente, ya que se guardan fuera del proyecto y son gestionados por su sistema operativo.
Los PlayerPrefs son relativamente fáciles de implementar y sólo requieren unas pocas líneas de código, pero sólo admiten valores de tipo Float, Int y String, lo que dificulta la serialización de objetos grandes y complejos. Un usuario decidido puede superar esta limitación convirtiendo sus datos guardados en algún formato representado por uno de estos tipos básicos, pero no se lo recomiendo ya que existen mejores herramientas para almacenar sus datos.
public void SavePrefs()
{
PlayerPrefs.SetInt("Volume", 50);
PlayerPrefs.Save();
}
public void LoadPrefs()
{
int volume = PlayerPrefs.GetInt("Volume", 0);
}
Por último, dado que cada aplicación Unity almacena todos sus PlayerPrefs en un único archivo, no es muy adecuada para manejar múltiples archivos de guardado o guardados en la nube, ya que ambos requieren que almacene y reciba los datos de guardado desde una ubicación diferente.
JSON es un formato de datos legible por humanos. Es decir, es fácilmente comprensible tanto para las personas como para las máquinas, lo que tiene ventajas e inconvenientes. Es mucho más fácil depurar sus datos guardados o crear nuevos datos guardados con fines de prueba cuando puede leerlos y comprenderlos, pero, por otro lado, también es fácil que los jugadores lean y modifiquen los datos. La capacidad de leer y cambiar datos es útil si se apoya el modding, pero perjudicial si se quiere evitar las trampas. Además de estas preocupaciones, dado que JSON es un formato basado en texto, es más costoso de analizar para las máquinas. Es decir, es más lento de leer y utiliza más memoria que las alternativas binarias. Por lo tanto, si tiene muchos datos, puede que quiera considerar opciones que no estén basadas en texto. Cada caso de uso es diferente, y son este tipo de compensaciones las que llevan a los desarrolladores a crear muchos otros formatos de datos.
JSON está estandarizado y se utiliza ampliamente en muchas aplicaciones diferentes. Como resultado, todas las plataformas lo soportan en gran medida, lo que resulta útil a la hora de crear juegos multiplataforma. JSON se desarrolló como un protocolo de comunicación para navegadores web, lo que lo hace intrínsecamente bueno para enviar datos a través de una red. Por ello, JSON es excelente para enviar y recibir datos de un servidor backend.
JsonUtility es la API integrada de Unity para serializar y deserializar datos JSON. Al igual que PlayerPrefs, también es relativamente fácil de implementar. Sin embargo, a diferencia de PlayerPrefs, deberá guardar los datos JSON usted mismo, ya sea en un archivo o a través de una red. Manejar usted mismo el almacenamiento de datos facilita la gestión de varios archivos guardados, ya que puede almacenar cada archivo en una ubicación diferente. Para hacer esto más fácil, escribí un gestor de archivos básico, que está disponible en este repositorio de ejemplos.
Es importante mencionar que JsonUtility no es una implementación JSON completa. Si está acostumbrado a trabajar con datos JSON, es posible que note la falta de compatibilidad con funciones específicas. Si está interesado en comparar el rendimiento de diferentes soluciones JSON, pruebe este proyecto de evaluación comparativa. Tenga en cuenta que lo mejor es realizar la prueba en el dispositivo de destino si es posible.
Las mismas limitaciones limitan a JsonUtility que al serializador interno de Unity - es decir, si no puede serializar un campo en el Inspector, no podrá serializarlo a JSON. Para sortear estas limitaciones, puede crear tipos de datos antiguos (o PODS) para albergar todos sus datos guardados. Cuando llegue el momento de guardar, transfiera sus datos de sus tipos de ejecución a un POD y guárdelo en un disco. Si es necesario, también puede crear callbacks de serialización personalizados para soportar tipos que el serializador de Unity no soporta por defecto.
En cuanto a JsonUtility, EditorJsonUtility es otra herramienta útil. Mientras que JsonUtility funciona para cualquier objeto basado en MonoBehaviour o ScriptableObject, EditorJsonUtility funcionará para cualquier tipo de motor Unity. Así que usted podría crear una representación JSON de cualquier objeto en el editor de Unity - o ir en la otra dirección y crear un activo a partir de un archivo JSON.
Aparte de las opciones de serialización incorporadas, existen otras bibliotecas externas que también podría utilizar. A menos que necesite específicamente utilizar un formato basado en texto por su legibilidad, lo mejor es optar por un serializador basado en binarios:
Herramientas binarias
- MessagePack es un serializador binario eficiente. Es eficaz y relativamente fácil de usar. Al igual que JSON, está disponible en casi todas las plataformas, por lo que puede utilizarlo para enviar datos a través de redes para comunicarse con servidores backend. Puede leer más al respecto aquí.
- ProtoBuf y Protobuf-net es otro serializador binario similar. También es rápido y eficaz. Google lo desarrolló como una alternativa eficaz a formatos existentes como XML. Al igual que JSON y MessagePack, también es adecuado para la comunicación a través de redes.
- BinaryFormatter es una biblioteca DotNet para almacenar sus objetos en formato binario directamente. Sin embargo, BinaryFormatter tiene peligrosas vulnerabilidades de seguridad y debe evitarse. Repito, no utilice BinaryFormatter. Obtenga más información sobre los riesgos de seguridad aquí.
Herramientas de texto
- EasySave es un plug-in bien soportado y popular disponible en la Unity Asset Store. Le permite guardar todos sus datos sin escribir ningún código, lo que es excelente para los principiantes. Además, cuenta con una API potente y flexible que lo hace ideal también para usuarios avanzados. No es gratuito, pero merece la pena si busca una solución lista para usar con todas las funciones.
- JSON.Net es una implementación de JSON gratuita y de código abierto para todas las plataformas DotNet. A diferencia de la JsonUtility incorporada, está totalmente equipada. Sin embargo, esto tiene un coste, ya que su rendimiento es significativamente menor que el de la JsonUtility incorporada. La versión estándar no es compatible con todas las plataformas de Unity, pero existe una versión modificada disponible en la Unity Asset Store que añade compatibilidad.
- XML es un formato de datos alternativo. Al igual que JSON, es relativamente legible por humanos y tiene algunas características que pueden ser útiles para su aplicación específica, como los espacios de nombres. DotNet tiene soporte incorporado para XML.
Cuando surge el tema de la seguridad, la mayoría de la gente piensa primero en la encriptación. Sin embargo, cuando se trata de almacenar datos localmente en el dispositivo de un jugador, la encriptación es relativamente fácil de superar. Incluso sin romper la encriptación, los usuarios pueden manipular los datos directamente en la memoria con herramientas disponibles gratuitamente. En otras palabras, es seguro asumir que cualquier cosa que se almacene localmente no es de fiar.
Si necesita verdadera seguridad, su mejor opción es mantener sus datos en un servidor donde los usuarios no puedan modificarlos. Para que esto funcione, la aplicación no debe enviar ningún dato directamente al servidor porque los usuarios podrían manipularlo. En su lugar, la aplicación sólo puede enviar comandos al servidor, dejar que éste modifique los datos y, a continuación, devolver los resultados a la aplicación. Así que si la seguridad de los datos es vital para usted, es mejor saberlo cuanto antes porque afectará a la arquitectura de su proyecto.
Para más información sobre la serialización, consulte la página del manual. Si desea ver esto en acción, consulte la sesión adjunta de Unite Now.