beasty-save-system / guides / scene-state.md
Estado de la escena
Cómo guardar los objetos que ya están en tu escena: BeastySaveable marca un objeto como digno de guardarse,
BeastySaveManager los escribe todos en un archivo. Esta página cubre ambos, y los cuatro comportamientos alrededor
de los ids que deciden si tus objetos vuelven o no.
Si aún no has construido la configuración básica, save-without-code.md la explica clic por clic. Esta página explica qué hace realmente esa configuración.
Los dos componentes
BeastySaveable va en cualquier objeto cuyo estado quieras conservar. Tiene un id y una lista de
componentes a capturar. Uno por GameObject — el componente no permite duplicados.
BeastySaveManager va en un objeto de la escena. Mantiene el BeastySaveSettings usado por el
guardado de escena, y es sobre el que llamas SaveAll y LoadAll. Mantiene una Instance estática.
Cada BeastySaveable habilitado se registra a sí mismo con el manager. SaveAll recorre ese registro, pide a cada
componente marcado de cada saveable su estado, y escribe todo en un archivo, indexado por id.
Ids
El id es cómo un archivo de guardado reconoce un objeto entre sesiones. Todo sobre el guardado de escena se reduce a si el id es el mismo la próxima vez que el juego se ejecute.
Se autogenera, y es editable
Añade un BeastySaveable y obtiene un id automáticamente. El inspector lo muestra como Save Id, con un
botón New al lado que genera uno nuevo.
Puedes editar el id a mano, y hay buenas razones para hacerlo — player, door.cellar, chest.tutorial son
más fáciles de encontrar en un archivo de guardado que un string aleatorio.
Advertencia Cambiar un id huérfaniza los datos ya guardados bajo el anterior. El estado sigue en el archivo, bajo una clave que nada en la escena reclama ya, y el objeto con el nuevo id no lo encontrará. Decide tus ids antes de tener guardados que te importen. El botón New tiene exactamente el mismo efecto: descarta la conexión del objeto con cada guardado existente.
Un asset de prefab no lleva ningún id
El asset de prefab en sí no tiene id. Cada instancia obtiene el suyo propio, generado cuando se crea. Dos copias del mismo prefab en una escena son dos saveables diferentes con dos ids diferentes, que es lo que quieres — son dos cofres diferentes.
Un objeto generado en tiempo de ejecución obtiene un id nuevo cada vez
Este es el que atrapa a la gente.
Un objeto que Instantiates mientras el juego se ejecuta obtiene un id completamente nuevo, cada vez. Juega el
juego, genera un enemigo, guarda: el estado del enemigo va al archivo bajo un id generado en esa sesión.
Reinicia, genera un enemigo, carga: el nuevo enemigo tiene un id diferente, no encuentra nada en el archivo, y mantiene su
estado por defecto. El estado antiguo se queda en el archivo, inalcanzable, para siempre.
La solución es darle al objeto generado un id que tú controles, en el momento de generarlo:
using Beasty_SaveSystem;
using UnityEngine;
public class EnemySpawner : MonoBehaviour
{
public GameObject enemyPrefab;
public GameObject Spawn(string enemyId, Vector3 position)
{
GameObject enemy = Instantiate(enemyPrefab, position, Quaternion.identity);
// Un id estable, y los componentes a capturar.
BeastySaveManager.Register(enemy, $"enemy.{enemyId}", enemy.transform);
return enemy;
}
}
Register añade el BeastySaveable por ti, configura el id, y asigna la lista de componentes — así que el objeto es
guardable desde ese momento. Hay dos overloads:
static BeastySaveable Register(GameObject target, params Component[] components);
static BeastySaveable Register(GameObject target, string id, params Component[] components);
El que no tiene id usa el que el objeto ya tenga, lo cual solo es útil para objetos que vinieron de la escena. Para cualquier cosa que generes, usa el overload que recibe un id.
El id tiene que ser algo que puedas reproducir en la siguiente ejecución. enemy.goblin.3 derivado de los datos de tu
propio juego es estable. enemy. más Guid.NewGuid() no lo es — es el mismo problema de nuevo, en tu propio código.
Las contrapartes, cuando un objeto desaparece definitivamente:
BeastySaveManager.Unregister(gameObject); // este ya no está
BeastySaveManager.UnregisterAll(); // limpia el registro
BeastySaveManager.SyncSceneSaveables(); // vuelve a escanear la escena en busca de saveables
Dos objetos con el mismo id: el segundo desaparece
Si dos saveables terminan con el mismo id, el segundo no se registra. Se registra un error, y ese objeto silenciosamente queda fuera del guardado — no se escribe, y no se restaura.
Los ids duplicados ocurren cuando copias y pegas un GameObject que ya tiene un id escrito a mano, o cuando generas
dos objetos y pasas el mismo id a Register. Si un objeto se niega misteriosamente a persistir, revisa la
consola en busca del error de id duplicado, y luego revisa su id en el inspector.
Los objetos inactivos se guardan
Un BeastySaveable se registra en OnEnable y se desregistra en OnDestroy — no en OnDisable.
Así que un objeto que desactivas con SetActive(false) sigue en el registro, sigue escribiéndose en el guardado,
y sigue restaurándose al cargar. Ese es el comportamiento que quieres: un enemigo deshabilitado o un panel de UI
cerrado mantiene su estado en lugar de perderlo en el momento en que desaparece.
Destruir el objeto es lo que lo elimina del guardado.
Varios componentes del mismo tipo
Dos componentes BoxCollider en un mismo GameObject se guardan y recuperan correctamente ambos. Se almacenan por separado en el
archivo (el segundo obtiene un sufijo #1 en su clave) y se restauran a los slots correctos. No tienes que hacer nada
para que esto funcione.
Guardando y cargando
Desde un botón, sin código
SaveAll(string) y LoadAll(string) son métodos void que reciben un único string, lo que los hace
conectables directamente al On Click () de un Button uGUI con el nombre del slot escrito en el inspector. Este es
el camino sin código, y está cubierto paso a paso en
save-without-code.md.
Desde código
BeastySaveManager manager = BeastySaveManager.Instance;
SaveResult saved = manager.SaveAllNow("slot1");
if (!saved.Success)
Debug.LogError($"Save failed: {saved}");
LoadResult loaded = manager.LoadAllNow("slot1");
if (!loaded.Success)
Debug.LogError($"Load failed: {loaded}");
SaveAllNow y LoadAllNow son los que devuelven un resultado. Úsalos siempre que quieras saber
si funcionó — lo cual es siempre, en código de producción. SaveAllNow también recibe un diccionario de
metadatos opcional:
manager.SaveAllNow("slot1", new Dictionary<string, string>
{
["chapter"] = "3",
["playtime"] = "01:22",
});
SaveAll y LoadAll hacen el mismo trabajo pero no devuelven nada, porque un UnityEvent no puede usar un valor
de retorno. En su lugar almacenan el resultado:
manager.SaveAll("slot1");
SaveResult result = manager.LastSaveResult; // y manager.LastLoadResult después de una carga
manager.DeleteSlot("slot1") elimina un slot y su backup.
Los eventos
void OnEnable()
{
BeastySaveManager.Instance.SaveCompleted += OnSaved;
BeastySaveManager.Instance.LoadCompleted += OnLoaded;
}
void OnDisable()
{
if (BeastySaveManager.Instance == null) return;
BeastySaveManager.Instance.SaveCompleted -= OnSaved;
BeastySaveManager.Instance.LoadCompleted -= OnLoaded;
}
void OnSaved(SaveResult result)
{
if (result.Success) ShowToast("Game saved");
else ShowError(result.Message);
}
void OnLoaded(LoadResult result)
{
if (result.Success) return;
if (result.BackupAvailable) OfferBackupRestore();
else ShowError(result.Message);
}
SaveCompleted y LoadCompleted se disparan en cada guardado y carga, tanto desde los botones como desde código.
Son el lugar correcto para poner el toast de “Game saved”, y el lugar correcto para ofrecer la copia de seguridad cuando una carga
falla. LoadResult.BackupAvailable se rellena en cada resultado de carga, éxito o fallo — consulta
backups-and-corruption.md.
Qué termina realmente en el archivo
Un guardado de escena tiene el tipo Beasty.SaveGroup y se ve así:
{
"saveables": {
"player": {
"UnityEngine.Transform": { "module": "core", "data": { } }
},
"door.cellar": {
"UnityEngine.BoxCollider": { "module": "physics3d", "data": { } },
"UnityEngine.BoxCollider#1": { "module": "physics3d", "data": { } }
}
}
}
Una entrada por id de saveable, una entrada por componente guardado dentro de él, y el módulo que escribió cada uno. El
sufijo #1 es el segundo componente del mismo tipo en ese objeto.
Un saveable que está en la escena pero no tiene datos en el archivo — un objeto nuevo añadido después de que se escribió el guardado — se deja intacto, con una advertencia. No es un error, y no hace fallar la carga.
Marcar un componente que no se puede guardar
La lista Saved Components etiqueta cada componente con la capa que puede convertirlo: core, un
id de módulo como ugui, o dev para un convertidor que registraste tú mismo.
Si marcas un componente que nada sabe convertir, el inspector te avisa, y un guardado fallará con
TypeUnavailable. Ya sea desmárcalo, activa el módulo que necesita
(converter-modules.md), o escribe un convertidor para él
(custom-converters.md).
Ver también
- save-without-code.md — la configuración clic por clic
- save-manager-window.md — la ventana del editor
- components.md —
BeastySaveableyBeastySaveManager, campo por campo - what-gets-saved.md — qué campos y tipos se guardan y recuperan correctamente
- converter-modules.md — qué almacena cada convertidor
- strict-vs-tolerant.md — qué hace una carga de escena cuando un componente falla