Saltar al contenido principal

Sistema de guardado

MCE usa un sistema de guardado basado en JSON con partidas con nombre, escrituras atómicas, rotación de autoguardados, versionado y extensibilidad. El sistema de guardado conserva todo el estado de juego del jugador: equipo, inventario, posición, medallas, progreso de misiones y variables de juego.

Opciones relacionadas con el guardado

Tres de los interruptores de esa pantalla pertenecen a este sistema: Partida guardada elige el formato de serialización, Autoguard. historia decide si los hitos de la historia se guardan solos, y Subir a la nube es el que pregunta antes de salir de la máquina.

Arquitectura​

El sistema de guardado se compone de varios componentes:

ComponentePropósito
SavegameManagerOrquesta las operaciones de guardar/cargar (un ScriptableObject enlazado mediante el SavegameInstaller)
SavableObjectClase base (un ScriptableObject) para cualquier objeto que conserve datos: SaveToText, LoadFromText y ResetSave
MCESavesSerializerSerializador JSON usado para todo texto guardado
GameVariablesAlmacén de variables con nombre: booleanos, enteros, flotantes y cadenas
GameVariableHolderUn SavableObject que posee un almacén GameVariables (GlobalGameData y cada SceneInfoAsset son titulares)
GameVariableReferenceReferencia de Inspector a una variable, usada por los nodos de CommandGraph
Migrations/ISaveMigration, SaveMigrationPipeline y SaveFormatVersions: el versionado de guardados

Qué se guarda​

SavegameManager.SavegameObjects es la lista de objetos guardables que entran en cada guardado. Los propios del motor son:

ObjetoContiene
GlobalGameData (guardado como SavableGlobalGameData)Posición del jugador, dinero, medallas obtenidas, variables de juego, la marca de versión del formato, base secreta y gimnasio del jugador
RosterLos monstruos del equipo con todo su estado (especie, nivel, IV, EV, naturaleza, movimientos, PP, HP, estado, amistad...)
MonsterStorageMonstruos en las cajas
BagObjetos y cantidades
DexSeguimiento de vistos/capturados por especie
QuestManagerMisiones activas, objetivos completados, recompensas reclamadas
CharacterDataEl personaje jugador
TimeManagerLa hora del día
NurseryLa guardería de cría
SceneInfoAssetLas variables de juego propias de cada mapa

Archivos de guardado​

Un guardado es una carpeta, no un único archivo. Los guardados viven en Saves/ dentro del Application.persistentDataPath de Unity:

PlataformapersistentDataPath
Windows%USERPROFILE%/AppData/LocalLow/<CompanyName>/<ProductName>/
macOS~/Library/Application Support/<CompanyName>/<ProductName>/
Linux~/.config/unity3d/<CompanyName>/<ProductName>/
AndroidAlmacenamiento interno (privado de la app)
iOSSandbox de la app
<persistentDataPath>/Saves/
2026-10-10-18-30-02/ a manual save, named with the current date
GlobalGameData.sav one .sav per savable object
Roster.sav
...
Manifest.txt the list of files a COMPLETE save must contain
Autosave/
OlderAutosave/

Los guardados se escriben para ser seguros ante cierres inesperados:

  • Cada archivo se escribe de forma atómica (en un archivo temporal que luego lo reemplaza).
  • Todo el guardado se escribe primero en una carpeta <name>.staging y solo se intercambia a su lugar después de escribir todos los archivos y el manifiesto. Un guardado interrumpido nunca deja una mezcla a medias de estado antiguo y nuevo.
  • Al cargar, si falta un archivo listado en el manifiesto, el guardado se interrumpió, así que se rechaza en lugar de cargar un guardado parcial.

Guardar y cargar​

El jugador guarda desde el menú de pausa (la entrada Exit ofrece guardar, y la tecla de guardado del menú pregunta y escribe un guardado con el nombre de la fecha actual) y carga desde el menú principal (Continue carga el último guardado, Load los lista todos). Cuando el juego funciona en línea (MCE Online), el servidor conserva el progreso y no se escribe el guardado local.

Desde código, mediante el SavegameManager (ediciones Source y Studio):

// A new save named with the current date
yield return savegameManager.SaveGameWithCurrentDate(playerCharacter);

// Rotate the autosave
yield return savegameManager.Autosave(playerCharacter);

// Load by name, or the most recent one
yield return savegameManager.LoadGame(saveName);
yield return savegameManager.LoadLastSavegame();

SavegameManager.GetAllSavegames(out list) y AreThereSavegames() te dicen qué hay en disco. Los estados de prueba (SaveStates) viven junto a los guardados del jugador con su propio prefijo y están ocultos en todas las listas que puede ver el jugador.

La fachada de guardado del SDK

engine.SaveSystem (ISaveSystem) expone Save(slot), Load(slot), HasSaveData(slot), DeleteSave(slot), OnSaved y OnLoaded, pero en el motor actual no está conectada al SavegameManager: nada atiende sus peticiones de guardar y cargar, y HasSaveData busca en persistentDataPath un archivo slot{N}.sav que el motor no escribe. Usa el SavegameManager (ediciones Source y Studio) o los menús del juego para los guardados reales. Consulta ISaveSystem.

Autoguardado​

Dos opciones de la pantalla de Opciones (guardadas en el GameplayConfigurationFile) controlan el autoguardado:

OpciónCuándo guarda
AutoSaveOnTeleportingCada vez que el jugador se teletransporta a otro mapa (no mientras se carga una partida)
AutoSaveOnStoryEn los puntos de la historia donde un grafo tiene un nodo AutoSaveIfEnabled

