beasty-save-system / troubleshooting.md
Solución de problemas
Síntoma, causa, solución. Encuentra tu síntoma, lee las dos líneas debajo, sigue el enlace si necesitas la historia completa.
Antes que nada: comprueba el resultado. Cada llamada devuelve un SaveResult o un LoadResult<T> y
nada en esta API lanza excepciones. Si no estás mirando result.Success y result.Error, el
sistema ya te ha dicho qué salió mal y tú no lo has leído.
LoadResult<PlayerData> result = BeastySave.Load<PlayerData>("slot1", settings);
if (!result.Success)
Debug.LogError(result); // "Corrupt: ...", "DecryptFailed: ...", "FileNotFound: ..."
La lista completa de códigos está en Resultados y errores.
Mi guardado cargó, pero la referencia al sprite / prefab / componente ha desaparecido
Causa. Nunca se guardó. Las referencias a UnityEngine.Object — un Sprite, un GameObject, un
Material, otro componente — no se escriben en el archivo, ni en campos ni en colecciones de ellos.
Esto es intencional, y normalmente no es el problema que crees. Como la referencia no se guarda, tampoco se sobrescribe al cargar: lo que conectaste en la escena o en el prefab sigue ahí, sin tocar. La referencia “desaparecida” casi siempre significa que otra cosa la limpió.
Solución. Guarda un id, no una referencia. Almacena el string que identifica la cosa, y resuélvelo tú mismo al cargar:
[SerializeField] private string equippedWeaponId; // guardado
private Weapon _equipped; // resuelto a partir del id después de cargar
Si tu referencia realmente necesita volver a través del sistema de guardado, los convertidores incorporados que sí
restauran assets (AudioSource.clip, Image.sprite, materiales de física) los resuelven por nombre mediante
Resources.Load, que solo funciona para assets dentro de una carpeta Resources/. Consulta
Qué se guarda y Módulos convertidores.
Nota Una excepción que vale la pena conocer: en una clase C# plana — no un MonoBehaviour — un campo que contiene un
UnityEngine.Objectno se omite. Hace fallar el guardado conSerializationFailed.
El objeto que generé en tiempo de ejecución no recuerda nada
Causa. Un BeastySaveable en un prefab no tiene id. Cada Instantiate genera uno nuevo. El
objeto se guarda bajo un id que nunca volverá a existir, así que al cargar no hay nada con qué emparejarlo y
vuelve con los valores por defecto del prefab.
Solución. Registra los objetos generados en tiempo de ejecución con un id estable que tú controles:
GameObject chest = Instantiate(chestPrefab);
BeastySaveManager.Register(chest, "chest.cave.03", chest.GetComponent<Chest>(), chest.transform);
El id debe ser el mismo en cada ejecución para el mismo objeto lógico. Derívalo del punto de aparición, la sala, la
misión — cualquier cosa estable. No de GetInstanceID(), ni de un contador que dependa del orden de aparición.
Consulta Estado de la escena.
Uno de mis objetos falta silenciosamente del guardado
Causa. Un id duplicado. Dos componentes BeastySaveable con el mismo id: el primero se registra, el
segundo es rechazado y queda fuera del guardado por completo. Es silencioso en el juego, pero no es silencioso en
la consola.
Solución. Lee la consola. Se registra un error en el momento del registro nombrando el id. Luego pulsa New en
el inspector del BeastySaveable para regenerar el id, o abre
Tools > Beasty Save System > Save Manager y mira la lista Saveables in Scene, que muestra cada
saveable en la escena (incluyendo los inactivos) con su id.
La forma habitual en que esto ocurre: duplicaste un GameObject que ya tenía un BeastySaveable, y la copia
se llevó el id consigo. Consulta Estado de la escena.
SaveAll falla con TypeUnavailable
Mensaje. "...has no registered converter; enable its converter module or register a custom IBeastyConverter."
Causa. Un componente marcado en la lista Saved Components de un BeastySaveable no tiene quién lo convierta. La
capa core cubre los tipos matemáticos, Transform, Camera, Light, SpriteRenderer, Texture2D y cualquier
MonoBehaviour. Todo lo demás — Animator, AudioSource, ParticleSystem, colliders, TMP_Text,
componentes de uGUI — proviene de un módulo convertidor.
Solución. Una de tres:
- Activa el módulo. Cada módulo necesita su paquete de Unity presente en el proyecto (por ejemplo, el
módulo Physics2D necesita
com.unity.modules.physics2d). Instálalo y el módulo compila y se registra a sí mismo. Consulta Módulos convertidores. - Desmarca el componente. Si no necesitabas que se guardara su estado, sácalo de la lista.
- Escribe un convertidor. Consulta Convertidores personalizados.
No tienes que esperar a que falle. El inspector de BeastySaveable te avisa en el editor: un componente marcado
sin convertidor se señala, y cada otro componente se etiqueta con la capa que lo convierte (dev, un id de módulo,
o core).
La carga falla con Corrupt
Causa. El checksum no coincide. El archivo se editó a mano, se truncó por un cierre inesperado, se dañó por el disco, o se copió mal. El sobre (envelope) está lo bastante intacto para leerse; el payload no es lo que dice ser.
Solución. Ofrécele al jugador la copia de seguridad. Todo LoadResult lleva BackupAvailable, tanto en éxito como en
fallo:
LoadResult<PlayerData> result = BeastySave.Load<PlayerData>("slot1", settings);
if (!result.Success && result.BackupAvailable)
{
// Pregunta al jugador primero. Luego:
BeastySave.RestoreBackup("slot1", settings);
result = BeastySave.Load<PlayerData>("slot1", settings);
}
RestoreBackup copia el .bak sobre el slot y deja el .bak en su lugar. También puedes hacerlo a mano
desde Tools > Beasty Save System > Save Manager, que tiene un botón Restore Backup por slot.
Un archivo corrupto nunca se rota hacia la copia de seguridad, así que el .bak es la última copia que se verificó. Consulta
Copias de seguridad y corrupción.
Nota Un error
Corrupttambién se produce antes en la carga, para un archivo cuya forma del sobre es incorrecta — un documento JSON que se parsea pero no es un guardado de Beasty. La misma solución.
La carga falla con DecryptFailed
Causa. Una de dos, y son problemas distintos:
- El flag
Encryptedno coincide con el archivo.Encrypted = truerechaza cargar un guardado en texto plano, y una configuración en texto plano no puede leer uno encriptado. Este es el caso común: activaste la encriptación a mitad de desarrollo y tus guardados existentes están en texto plano. - La clave es incorrecta. El
EncryptionKeyenBeastySaveSettingsno es el que se usó para escribir el archivo. Ten en cuenta que unEncryptionKeyvacío no significa “sin clave” — significa la clave por defecto compartida.
Solución. Haz que la configuración coincida con cómo se escribió el archivo. Si cambiaste la clave o el flag entre builds, los guardados antiguos son ilegibles y no hay forma de recuperarlos — eso es lo que significa la encriptación. Decide esto antes de publicar, no después. Consulta Encriptación.
La carga falla con VersionTooNew
Causa. El guardado fue escrito por un build más nuevo que el que lo está leyendo. O bien su dataVersion es
más alto que el DataVersion en tu BeastySaveSettings, o su versión de contenedor es más alta que la que esta
versión entiende.
Las migraciones solo se ejecutan hacia adelante. El sistema puede actualizar un guardado antiguo a la versión actual; no puede hacer bajar uno futuro.
Solución. En desarrollo, esto generalmente significa que un compañero de equipo escribió el archivo con su build, o que retrocediste tu proyecto.
Borra el slot, o sube DataVersion para que coincida.
En un juego publicado, significa que un jugador hizo un downgrade — un guardado en la nube de una versión más nueva
llegando a una instalación más antigua. Manéjalo: comprueba BeastySaveError.VersionTooNew y muestra “Este guardado se hizo con una
versión más nueva del juego”, no un fallo genérico. Consulta
Versionado y migraciones.
Mi carga silenciosamente no hace nada
Causa. Casi seguro FileNotFound, y no comprobaste el resultado. Un slot faltante se registra como una
advertencia, no un error — deliberadamente, porque las pantallas de selección de slot sondean cada slot constantemente y
un error por cada slot vacío inundaría tu consola.
Solución. Comprueba result.Success. Siempre.
LoadResult<PlayerData> result = BeastySave.Load<PlayerData>("slot1", settings);
if (result.Success)
Apply(result.Value);
else if (result.Error == BeastySaveError.FileNotFound)
StartNewGame(); // un slot vacío no es un error
else
ShowError(result.Message);
BeastySave.Exists(slot, settings) responde la pregunta directamente si eso es todo lo que necesitas. También comprueba
BeastySaveManager.LastLoadResult si estás usando el camino sin código LoadAll, y suscríbete a
LoadCompleted. Consulta Resultados y errores y
Slots y metadatos.
Funcionó en el editor y se rompió en el build
Dos causas, ambas reales.
Assets referenciados por nombre. Los convertidores que restauran un asset — AudioSource.clip, Image.sprite,
materiales de física — escriben el nombre del asset y lo vuelven a resolver al cargar con Resources.Load. En el
editor el asset suele estar disponible. En un build, solo se resuelve si vive en una carpeta
Resources/. Si no se resuelve, la referencia ya conectada en la escena se deja intacta — así que el objeto
carga, y lleva puesto el sprite equivocado.
Solución: pon esos assets en una carpeta Resources/, o deja de depender de que la referencia vuelva a través del
guardado y resuélvela tú mismo a partir de un id.
Propiedades exclusivas del editor. Light.lightmapBakeType es una API exclusiva del editor. El convertidor de Light la escribe
y la lee solo en el editor; un build ni la almacena ni la restaura. Un guardado hecho en el editor
lleva ese miembro, un build lo ignora — lo cual es correcto, y también por eso el tipo de bake de una luz
no sobrevive al player.
Además: Light.cookie se escribe por nombre pero nunca se restaura, y MeshCollider.sharedMesh nunca
se serializa en absoluto. Consulta Módulos convertidores.
Mi convertidor personalizado dejó de funcionar después de pulsar Play
Causa. Entrar en Play Mode reinicia las variables estáticas. Los convertidores registrados con
BeastySave.RegisterConverter y las migraciones registradas con BeastySave.RegisterMigration desaparecen en
cada Play. Si los registraste desde un elemento de menú, un callback del editor, o un Awake de un MonoBehaviour que
no siempre se ejecuta, no están ahí cuando ocurre el guardado — y tus datos silenciosamente vuelven al
comportamiento incorporado.
Solución. Regístralos desde un [RuntimeInitializeOnLoadMethod]:
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Register()
{
BeastySave.RegisterConverter(new FurnaceConverter());
BeastySave.RegisterMigration(1, 2, MigrateV1ToV2);
}
Los convertidores registrados con BeastySave.RegisterModule sobreviven al reinicio — por eso los módulos incorporados
siguen funcionando. Consulta Convertidores personalizados.
Nada se guarda en WebGL
Causa. WebGL no está soportado. La escritura atómica depende de la semántica del sistema de archivos que el build de navegador
no tiene, y las variantes asíncronas están basadas en Task.
Solución. No hay ninguna, y no hay ninguna opción que lo cambie. Un build de navegador necesita una capa de persistencia diferente. Consulta Plataformas y límites.
Otros fallos que vale la pena nombrar
| Error | Qué significa |
|---|---|
InvalidArgument | Datos nulos, o un nombre de slot inválido. Los nombres de slot que intentan escapar de la carpeta, y los nombres de dispositivo de Windows, son rechazados. |
SerializationFailed | Los datos no se pudieron convertir en JSON: un ciclo de referencias (“save data must be acyclic”), un valor float NaN o Infinity, un diccionario con claves no primitivas, un campo UnityEngine.Object en una clase C# plana. |
IoError | No se pudo escribir o leer la carpeta o el archivo. Disco lleno, permisos, una ruta que no existe. |
ParseError | El archivo no es JSON válido en absoluto. |
TypeMismatch | El type registrado en el archivo no es el tipo que pediste cargar. |
MigrationFailed | Una migración registrada lanzó una excepción, o ninguna cadena de pasos alcanza el DataVersion actual. |
FieldMapFailed | Un campo no se pudo mapear. En una carga estricta esto hace fallar toda la carga y no se aplica nada. En una carga tolerante habría sido una advertencia en su lugar. |
Consulta Resultados y errores para la lista completa de los trece códigos y
Carga estricta vs. tolerante para la diferencia que marca la configuración Strict.