tutoriales.com

Unreal Engine: Creando un Sistema de Puzzles de Bloques Deslizantes con Blueprint

Este tutorial te guiará paso a paso en la creación de un sistema completo de puzzles de bloques deslizantes en Unreal Engine, utilizando exclusivamente Blueprints. Aprenderás a configurar los bloques, detectar movimientos válidos, intercambiar posiciones y verificar la solución del puzzle.

Intermedio20 min de lectura25 views
Reportar error

¡Bienvenido a este tutorial sobre cómo crear un sistema de puzzles de bloques deslizantes en Unreal Engine! 🧩 Este tipo de puzzle es un clásico en muchos videojuegos, ideal para añadir un toque de interactividad y desafío a tus niveles. A lo largo de esta guía, utilizaremos Blueprints, lo que lo hace accesible incluso si no tienes experiencia previa en programación C++.

🎯 ¿Qué aprenderás en este tutorial?

  • Configurar un Actor de puzzle base y sus bloques individuales.
  • Implementar la lógica para seleccionar y mover bloques.
  • Establecer reglas para movimientos válidos (deslizar a un espacio vacío).
  • Realizar el intercambio visual y lógico de la posición de los bloques.
  • Crear un sistema para detectar cuándo el puzzle ha sido resuelto.
  • Añadir retroalimentación visual y efectos básicos.
📌 Nota: Este tutorial asume que tienes un conocimiento básico de la interfaz de Unreal Engine y sabes cómo crear nuevos Blueprints y Actors.

🛠️ Configuración Inicial del Proyecto

Antes de sumergirnos en la lógica, necesitamos preparar nuestro entorno. Si ya tienes un proyecto, puedes usarlo. Si no, crea un nuevo proyecto de Unreal Engine 5.2+ (o la versión más reciente que tengas) basado en la plantilla Blank o Third Person.

📦 Estructura de Carpetas Recomendada

Una buena organización es clave. Crea las siguientes carpetas en Content:

  • Puzzles
    • SlidingPuzzle
      • Blueprints
      • Materials
      • Meshes
      • Textures

Esto nos ayudará a mantener todo ordenado y fácil de encontrar.

🖼️ Creando los Materiales y Meshes Básicos

Para nuestros bloques, usaremos un Static Mesh simple (un cubo) y algunos materiales básicos. Puedes importar tus propios Static Meshes si lo deseas, pero para este tutorial, un cubo será suficiente.

  1. Crea un cubo básico: En la carpeta Meshes, haz clic derecho y selecciona Blueprint Class > All Classes y busca StaticMeshActor. Nómbralo SM_PuzzleBlock. Abrelo y establece su Static Mesh a Cube (busca Shape_Cube).
  2. Crea un material: En la carpeta Materials, haz clic derecho y selecciona Material. Nómbralo M_PuzzleBlock_Base. Abrelo y dale un color base simple (e.g., gris claro). Puedes añadir un parámetro de color para variar los colores de los bloques si lo deseas. Para este tutorial, usaremos un color uniforme.
¿Por qué un StaticMeshActor y no solo Static Mesh?Es más fácil de manipular en el nivel si ya tiene componentes de Actor. Para la base de nuestro puzzle, usaremos `StaticMeshActor` como padre y luego crearemos un Blueprint para los bloques interactivos. Puedes usar solo `Static Mesh` y luego envolverlo en un Blueprint `Actor` más tarde si prefieres.

🧱 Creando el Blueprint del Bloque de Puzzle

Este será el Actor individual que representará cada bloque en nuestro puzzle.

  1. En la carpeta Blueprints, haz clic derecho y selecciona Blueprint Class. Elige Actor como clase padre y nómbralo BP_SlidingPuzzleBlock.
  2. Abre BP_SlidingPuzzleBlock.

⚙️ Componentes del Bloque

  • Static Mesh: Añade un componente Static Mesh. Nómbralo BlockMesh. Establece su Static Mesh a Shape_Cube y su material a M_PuzzleBlock_Base (o el que hayas creado).
    • Ajusta su escala a (X=0.95, Y=0.95, Z=0.1) para que parezca una losa. Esto también dejará un pequeño margen entre bloques.
  • Box Collision: Añade un componente Box Collision. Nómbralo InteractionBox. Hazlo un poco más grande que el BlockMesh para facilitar la interacción. Esto será clave para detectar clics del jugador.

