tutoriales.com

Unreal Engine: Creación de un Sistema de Carga/Guardado de Partidas con SaveGame y Blueprint

Este tutorial te guiará paso a paso en la creación de un sistema de guardado y carga de partidas en Unreal Engine 5, utilizando Blueprints y el objeto SaveGame. Aprenderás a persistir datos importantes del juego, como la posición del jugador, el inventario y el progreso de la partida, para ofrecer una experiencia de juego fluida y memorable.

Intermedio18 min de lectura24 views
Reportar error

Introducción al Guardado y Carga de Partidas en Unreal Engine 🎮

Crear un sistema de guardado y carga de partidas es fundamental para casi cualquier videojuego. Permite a los jugadores continuar su progreso donde lo dejaron, mejorando significativamente la experiencia de usuario. En Unreal Engine, esto se logra principalmente a través del objeto SaveGame, que actúa como un contenedor para los datos que queremos persistir.

En este tutorial, exploraremos cómo implementar un sistema robusto utilizando Blueprints. Cubriremos la creación de un objeto SaveGame, la gestión de diferentes ranuras de guardado y cómo cargar y aplicar esos datos al estado actual del juego.

¿Por qué es importante un sistema de guardado? 🤔

  • Persistencia: Mantiene el progreso del jugador entre sesiones de juego.
  • Comodidad: Permite a los jugadores tomarse un descanso y volver más tarde.
  • Flexibilidad: Facilita la implementación de múltiples partidas guardadas o puntos de control.
  • Experiencia de Usuario: Reduce la frustración de perder el progreso y mejora la retención de jugadores.
💡 Consejo: Planifica con antelación qué datos necesitas guardar. No todos los datos del juego necesitan ser persistidos; solo aquellos que definen el estado crítico del progreso del jugador.

1. Conceptos Fundamentales de SaveGame en Unreal Engine ✨

Antes de sumergirnos en la implementación, es crucial entender los componentes clave involucrados en el sistema de guardado de Unreal Engine.

El Objeto SaveGame 📖

El SaveGame es la piedra angular de nuestro sistema. Es un objeto de Unreal Engine que se utiliza para almacenar los datos que deseamos guardar. Es como una 'caja' donde metemos toda la información relevante antes de 'cerrarla' (guardarla en disco) y 'abrirla' (cargarla desde disco).

Los datos que se guardan en el SaveGame son típicamente variables que representan el estado del juego: salud del jugador, posición, inventario, misiones completadas, etc. Estas variables deben ser declaradas dentro de la clase SaveGame.

GameInstance y GameState 📌

El GameInstance es un objeto que persiste durante toda la vida de la aplicación del juego, incluso a través de diferentes mapas. Es un lugar excelente para almacenar referencias al sistema de guardado o datos globales que necesitan persistir durante una sola sesión de juego (por ejemplo, qué ranura de guardado está activa actualmente).

El GameState (y PlayerState) son objetos que persisten durante la vida de un solo nivel (mapa) en el juego. Son útiles para almacenar el estado actual del juego o del jugador dentro de un nivel, pero no persisten a través de cargas de nivel. Para que sus datos persistan a través de niveles y sesiones, deberás copiarlos hacia/desde tu SaveGame.

🔥 Importante: El SaveGame no se guarda automáticamente. Debes serializarlo explícitamente a disco y deserializarlo desde disco usando funciones como SaveGameToSlot y LoadGameFromSlot.

Flujo Básico del Sistema de Guardado/Carga

SISTEMA DE GUARDADO Inicio ¿Existe SaveGame? Crear Nuevo Cargar Existente Copiar Datos al SaveGame Guardar a Ranura Fin SISTEMA DE CARGA Inicio Cargar de Ranura Copiar SaveGame al Juego Fin No

2. Creando Nuestra Clase SaveGame en Blueprint 🛠️

El primer paso es crear nuestra propia clase SaveGame que contendrá todas las variables que queremos guardar. Para este tutorial, guardaremos la posición del jugador, su salud y un array simple de nombres de objetos en el inventario.