Cada autoguardado va a Autosave/; el anterior se rota a OlderAutosave/ solo después de que el nuevo se haya escrito correctamente, de modo que un autoguardado interrumpido nunca destruye el último bueno.

Buenas prácticas de autoguardado
  • Coloca AutoSaveIfEnabled después del hito de la historia, nunca en medio de una batalla o una escena.
  • Muestra un indicador de autoguardado al guardar.

Versionado de guardados​

Los guardados llevan una versión de formato, que se escribe en SavableGlobalGameData al guardar y que el SaveMigrationPipeline compara al cargar, ejecutando las migraciones que necesita un guardado más antiguo:

VersiónNombreSignificado
0LegacyHashesGuardados anteriores a la marca de versión: especies, formas, habilidades, movimientos y objetos identificados por el hash en tiempo de ejecución de los nombres de sus assets
1StableNamesLos identifica por los nombres de sus assets (cadenas estables). Es la versión actual

Se agrega una nueva ISaveMigration al pipeline cuando cambia el formato, y se incrementa SaveFormatVersions.Current.

Nombres de campos serializados y nombres de assets

Nunca renombres campos serializados en clases relacionadas con el guardado sin proporcionar una migración, y trata los nombres de asset de especies, movimientos, habilidades y objetos como claves de guardado: renombrar un asset rompe los guardados que lo nombran. Si renombras un campo, los archivos de guardado existentes pierden esos datos al cargar; usa [FormerlySerializedAs("oldName")] para renombrar campos.

GameVariables​

El sistema GameVariables es el almacén de MCE de valores con nombre persistentes. Lo leen y escriben los nodos de CommandGraph y los scripts, y se guarda con el objeto que lo posee.

Tipos de variable​

TipoDescripciónEjemplo
BooleanBanderas verdadero/falsodefeatedBrock = true
IntegerContadores numéricosfishCaught = 42
FloatNúmeros decimalesrivalTrust = 0.5
StringValores de textorivalName = "Gary"

Cada variable vive en un titular: el global (GlobalGameData) o el SceneInfoAsset de un mapa. Una variable definida en un mapa es local a ese mapa; una global la comparte todo el juego.

Uso en CommandGraph​

  • SetBoolVariable, SetIntVariable: establecen el valor de una variable.
  • IfBool, IfIntEquals, IfIntCompare, SwitchIntVariable: ramifican según una variable.

Cada uno de estos nodos tiene una referencia de variable donde eliges el titular, el tipo y el nombre.

Uso en código​

// globalGameData is the injected GlobalGameData (a GameVariableHolder);
// the class that receives it implements IPlayerDataReceiver
bool beatBrock = globalGameData.GetVariable<bool>("defeatedBrock");
int fishCount = globalGameData.GetVariable<int>("fishCaught");

globalGameData.SetVariable("defeatedBrock", true);
globalGameData.SetVariable("fishCaught", fishCount + 1);

Datos de guardado personalizados​

Usa GameVariables (todas las ediciones)​

La forma más sencilla de conservar tus propias banderas y contadores con el guardado es almacenarlos como variables de juego en GlobalGameData: se guardan, cargan, migran y reinician junto con todo lo demás.

Extender SavableObject (ediciones Source y Studio)​

Para una estructura de datos completa, escribe un nuevo SavableObject y agrega un asset de él a la lista Savegame Objects del asset SavegameManager:

[CreateAssetMenu(menuName = "MyGame/Custom Save Data")]
public class CustomSaveData : SavableObject
{
[Serializable] private class Data { public int counter; public List<string> unlocked; }
private Data data = new() { unlocked = new List<string>() };

public override string SaveToText(ISerializer<string> serializer, PlayerCharacter playerCharacter) =>
serializer.To(data);

public override IEnumerator LoadFromText(ISerializer<string> serializer, string data,
MCESettings mceSettings, MonsterDatabaseInstance monsterDatabase)
{
this.data = serializer.From<Data>(data);
yield break;
}

public override void ResetSave() => data = new Data { unlocked = new List<string>() };
}

Se escribe como su propio archivo .sav en cada guardado y se lista en el manifiesto. ResetSave le da un estado limpio cuando empieza una partida nueva.

Formato del archivo de guardado​

Todo archivo .sav es JSON plano, escrito por MCESavesSerializer, así que se puede inspeccionar al depurar. La forma exacta depende del objeto al que pertenece (los datos globales, el equipo y el resto tienen cada uno su propio archivo).

No edites los archivos de guardado

Aunque los archivos de guardado son JSON legible, editarlos manualmente puede causar problemas si introduces datos inválidos (p. ej., un movimiento que no existe en la base de datos). Usa herramientas del juego o comandos de depuración en su lugar.

Buenas prácticas​

  1. Prueba los ciclos de guardar/cargar. Guarda, sal, vuelve a cargar y verifica que todo esté intacto.
  2. Usa GameVariables para las banderas de eventos. Persisten automáticamente y se integran con CommandGraph.
  3. Planifica la migración de guardados. Si cambias el formato en una actualización, agrega un paso de migración.
  4. No renombres los nombres de asset de especies, movimientos, habilidades u objetos una vez que los jugadores tengan guardados.
  5. Gestiona con elegancia los fallos de carga. Un guardado dañado o interrumpido se rechaza con un diálogo de error en lugar de cargarse parcialmente.