🔔 Eventos del Bloque

Necesitamos que el bloque sea interactivo. En la pestaña Details del InteractionBox, desplázate hacia abajo y selecciona OnClicked (with Touch).

Este evento se disparará cuando el jugador haga clic en el bloque. Por ahora, déjalo vacío. Lo conectaremos a nuestro Actor principal del puzzle.

📊 Variables del Bloque

Necesitaremos algunas variables para gestionar el estado y la posición del bloque:

  • CurrentGridIndex (Tipo: Vector2D): Representa la posición actual del bloque en la cuadrícula lógica del puzzle (ej. (0,0), (1,0)).
  • TargetGridIndex (Tipo: Vector2D): La posición en la cuadrícula donde el bloque debería estar cuando el puzzle está resuelto. (Haremos que el BP_PuzzleManager la asigne).
  • bIsEmptyBlock (Tipo: Boolean): Indica si este bloque es el espacio vacío del puzzle. Solo habrá uno en cada puzzle.

Compila y guarda el BP_SlidingPuzzleBlock.


🧩 Creando el Blueprint del Puzzle Manager

Este será el Actor principal que gestionará la lógica de todo el puzzle: la creación de la cuadrícula, la detección de movimientos, el intercambio de bloques y la comprobación de la solución.

  1. En la carpeta Blueprints, haz clic derecho y selecciona Blueprint Class. Elige Actor como clase padre y nómbralo BP_SlidingPuzzleManager.
  2. Abre BP_SlidingPuzzleManager.

⚙️ Componentes del Puzzle Manager

  • Default Scene Root: No necesitamos añadir nada visual directamente aquí, ya que el manager solo gestionará los bloques.

📊 Variables del Puzzle Manager

Este Actor necesitará varias variables para funcionar:

  • PuzzleGridSize (Tipo: Int, por defecto 3): El tamaño de la cuadrícula del puzzle (ej. 3x3, 4x4).
  • BlockSpacing (Tipo: Float, por defecto 100.0): El espacio entre los centros de los bloques. Esto debe coincidir con el tamaño de tu BlockMesh.
  • PuzzleBlocks (Tipo: Array de BP_SlidingPuzzleBlock Object Reference): Una matriz que contendrá todas las referencias a los bloques de nuestro puzzle.
  • EmptyBlockIndex (Tipo: Vector2D): La posición actual del espacio vacío en la cuadrícula.
  • BlockBlueprintClass (Tipo: Class Reference de BP_SlidingPuzzleBlock): Para poder spawnear los bloques dinámicamente. Importante: Asegúrate de hacerla Editable (el ojo abierto) para poder seleccionarla en el editor.

🚀 EventGraph: Lógica de Inicialización

En el EventGraph, usaremos el Event BeginPlay para inicializar nuestro puzzle.

  1. Event BeginPlay
    • Llama a una función GeneratePuzzle.
🔥 Importante: Asegúrate de que `GeneratePuzzle` sea una función, no un evento, para que sea más fácil de gestionar y organizar.

✨ Función: GeneratePuzzle

Esta función será responsable de crear los bloques y posicionarlos en la cuadrícula.

  1. Clear Array PuzzleBlocks (para re-generar si es necesario).
  2. Bucle For Loop: Iterar desde 0 hasta PuzzleGridSize * PuzzleGridSize - 1.
    • Dentro del bucle, calcula GridX = Index % PuzzleGridSize y GridY = Index / PuzzleGridSize.
    • Calcula la posición mundial WorldLocation para el bloque: (GridX * BlockSpacing, GridY * BlockSpacing, 0).
    • Spawn Actor From Class:
      • Clase: BlockBlueprintClass.
      • Transform: Usa la WorldLocation calculada.
      • Owner: Self (el BP_SlidingPuzzleManager).
    • Set CurrentGridIndex del bloque spawnado a (GridX, GridY).
    • Set TargetGridIndex del bloque spawnado a (GridX, GridY) (inicialmente, todos están en su lugar correcto).
    • Add el bloque spawnado a la matriz PuzzleBlocks.
  3. Define el bloque vacío: Después del bucle, elige un bloque para que sea el espacio vacío. Para un puzzle 3x3, el último bloque (índice 8) es un buen candidato. Por ejemplo, Get el último elemento de PuzzleBlocks (índice PuzzleGridSize * PuzzleGridSize - 1).
    • Set bIsEmptyBlock en True para ese bloque.
    • Set Actor Hidden In Game a True para el BlockMesh de ese bloque (para que no sea visible).
    • Set EmptyBlockIndex a la CurrentGridIndex de ese bloque (ej. (2,2) para 3x3).