Paso 2.1: Crear el Blueprint BP_MySaveGame

  1. En el Content Browser, haz clic derecho y selecciona Blueprint Class.
  2. En la ventana Pick Parent Class, busca y selecciona SaveGame. Nombralo BP_MySaveGame.
  3. Abre BP_MySaveGame.

Paso 2.2: Añadir Variables al BP_MySaveGame

Dentro de BP_MySaveGame, añade las siguientes variables:

  • PlayerLocation: Tipo Vector. Representará la última posición guardada del jugador.
  • PlayerHealth: Tipo Float. La salud actual del jugador.
  • InventoryItems: Tipo Array de String. Una lista simple de ítems en el inventario.
  • SaveGameSlotName: Tipo String. El nombre de la ranura de guardado.
  • SaveGameSlotIndex: Tipo Integer. El índice de la ranura de guardado (útil para UIs).
📌 Nota: Todas las variables que quieras guardar DEBEN ser declaradas dentro de tu clase SaveGame.

3. Implementando la Lógica de Guardado en Blueprint 💾

Ahora que tenemos nuestro objeto SaveGame, necesitamos la lógica para llenarlo con datos y guardarlo en un slot.

Paso 3.1: Función para Guardar Partida (Save Game) en PlayerController o GameMode

Es una buena práctica encapsular la lógica de guardado en un lugar central, como tu PlayerController (si el guardado es por jugador) o GameMode (para datos de juego global). Para este ejemplo, lo haremos en PlayerController.

  1. Abre tu PlayerController Blueprint (o crea uno si no tienes).

  2. Crea una nueva función llamada SaveGameData.

  3. Dentro de SaveGameData, implementa la siguiente lógica:

    • Comprobar si existe SaveGame en el Slot: Usa DoesSaveGameExist con el SaveGameSlotName (por ejemplo, "Slot1").
    • Crear o Cargar SaveGame:
      • Si existe: LoadGameFromSlot (clase BP_MySaveGame).
      • Si NO existe: CreateSaveGameObject (clase BP_MySaveGame).
    • Asignar datos al SaveGame Objeto:
      • Obtén la ubicación actual del jugador (GetActorLocation de Self si es el PlayerController o del Pawn poseído).
      • Obtén la salud del jugador (asumiendo que tienes una variable de salud en tu PlayerController o Pawn).
      • Obtén los ítems del inventario (asumiendo que tienes una lista de ítems).
      • Setea estas variables en tu BP_MySaveGame (por ejemplo, Set PlayerLocation).
    • Guardar el SaveGame en el Slot: Usa SaveGameToSlot, pasando el objeto SaveGame recién llenado y el SaveGameSlotName y SaveGameSlotIndex.
    • 💡 Consejo: Puedes usar el `PlayerState` para obtener la salud, el inventario, etc., ya que este persiste a través de los respawns del jugador en un mismo nivel.
Inicio ¿Existe partida guardada? (SlotName) NO CreateSaveGameObject (BP_MySaveGame) LoadGameFromSlot (BP_MySaveGame) Cast To BP_MySaveGame Get PlayerPawn · GetActorLocation · GetHealth · GetInventoryItems Set variables en BP_MySaveGame · PlayerLocation · PlayerHealth · InventoryItems SaveGameToSlot (BP_MySaveGame, SlotName, SlotIndex) Fin

Paso 3.2: Disparar la Función de Guardado

Puedes llamar a SaveGameData en diferentes momentos:

  • Al presionar una tecla (por ejemplo, P para "Guardar") en PlayerController.
  • Al alcanzar un punto de control (Checkpoint).
  • Al salir del juego (evento OnEndPlay en GameMode o GameInstance).
  • Mediante un widget de menú de pausa.
// Ejemplo de Blueprint para llamar a SaveGameData al presionar 'P'
Event Keyboard P
  Pressed
    Call SaveGameData (Target: Self)

4. Implementando la Lógica de Carga en Blueprint ↩️

