Sistema de Checkpoints

Un servicio de checkpoints por slots que separa la regla de negocio de cuándo guardar del detalle de almacenamiento de cómo y dónde.

Para qué sirve este sistema

Todo juego que permite a los jugadores guardar su progreso termina escribiendo la misma infraestructura. La gestión de slots, la serialización, las rutas de archivo, las escrituras atómicas y la validación de carga se acumulan en una masa enredada que es difícil de probar y aún más difícil de sustituir cuando cambia el medio de almacenamiento.

El sistema de Checkpoints de Serenity ofrece una única interfaz de servicio a la que llamar al guardar, cargar o eliminar el estado del juego. El blob serializado se trata como un array de bytes opaco, de modo que la regla de negocio que determina cuándo se puede tomar un checkpoint está completamente desacoplada del backend de almacenamiento que lo persiste.

El problema en Unity

Un sistema de guardado en Unity que crece de forma orgánica tiende a mezclar responsabilidades. El código que decide cuándo guardar un checkpoint acaba sabiendo sobre rutas de archivo, formatos JSON o claves de PlayerPrefs. El código que lee un slot de guardado acaba deserializando directamente dentro de un MonoBehaviour. Cuando se necesita añadir un nuevo slot, cambiar el backend de almacenamiento o soportar guardados en la nube, el alcance del impacto es impredecible.

Sin un límite claro entre el disparo del guardado y el mecanismo de almacenamiento, las pruebas son manuales y cada cambio de almacenamiento requiere tocar la lógica del juego. El resultado son bugs de guardado que solo aparecen en ciertas plataformas o tras secuencias de sesión específicas.

Cómo lo aborda Serenity

Serenity expone el comportamiento de checkpoint a través de ICheckpointService, que ofrece Save, TryLoad, HasCheckpoint, Delete y QueryAvailable. Los slots se modelan mediante el enum CheckpointSlot, que define Auto, Manual1, Manual2, Manual3 y QuickSave. Cada guardado produce un CheckpointId y se asocia con un valor CheckpointMetadata que registra un timestamp, una etiqueta y un nombre de fase.

El lado del almacenamiento se abstrae detrás de ICheckpointStore, y el modelo de lectura inmutable devuelto en la carga es CheckpointSnapshot, que transporta el slot, el array de bytes opaco y los metadatos. La infraestructura concreta usa UnityCheckpointStore conectado a través de UnityCheckpointInstaller, situado sobre la jerarquía de ports de Persistence y FilePersistence para escrituras atómicas en archivo.

Cómo encaja en Serenity

El sistema de Checkpoints vive en el namespace Serenity.Checkpoint y sigue la estructura por capas de la foundation. La capa de Dominio define el enum CheckpointSlot y los value objects CheckpointId, CheckpointMetadata y CheckpointSnapshot. La capa de Aplicación expone ICheckpointService e ICheckpointStore como contratos de servicio y almacenamiento. La capa de Infraestructura proporciona UnityCheckpointService y UnityCheckpointStore. La capa de Instalación conecta todo a través de CheckpointInstaller y UnityCheckpointInstaller.

Checkpoint se compone con la jerarquía de ports de Persistence — IPersistenceStore, IBlobStore, IKeyValueStore — y con FilePersistence, que proporciona escrituras atómicas en archivo a través de FileStore e IFileWriterService. Esto significa que cambiar el backend de almacenamiento es cuestión de proporcionar una implementación diferente de ICheckpointStore sin tocar ninguna lógica de negocio.

Flujo de trabajo práctico

  1. Implementa o configura el serializador del estado del juego para producir un array de bytes que represente el estado actual.
  2. Llama a ICheckpointService.Save con el CheckpointSlot de destino, el array de bytes y un CheckpointMetadata que describa el punto de guardado.
  3. En la carga, llama a TryLoad con un CheckpointSlot o un CheckpointId y recibe un CheckpointSnapshot con los datos opacos y sus metadatos.
  4. Usa QueryAvailable para listar todos los checkpoints existentes y construir la interfaz de slots de guardado a partir del array de CheckpointMetadata devuelto.
  5. Llama a HasCheckpoint antes de sobreescribir un slot para detectar conflictos y solicitar confirmación al jugador.
  6. Llama a Delete para eliminar un slot cuando el jugador borra un archivo de guardado.

Qué incluye

  • Servicio de checkpoint ICheckpointService con Save, TryLoad, HasCheckpoint, Delete y QueryAvailable
  • Enum de slot CheckpointSlot que define Auto, Manual1, Manual2, Manual3 y QuickSave
  • Modelo de lectura inmutable CheckpointSnapshot con slot, array de bytes opaco y metadatos
  • Value object CheckpointMetadata con ticks de timestamp, etiqueta y nombre de fase
  • Identificador estable CheckpointId como struct inmutable con generación de GUID
  • Port de almacenamiento ICheckpointStore completamente desacoplado de las reglas de negocio
  • Infraestructura concreta UnityCheckpointStore y UnityCheckpointInstaller para proyectos Unity
  • Composición con FilePersistence y FileStore para escrituras atómicas y seguras en plataforma

Cuándo usarlo

  • Juegos que necesitan slots de guardado con nombre — auto-guardado, guardado rápido y múltiples slots manuales — sin acoplar la lógica de slots a la E/S de archivos.
  • Proyectos que necesitan cambiar o combinar backends de almacenamiento, por ejemplo pasar de archivos locales a guardados en la nube, sin reescribir la lógica del juego.
  • Bases de código que quieren el comportamiento de guardado y carga cubierto por pruebas unitarias que se ejecuten sin un sistema de archivos.
  • Equipos que quieren una interfaz de guardado coherente utilizable en diferentes escenas y modos de juego, coordinada con Game Session y Game Settings.

Sistemas relacionados

Usa Serenity cuando quieras un comportamiento de guardado y carga que ya hable con la gestión de sesión y los ajustes del juego, pero que siga tratando el formato de serialización y el medio de almacenamiento como detalles intercambiables de forma independiente.

Volver a la página principal