💡 Consejo: Para obtener la posición de un bloque en la cuadrícula, puedes usar `(Index % PuzzleGridSize, Index / PuzzleGridSize)`.
GeneratePuzzle Clear PuzzleBlocks For Each Index (0 to N*N-1) Calcular GridX, GridY Calcular WorldLocation Spawn BP_SlidingPuzzleBlock Set Current & Target GridIndex Add to PuzzleBlocks Array Seleccionar Último Bloque Set bIsEmptyBlock = True Hide BlockMesh Set EmptyBlockIndex FIN

🖱️ Lógica de Interacción: OnBlockClicked

Cuando un jugador hace clic en un bloque, el BP_PuzzleManager necesita procesar ese clic. En el BP_SlidingPuzzleManager, crea un Custom Event llamado OnBlockClicked.

  • Debe tener un parámetro de entrada: ClickedBlock (Tipo: BP_SlidingPuzzleBlock Object Reference).

Ahora, volvamos al BP_SlidingPuzzleBlock.

  1. En el evento OnClicked (with Touch) del InteractionBox.
  2. Get Owner y haz un Cast To BP_SlidingPuzzleManager.
  3. Si el Cast es exitoso, llama a OnBlockClicked en el BP_SlidingPuzzleManager y pasa Self (el bloque clicado) como ClickedBlock.

Compila y guarda ambos Blueprints.

✅ Función: TryMoveBlock

Esta es la función central que determina si un movimiento es válido y lo ejecuta.

  1. Crea una nueva función en BP_SlidingPuzzleManager llamada TryMoveBlock.

    • Parámetro de entrada: BlockToMove (Tipo: BP_SlidingPuzzleBlock Object Reference).
    • Parámetro de salida: bMoved (Tipo: Boolean).
  2. Obtener índices:

    • BlockCurrentIndex = Get CurrentGridIndex de BlockToMove.
    • EmptyIndex = Get EmptyBlockIndex.
  3. Comprobar validez del movimiento:

    • Calcula la diferencia Delta = BlockCurrentIndex - EmptyIndex.

    • Condición Branch: El movimiento es válido si:

      • Delta.Size() (longitud del vector) es igual a 1.0 (solo un bloque de distancia).
      • Y (Abs(Delta.X) == 1 y Delta.Y == 0) O (Abs(Delta.Y) == 1 y Delta.X == 0). Esto asegura que el movimiento es solo horizontal o vertical, no diagonal.
    • Si la condición es True (movimiento válido):

      • Intercambiar posiciones:
        • Set CurrentGridIndex de BlockToMove a EmptyIndex.
        • Set EmptyBlockIndex a BlockCurrentIndex.
        • Animación de movimiento: Llama a una función MoveBlockToGridLocation (que crearemos después) para animar el movimiento visualmente.
        • Set bMoved a True.
      • Llama a una función CheckForSolution (que crearemos después).
    • Si la condición es False (movimiento inválido):

      • Set bMoved a False.

💫 Función: MoveBlockToGridLocation (Animación)

