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.
¡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
Actorde 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.
🛠️ 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:
PuzzlesSlidingPuzzleBlueprintsMaterialsMeshesTextures
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.
- Crea un cubo básico: En la carpeta
Meshes, haz clic derecho y seleccionaBlueprint Class>All Classesy buscaStaticMeshActor. NómbraloSM_PuzzleBlock. Abrelo y establece suStatic MeshaCube(buscaShape_Cube). - Crea un material: En la carpeta
Materials, haz clic derecho y seleccionaMaterial. NómbraloM_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.
- En la carpeta
Blueprints, haz clic derecho y seleccionaBlueprint Class. EligeActorcomo clase padre y nómbraloBP_SlidingPuzzleBlock. - Abre
BP_SlidingPuzzleBlock.
⚙️ Componentes del Bloque
Static Mesh: Añade un componenteStatic Mesh. NómbraloBlockMesh. Establece suStatic MeshaShape_Cubey su material aM_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.
- Ajusta su escala a
Box Collision: Añade un componenteBox Collision. NómbraloInteractionBox. Hazlo un poco más grande que elBlockMeshpara 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 elBP_PuzzleManagerla 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.
- En la carpeta
Blueprints, haz clic derecho y seleccionaBlueprint Class. EligeActorcomo clase padre y nómbraloBP_SlidingPuzzleManager. - 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 defecto3): El tamaño de la cuadrícula del puzzle (ej. 3x3, 4x4).BlockSpacing(Tipo:Float, por defecto100.0): El espacio entre los centros de los bloques. Esto debe coincidir con el tamaño de tuBlockMesh.PuzzleBlocks(Tipo:ArraydeBP_SlidingPuzzleBlockObject 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 ReferencedeBP_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.
Event BeginPlay- Llama a una función
GeneratePuzzle.
- Llama a una función
✨ Función: GeneratePuzzle
Esta función será responsable de crear los bloques y posicionarlos en la cuadrícula.
Clear ArrayPuzzleBlocks(para re-generar si es necesario).- Bucle
For Loop: Iterar desde0hastaPuzzleGridSize * PuzzleGridSize - 1.- Dentro del bucle, calcula
GridX = Index % PuzzleGridSizeyGridY = Index / PuzzleGridSize. - Calcula la posición mundial
WorldLocationpara el bloque:(GridX * BlockSpacing, GridY * BlockSpacing, 0). Spawn Actor From Class:- Clase:
BlockBlueprintClass. - Transform: Usa la
WorldLocationcalculada. - Owner:
Self(elBP_SlidingPuzzleManager).
- Clase:
Set CurrentGridIndexdel bloque spawnado a(GridX, GridY).Set TargetGridIndexdel bloque spawnado a(GridX, GridY)(inicialmente, todos están en su lugar correcto).Addel bloque spawnado a la matrizPuzzleBlocks.
- Dentro del bucle, calcula
- 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,
Getel último elemento dePuzzleBlocks(índicePuzzleGridSize * PuzzleGridSize - 1).Set bIsEmptyBlockenTruepara ese bloque.Set Actor Hidden In GameaTruepara elBlockMeshde ese bloque (para que no sea visible).Set EmptyBlockIndexa laCurrentGridIndexde ese bloque (ej.(2,2)para 3x3).
🖱️ 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_SlidingPuzzleBlockObject Reference).
Ahora, volvamos al BP_SlidingPuzzleBlock.
- En el evento
OnClicked (with Touch)delInteractionBox. Get Ownery haz unCast To BP_SlidingPuzzleManager.- Si el
Castes exitoso, llama aOnBlockClickeden elBP_SlidingPuzzleManagery pasaSelf(el bloque clicado) comoClickedBlock.
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.
-
Crea una nueva función en
BP_SlidingPuzzleManagerllamadaTryMoveBlock.- Parámetro de entrada:
BlockToMove(Tipo:BP_SlidingPuzzleBlockObject Reference). - Parámetro de salida:
bMoved(Tipo:Boolean).
- Parámetro de entrada:
-
Obtener índices:
BlockCurrentIndex = Get CurrentGridIndexdeBlockToMove.EmptyIndex = Get EmptyBlockIndex.
-
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 a1.0(solo un bloque de distancia).- Y (
Abs(Delta.X) == 1yDelta.Y == 0) O (Abs(Delta.Y) == 1yDelta.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 CurrentGridIndexdeBlockToMoveaEmptyIndex.Set EmptyBlockIndexaBlockCurrentIndex.- Animación de movimiento: Llama a una función
MoveBlockToGridLocation(que crearemos después) para animar el movimiento visualmente. Set bMovedaTrue.
- Llama a una función
CheckForSolution(que crearemos después).
- Intercambiar posiciones:
-
Si la condición es
False(movimiento inválido):Set bMovedaFalse.
-
💫 Función: MoveBlockToGridLocation (Animación)
Esta función moverá visualmente un bloque a una nueva posición en la cuadrícula de forma suave.
-
Crea una nueva función en
BP_SlidingPuzzleManagerllamadaMoveBlockToGridLocation.- Parámetro de entrada:
Block(Tipo:BP_SlidingPuzzleBlockObject Reference). - Parámetro de entrada:
GridLocation(Tipo:Vector2D).
- Parámetro de entrada:
-
Calcular
TargetWorldLocation:(GridLocation.X * BlockSpacing, GridLocation.Y * BlockSpacing, 0). -
Timeline: Añade unTimelinepara animar la posición.- Añade una pista
Floaty nombraAlpha. - 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).
- Añade una pista
-
Updatedel Timeline:Lerp (Vector): Interpola entre la posición actual delBlock(Get Actor Location) y laTargetWorldLocationusando el valorAlphadelTimeline.Set Actor Locationpara elBlockusando el resultado delLerp.
-
Finisheddel 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.
- Crea una nueva función en
BP_SlidingPuzzleManagerllamadaCheckForSolution. Assume bIsSolved = True(variable local temporal).- Bucle
For Each Loopsobre la matrizPuzzleBlocks:- Condición
Branch: Comprueba siCurrentGridIndexes igual aTargetGridIndexpara elArray Elementactual. - Si no son iguales Y el bloque no es el
EmptyBlock:Set bIsSolvedaFalse.Breakel bucle (no necesitamos seguir comprobando).
- Condición
- Después del bucle: Si
bIsSolvedsigue siendoTrue:Print String: "¡Puzzle Resuelto!" (o llama a un eventoOnPuzzleSolved).- Puedes añadir efectos de celebración, abrir una puerta, etc.
🎮 Poniendo el Puzzle en el Nivel
Ahora que tenemos toda la lógica, es hora de probar nuestro puzzle en el nivel.
- Arrastra un
BP_SlidingPuzzleManagerdesde tuContent Browseral nivel. - Selecciona el
BP_SlidingPuzzleManageren elWorld Outliner. - En la pestaña
Details, en la sección de variables, buscaBlockBlueprintClass. - Haz clic en el menú desplegable y selecciona
BP_SlidingPuzzleBlock. - Asegúrate de que
PuzzleGridSizeyBlockSpacingsean valores adecuados (ej. 3 y 100).
🚶 Habilitar Clicks del Jugador
Para que los clics funcionen, tu Player Controller debe estar configurado para detectar clics de ratón.
- Abre tu
Player Controller(normalmenteBP_ThirdPersonCharactertiene su propioPlayerControllero usa elDefaultPlayerControllerdelGameMode). - En
Event BeginPlay:- Llama a
Set Show Mouse Cursory establece el booleano aTrue. - Llama a
Set Input Mode Game OnlyoSet Input Mode UIOnlysi solo quieres interacción con UI. Para nuestro caso,Set Input Mode GameAndUIes una buena opción para poder mover la cámara y hacer clic.- Si usas
Set Input Mode GameAndUI, arrastraGet Player Controlleral pinPlayer Controller. - Arrastra
Get Player Controlleral pinIn Widget to Focus.
- Si usas
- Llama a
¿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.
- En
BP_SlidingPuzzleManager, crea una nueva función llamadaShufflePuzzle. - Esta función debe ser llamada después de
GeneratePuzzleenEvent BeginPlay. - Lógica de
ShufflePuzzle:- Implementa un bucle
For Loopque 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 (entre0yPuzzleGridSize-1en X e Y). - Si es válido, busca el
BP_SlidingPuzzleBlockque tieneCurrentGridIndexigual aPotentialBlockIndex(puedes iterar sobrePuzzleBlockso hacer una función de búsqueda). - Si encuentras el bloque, llama a
TryMoveBlockcon ese bloque. (Ignora el valor de retornobMovedya que solo estamos mezclando).
- Implementa un bucle
🎨 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_PuzzleManageremita partículas de celebración.
📈 Dificultad y Variedad
- Diferentes tamaños: Permitir que el
PuzzleGridSizesea 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
TargetGridIndexyPuzzleGridSizepara 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
bIsFixedalBP_SlidingPuzzleBlocky modificarTryMoveBlockpara 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
CurrentGridIndexde cada bloque y elEmptyBlockIndexpara poder retomar el puzzle donde se dejó.
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
- Unreal Engine: Creación de un Sistema de Diálogos Ramificados con Data Tables y Blueprintintermediate15 min
- Creación de un Sistema de Progresión de Habilidades (Skill Tree) en Unreal Engine 5intermediate18 min
- Unreal Engine: Creando un Sistema de Inventario Dinámico con UMG y C++intermediate25 min
- Dominando el Sistema de Animación en Unreal Engine 5: Desde Retargeting hasta Blend Spacesintermediate20 min
- Unreal Engine: Implementando IA Básica con Behaviour Trees y EQS para Enemigosintermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!