🎮 UnityDocs

Guardado con varias ranuras (save slots)

Amplía el guardado JSON a varias partidas: un archivo por ranura, metadatos para el menú de cargar (nivel, tiempo, fecha) y cómo listar, borrar y detectar ranuras vacías.

🍳 RecetaIntermedio✅ RevisadoUnity 6.0 LTSActualizado 2026-07-15
DificultadConceptual Código Matemáticas
Tiempo📖 Lectura 6 min · ⌨️ Práctica 25 min

La idea#

Partimos del guardado con JSON. Para tener varias partidas, la clave es sencilla: un archivo por ranura (slot_0.json, slot_1.json...) y unos metadatos para que el menú de "Cargar partida" muestre información sin cargar la partida entera.

Paso 1 — Datos con metadatos#

Incluye en tus datos lo que el menú necesita mostrar de un vistazo:

C#
[System.Serializable]
public class DatosPartida
{
    public int version = 1;         // para migrar guardados antiguos
    public string nombre;
    public int nivel;
    public float vida;
    public int monedas;
    public float tiempoJugado;      // segundos
    public string fechaGuardado;    // para ordenar/mostrar
}

Paso 2 — El gestor de ranuras#

C#
using System.IO;
using UnityEngine;

public static class GestorGuardado
{
    static string Ruta(int slot) =>
        Path.Combine(Application.persistentDataPath, $"slot_{slot}.json");

    public static void Guardar(int slot, DatosPartida datos)
    {
        datos.fechaGuardado = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm");
        File.WriteAllText(Ruta(slot), JsonUtility.ToJson(datos, true));
    }

    public static DatosPartida Cargar(int slot)
    {
        string ruta = Ruta(slot);
        if (!File.Exists(ruta)) return null;         // ranura vacía
        return JsonUtility.FromJson<DatosPartida>(File.ReadAllText(ruta));
    }

    public static bool Existe(int slot) => File.Exists(Ruta(slot));

    public static void Borrar(int slot)
    {
        if (File.Exists(Ruta(slot))) File.Delete(Ruta(slot));
    }
}
Métodos estáticos: sin instancia

Aquí el gestor es una clase static: son utilidades puras (leer/escribir archivos) sin estado propio, así que no necesitas ponerla en un GameObject ni un Singleton. GestorGuardado.Guardar(0, datos) y listo.

Paso 3 — El menú de cargar#

Para cada ranura (0, 1, 2...), muestra un botón. Si Existe(slot), carga solo los metadatos para pintar "Nivel 3 · 2h 15m · 2026-07-15"; si no, muestra "Ranura vacía".

C#
void PintarRanura(int slot, TextMeshProUGUI etiqueta)
{
    DatosPartida d = GestorGuardado.Cargar(slot);
    etiqueta.text = d == null
        ? "— Ranura vacía —"
        : $"{d.nombre}  ·  Nivel {d.nivel}  ·  {d.fechaGuardado}";
}

Al pulsar una ranura con partida, cargas sus datos y vas a la escena correspondiente (SceneManager); al pulsar una vacía, empiezas una partida nueva en ese slot.

Paso 4 — Autoguardado y ranura rápida#

Muchos juegos reservan una ranura especial para el autoguardado (por ejemplo slot_auto.json) que se sobrescribe en los checkpoints, además de las ranuras manuales del jugador.

Versionado: pensar en el futuro#

Añade un campo 'version'

Cuando publiques una actualización que cambie la estructura de DatosPartida, los guardados viejos pueden romperse. El campo version te permite detectar un guardado antiguo y migrarlo (rellenar los campos nuevos con valores por defecto) en vez de fallar. Un save system serio siempre versiona.

Errores frecuentes#

  • Un solo archivo para todo (no puedes tener varias partidas).
  • Cargar la partida completa solo para mostrar el menú (carga solo metadatos).
  • Guardar fuera de persistentDataPath (falla en build/móvil).
  • No versionar y romper los guardados al actualizar el juego.
  • No contemplar la ranura vacía (Cargar devuelve null).

Resumen#

Un archivo por ranura (slot_N.json) en persistentDataPath, con metadatos (nivel, tiempo, fecha) para pintar el menú sin cargar la partida entera. Un gestor static con Guardar/Cargar/Existe/Borrar, una ranura de autoguardado aparte, y un campo version para migrar guardados antiguos. Todo construido sobre el guardado JSON básico.

Fuentes y para profundizar