Esta función moverá visualmente un bloque a una nueva posición en la cuadrícula de forma suave.

  1. Crea una nueva función en BP_SlidingPuzzleManager llamada MoveBlockToGridLocation.

    • Parámetro de entrada: Block (Tipo: BP_SlidingPuzzleBlock Object Reference).
    • Parámetro de entrada: GridLocation (Tipo: Vector2D).
  2. Calcular TargetWorldLocation: (GridLocation.X * BlockSpacing, GridLocation.Y * BlockSpacing, 0).

  3. Timeline: Añade un Timeline para animar la posición.

    • Añade una pista Float y nombra Alpha.
    • Añade dos puntos clave: (Time=0, Value=0) y (Time=0.5, Value=1) (0.5 segundos de duración para la animación).
    • Configura la curva para que sea suave (ej. auto o user).
  4. Update del Timeline:

    • Lerp (Vector): Interpola entre la posición actual del Block (Get Actor Location) y la TargetWorldLocation usando el valor Alpha del Timeline.
    • Set Actor Location para el Block usando el resultado del Lerp.
  5. Finished del Timeline: Cuando la animación termine, no necesitamos hacer nada especial aquí por ahora.

🏆 Función: CheckForSolution

Esta función recorrerá todos los bloques para ver si están en sus posiciones correctas.

  1. Crea una nueva función en BP_SlidingPuzzleManager llamada CheckForSolution.
  2. Assume bIsSolved = True (variable local temporal).
  3. Bucle For Each Loop sobre la matriz PuzzleBlocks:
    • Condición Branch: Comprueba si CurrentGridIndex es igual a TargetGridIndex para el Array Element actual.
    • Si no son iguales Y el bloque no es el EmptyBlock:
      • Set bIsSolved a False.
      • Break el bucle (no necesitamos seguir comprobando).
  4. Después del bucle: Si bIsSolved sigue siendo True:
    • Print String: "¡Puzzle Resuelto!" (o llama a un evento OnPuzzleSolved).
    • Puedes añadir efectos de celebración, abrir una puerta, etc.
CheckForSolution Set bIsSolved = True Bucle: Cada Bloque ¿Fuera de lugar o No es vacío? bIsSolved = False Break Loop No ¿bIsSolved? ¡Puzzle Resuelto! Trigger OnPuzzleSolved No Fin

🎮 Poniendo el Puzzle en el Nivel

Ahora que tenemos toda la lógica, es hora de probar nuestro puzzle en el nivel.

  1. Arrastra un BP_SlidingPuzzleManager desde tu Content Browser al nivel.
  2. Selecciona el BP_SlidingPuzzleManager en el World Outliner.
  3. En la pestaña Details, en la sección de variables, busca BlockBlueprintClass.
  4. Haz clic en el menú desplegable y selecciona BP_SlidingPuzzleBlock.
  5. Asegúrate de que PuzzleGridSize y BlockSpacing sean valores adecuados (ej. 3 y 100).
⚠️ Advertencia: Si no asignas `BlockBlueprintClass`, el `BP_SlidingPuzzleManager` no sabrá qué tipo de bloque spawnear y verás errores.

🚶 Habilitar Clicks del Jugador

Para que los clics funcionen, tu Player Controller debe estar configurado para detectar clics de ratón.

  1. Abre tu Player Controller (normalmente BP_ThirdPersonCharacter tiene su propio PlayerController o usa el DefaultPlayerController del GameMode).
  2. En Event BeginPlay:
    • Llama a Set Show Mouse Cursor y establece el booleano a True.
    • Llama a Set Input Mode Game Only o Set Input Mode UIOnly si solo quieres interacción con UI. Para nuestro caso, Set Input Mode GameAndUI es una buena opción para poder mover la cámara y hacer clic.
      • Si usas Set Input Mode GameAndUI, arrastra Get Player Controller al pin Player Controller.
      • Arrastra Get Player Controller al pin In Widget to Focus.
¿Qué Input Mode debo usar?Si quieres que el jugador pueda interactuar con el mundo y la UI (como nuestro puzzle), `GameAndUI` es la mejor opción. Si solo quieres UI, `UIOnly`. Si solo quieres control de personaje, `GameOnly`.

🔄 Mezclando el Puzzle