Cargar una partida es el proceso inverso: recuperamos el SaveGame del disco y usamos sus datos para restaurar el estado del juego.

Paso 4.1: Función para Cargar Partida (Load Game) en PlayerController o GameMode

  1. En tu PlayerController Blueprint, crea una nueva función llamada LoadGameData.

  2. Implementa la siguiente lógica:

    • Comprobar si existe SaveGame: Usa DoesSaveGameExist con el SaveGameSlotName.
    • Cargar SaveGame: Si existe, usa LoadGameFromSlot (clase BP_MySaveGame).
    • Aplicar datos del SaveGame al juego:
      • Obtén la referencia al BP_MySaveGame cargado (recuerda Cast To BP_MySaveGame).
      • Obtén PlayerLocation del SaveGame y usa SetActorLocation en el PlayerPawn (GetControlledPawn).
      • Obtén PlayerHealth y actualiza la salud del PlayerPawn o PlayerState.
      • Obtén InventoryItems y actualiza el inventario del jugador.
    • ⚠️ Advertencia: Al cargar la ubicación del jugador, asegúrate de que el `Pawn` ya esté en el mundo y que la colisión esté desactivada temporalmente si es necesario para evitar problemas de teletransporte.
INICIO ¿Existe partida? (SlotName) Error / Nueva Partida (Mostrar Mensaje) LoadGameFromSlot BP_MySaveGame Cast To BP_MySaveGame Get PlayerPawn (ControlledPawn) Get Variables from SaveGame: Location, Health, Inventory SetActorLocation Set Player Health Set Player Inventory FIN No

Paso 4.2: Disparar la Función de Carga

La función LoadGameData generalmente se llama en los siguientes escenarios:

  • Al iniciar una nueva partida desde el menú principal (después de seleccionar "Cargar Partida").
  • Al morir el jugador y reiniciar desde el último punto de guardado.
  • Al presionar una tecla (por ejemplo, L para "Cargar") en PlayerController (solo para pruebas).
// Ejemplo de Blueprint para llamar a LoadGameData al presionar 'L'
Event Keyboard L
  Pressed
    Call LoadGameData (Target: Self)

5. Gestión de Múltiples Slots de Guardado y UI 📦

Los juegos modernos suelen permitir a los jugadores tener múltiples partidas guardadas. Esto se logra fácilmente con el sistema SaveGame usando diferentes SlotName y SlotIndex.

5.1: Almacenar Información de Slots Disponibles

Para mostrar una lista de partidas guardadas en un menú, necesitarás saber qué slots tienen un SaveGame y quizás alguna metainformación (fecha de guardado, nombre del nivel, etc.).

  • BP_SaveGameMeta (Opcional pero recomendado): Puedes crear otra clase SaveGame llamada BP_SaveGameMeta que solo guarde metadatos para cada slot. Este BP_SaveGameMeta se guardaría en un slot fijo (ej: "MetaSlot") y contendría un Map o Array de estructuras con información de cada BP_MySaveGame (nombre del slot, fecha, captura de pantalla, etc.).

    • Cuando guardas BP_MySaveGame en "Slot1", también actualizas BP_SaveGameMeta para añadir o actualizar la entrada de "Slot1" con sus metadatos.
    • Cuando muestras la UI de "Cargar Partida", cargas BP_SaveGameMeta para poblar la lista de opciones.

5.2: Interfaz de Usuario (UMG) para Guardar/Cargar

  1. Widget WBP_SaveLoadMenu: Crea un widget UMG que contenga botones para "Guardar Juego" y "Cargar Juego", así como una lista de slots de guardado disponibles.
  2. Lógica del Widget:
    • Al inicializar el widget, puedes iterar a través de un rango de posibles SlotIndex (por ejemplo, del 0 al 4) y usar DoesSaveGameExist para determinar qué slots están ocupados.
    • Si un slot existe, cárgalo temporalmente (LoadGameFromSlot) para obtener su SaveGameSlotName y cualquier metadato (como la fecha de guardado si la incluyes en BP_MySaveGame) y muéstralos en la UI.
    • Cuando el jugador selecciona un slot, pasas el SlotName y SlotIndex a tus funciones SaveGameData o LoadGameData en el PlayerController.
Paso 1: Crear Widget WBP_SaveLoadMenu con una lista de botones/entradas.
Paso 2: En la lógica del widget, al construirlo, iterar slots 0-N.
Paso 3: Para cada slot, usar DoesSaveGameExist.
Paso 4: Si existe, LoadGameFromSlot (solo para leer metadatos, no para aplicar al juego).
Paso 5: Mostrar los datos del slot en la UI (ej. "Slot 1 - 10/10/2023 - Nivel 3").
Paso 6: Al hacer clic en un slot, llamar a SaveGameData o LoadGameData en el PlayerController con el SlotName y SlotIndex seleccionados.

6. Consideraciones Avanzadas y Mejores Prácticas 🚀

Guardado Automático (Autosave)

Puedes implementar un guardado automático periódico o en eventos específicos (por ejemplo, al entrar en una nueva área, al completar una misión). Asegúrate de no interrumpir la experiencia del jugador y de que el guardado automático no sobrescriba una partida guardada manual importante sin advertencia.

Seguridad y Anti-Trampas

Los archivos .sav de Unreal Engine se pueden editar fácilmente con un editor hexadecimal si no se cifran. Para juegos que requieren mayor seguridad:

  • Cifrado: Utiliza la opción Encrypt en el nodo SaveGameToSlot o implementa tu propio algoritmo de cifrado/descifrado antes de guardar/cargar.
  • Validación de Checksum: Calcula un checksum de tus datos antes de guardar y verifícalo al cargar para detectar modificaciones.

Qué No Guardar Directamente en SaveGame

  • Referencias a Actores en el Nivel: Guarda identificadores únicos (como FName o un Guid) y luego busca o recrea esos actores al cargar.
  • Datos que pueden ser recalculados: Por ejemplo, si tienes una fórmula para la fuerza de un enemigo basada en el nivel, guarda el nivel y recalcula la fuerza al cargar.
  • Datos de Gran Tamaño: Si el SaveGame se vuelve muy grande, puede ralentizar el proceso de guardado/carga. Optimiza la información que guardas.

Manejo de Errores y Edge Cases

  • Slots corruptos: Implementa un manejo de errores si LoadGameFromSlot falla o devuelve un objeto nulo.
  • Versiones de guardado: Si tu juego evoluciona y añades o quitas variables en BP_MySaveGame, las partidas guardadas antiguas podrían no ser compatibles. Considera un sistema de versionado en tu SaveGame para migrar datos o invalidar partidas antiguas.
Ejemplo de Sistema de Versionado Básico

En tu BP_MySaveGame, añade una variable SaveGameVersion de tipo Integer. Al guardar, siempre establece esta variable a la versión actual. Al cargar, compara la SaveGameVersion cargada con la versión actual del juego. Si son diferentes, puedes implementar lógica para adaptar los datos o notificar al usuario que el guardado es obsoleto.

Barra de Progreso de Guardado/Carga

Para partidas muy grandes, el proceso de guardado o carga puede tardar unos segundos. Es una buena práctica mostrar una barra de progreso o un icono de "guardando..." para que el jugador sepa que el juego no se ha congelado.

Guardando Partida...

Conclusión ✅

Has llegado al final de este tutorial sobre cómo crear un sistema de guardado y carga de partidas en Unreal Engine usando Blueprints. Hemos cubierto desde los conceptos fundamentales del objeto SaveGame hasta la implementación práctica de funciones de guardado y carga, pasando por la gestión de múltiples slots y consideraciones avanzadas. Con este conocimiento, puedes construir un sistema de persistencia robusto para tus juegos, mejorando enormemente la experiencia del jugador.

Experimenta con diferentes tipos de datos y escenarios de guardado para adaptar el sistema a las necesidades específicas de tu proyecto. ¡Feliz desarrollo!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!