Un puzzle deslizante no es divertido si ya está resuelto. Necesitamos una manera de mezclarlo.

  1. En BP_SlidingPuzzleManager, crea una nueva función llamada ShufflePuzzle.
  2. Esta función debe ser llamada después de GeneratePuzzle en Event BeginPlay.
  3. Lógica de ShufflePuzzle:
    • Implementa un bucle For Loop que itere un número de veces (ej. 50-100 para un buen shuffle).
    • Dentro del bucle, encuentra un bloque aleatorio adyacente al EmptyBlock.
    • Para hacer esto, puedes generar una dirección aleatoria ((1,0), (-1,0), (0,1), (0,-1)).
    • Calcula el PotentialBlockIndex = EmptyBlockIndex + RandomDirection.
    • Validar PotentialBlockIndex: Asegúrate de que está dentro de los límites de la cuadrícula (entre 0 y PuzzleGridSize-1 en X e Y).
    • Si es válido, busca el BP_SlidingPuzzleBlock que tiene CurrentGridIndex igual a PotentialBlockIndex (puedes iterar sobre PuzzleBlocks o hacer una función de búsqueda).
    • Si encuentras el bloque, llama a TryMoveBlock con ese bloque. (Ignora el valor de retorno bMoved ya que solo estamos mezclando).
💡 Consejo: Una manera más robusta de mezclar es simular muchos movimientos válidos aleatorios. Esto asegura que el puzzle siempre tendrá solución.
Función: ShufflePuzzle For I = 0 hasta ShuffleCount Generar RandomDirection Calcular PotentialBlockIndex = EmptyBlockIndex + RandomDirection ¿PotentialBlockIndex en límites? Si Find BlockAtPotentialIndex ¿BlockFound? Si Call TryMoveBlock(FoundBlock) No No Siguiente Iteración

🎨 Mejoras y Extensiones (Opcional)

Aquí tienes algunas ideas para llevar tu sistema de puzzles al siguiente nivel:

✨ Retroalimentación Visual y Sonora

  • Efectos de clic: Reproducir un sonido o un pequeño efecto visual cuando un bloque se clica (incluso si el movimiento es inválido).
  • Highlight de bloque: Resaltar el bloque sobre el que el ratón está actualmente, o el bloque que ha sido seleccionado para mover.
  • Animación de victoria: Cuando el puzzle se resuelve, hacer que los bloques brillen, se muevan, o que el BP_PuzzleManager emita partículas de celebración.

📈 Dificultad y Variedad

  • Diferentes tamaños: Permitir que el PuzzleGridSize sea configurable en el editor (ya lo es).
  • Imágenes en los bloques: En lugar de un color sólido, aplicar una textura dividida en partes a los bloques, que solo se vea correctamente cuando el puzzle está resuelto. Para esto, necesitarías un material más complejo que use las TargetGridIndex y PuzzleGridSize para calcular las coordenadas UV correctas para cada bloque.
  • Bloques fijos: Algunos puzzles tienen bloques que no se pueden mover. Podrías añadir una variable bIsFixed al BP_SlidingPuzzleBlock y modificar TryMoveBlock para tenerlo en cuenta.
  • Tiempo límite: Añadir un temporizador para resolver el puzzle.

💾 Guardado y Carga

  • Implementar un sistema de guardado que almacene el CurrentGridIndex de cada bloque y el EmptyBlockIndex para poder retomar el puzzle donde se dejó.
⚠️ Advertencia: Para guardar el estado de los bloques, necesitarás un sistema de guardado más avanzado, posiblemente usando `SaveGame` Actors o serializando la información.

Conclusión

¡Felicidades! 🎉 Has construido un sistema funcional de puzzles de bloques deslizantes en Unreal Engine utilizando únicamente Blueprints. Este es un excelente punto de partida para añadir mecánicas de puzzle a tus juegos, y las bases que has aprendido aquí son aplicables a muchos otros tipos de sistemas interactivos.

Experimenta con las mejoras sugeridas y no dudes en adaptar este sistema a las necesidades específicas de tu proyecto. La flexibilidad de Blueprints te permite expandir y personalizar casi cualquier aspecto de esta implementación.

Si tienes alguna pregunta o encuentras algún desafío, la comunidad de Unreal Engine es muy activa y siempre hay recursos disponibles. ¡Sigue creando y divirtiéndote!

Tutoriales relacionados

Comentarios (0)

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