Generación procedural de mazmorras en Godot 4: guía rápida

Aprende la generación procedural de mazmorras en Godot 4 con el algoritmo Random Walk y los nodos TileMapLayer para crear mapas 2D rápidos.

Generación procedural de mazmorras en Godot 4: guía rápida
Imagen ilustrativa generada por IA (maiastudios.com.br)

La generación procedural de mapas es uno de los pilares del desarrollo de videojuegos roguelike, RPGs de exploración y prototipos para game jams. Crear mazmorras y cuevas manualmente consume mucho tiempo; sin embargo, los algoritmos sencillos y directos logran entregar diseños orgánicos, navegables e infinitamente rejugables con muy pocas líneas de código.

En este artículo, vas a aprender a implementar la generación procedural de mazmorras en Godot 4 de forma sencilla y veloz utilizando el algoritmo de Random Walk (Caminata Aleatoria) y los nodos modernos de TileMapLayer.

1. El concepto: ¿cómo funciona el Random Walk en la generación procedural de mazmorras en Godot 4?

El algoritmo de Random Walk funciona simulando un "minero" o agente que se mueve en una cuadrícula:

  1. Punto de partida: El agente comienza en el centro del área del mapa.
  2. Excavación: En cada paso, marca la posición actual como suelo/piso.
  3. Movimiento: Elige dar un paso hacia una de las 4 direcciones cardinales (Arriba, Abajo, Izquierda o Derecha).
  4. Cambio de dirección: Existe una probabilidad configurada para que el agente mantenga el rumbo o gire hacia un nuevo lado, creando pasillos o salas más densas.
  5. Generación de paredes: Al finalizar los pasos, el algoritmo analiza todas las células adyacentes (las 8 direcciones alrededor de cada piso) y posiciona bloques de pared donde aún no haya piso.
primera mazmorra procedural en godot 4.
Fuente (Archivo personal/maiastudios.com.br)

2. Preparando la escena en Godot 4

Con la arquitectura moderna de Godot 4, el antiguo nodo único de TileMap dio paso al uso modular de TileMapLayer, el cual ofrece mejor organización, rendimiento y control de capas.

Estructura de nodos

Crea una nueva escena 2D con el siguiente árbol de nodos:

DungeonGenerator (Node2D) - Adjunta el script aquí
├── PisoLayer (TileMapLayer)
└── ParedeLayer (TileMapLayer)

Configuración de los TileSets:

  1. En el inspector de ParedeLayer, crea o asigna un TileSet con tu sprite de pared (usando el source_id = 0 en la coordenada atlas Vector2i(0, 0)).
  2. En el inspector de PisoLayer, configura el TileSet del piso (usando el source_id = 1 en la coordenada atlas Vector2i(0, 0)).
  3. Ajusta el orden de renderizado (Z-Index) para garantizar que las capas queden alineadas según tu estilo visual.
segunda mazmorra procedural en godot 4.
Fuente (Archivo personal/maiastudios.com.br)

3. El script completo (GDScript)

Adjunta el siguiente script al nodo raíz de tu escena (DungeonGenerator):

extends Node2D
@export_group("Configurações da Dungeon")
@export var largura_mapa: int = 120
@export var altura_mapa: int = 120
@export var total_passos: int = 1024
@export var mudar_direcao_chance: float = 0.25
@onready var piso_layer: TileMapLayer = $PisoLayer
@onready var parede_layer: TileMapLayer = $ParedeLayer
# Vectores de dirección cardinal (para el random walk)
const DIRECOES = [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT]
# 8 direcciones (incluyendo diagonales) para rodear completamente los pisos con paredes
const DIRECOES_VIZINHOS = [
Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT,
Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1)
]
func _ready() -> void:
randomize()
gerar_dungeon()
func _unhandled_input(event: InputEvent) -> void:
# Presiona Espacio / Enter (ui_accept) para regenerar el mapa en tiempo real
if event.is_action_pressed("ui_accept"):
gerar_dungeon()
func gerar_dungeon() -> void:
piso_layer.clear()
parede_layer.clear()
var celulas_piso: Array[Vector2i] = executar_random_walk()
preencher_tiles_piso(celulas_piso)
gerar_paredes(celulas_piso)
func executar_random_walk() -> Array[Vector2i]:
var piso: Array[Vector2i] = []
var posicao_atual = Vector2i(largura_mapa / 2, altura_mapa / 2)
var direcao_atual = DIRECOES.pick_random()
for passo in range(total_passos):
if not piso.has(posicao_atual):
piso.append(posicao_atual)

if randf() < mudar_direcao_chance:
direcao_atual = DIRECOES.pick_random()
posicao_atual += direcao_atual
# Limita al agente a los límites de la matriz
posicao_atual.x = clampi(posicao_atual.x, 1, largura_mapa - 2)
posicao_atual.y = clampi(posicao_atual.y, 1, altura_mapa - 2)
return piso
func preencher_tiles_piso(celulas: Array[Vector2i]) -> void:
# Tile de piso (source_id = 1, coordenada atlas (0, 0))
for pos in celulas:
piso_layer.set_cell(pos, 1, Vector2i(0, 0))
func gerar_paredes(celulas_piso: Array[Vector2i]) -> void:
# Para cada célula vecina a un piso que no sea piso, coloca una pared
for pos in celulas_piso:
for dir in DIRECOES_VIZINHOS:
var vizinho = pos + dir
if not celulas_piso.has(vizinho):
# Tile de pared (source_id = 0, coordenada atlas (0, 0))
parede_layer.set_cell(vizinho, 0, Vector2i(0, 0))

4. Entendiendo los parámetros

Puedes personalizar el estilo de tu mazmorra directamente en el panel Inspector de Godot, sin necesidad de modificar código:

total_passos: Define la extensión de la mazmorra. Valores bajos (ej: 400 a 600) crean cuevas compactas y claustrofóbicas; valores altos (ej: 1500 a 3000) generan catacumbas extensas e intrincadas.

mudar_direcao_chance:

  • Valores bajos (0.05 a 0.15): El agente mantiene la dirección por más tiempo, creando pasillos largos y rectos.
  • Valores altos (0.4 a 0.6): El agente cambia constantemente de dirección, excavando cámaras abiertas y salones amplios.

largura_mapa / altura_mapa: Mantienen al generador confinado dentro de un margen seguro mediante clampi(), evitando que los tiles se esparzan indefinidamente.

Script que crea la mazmorra procedural en godot 4.
Fuente (Archivo personal/maiastudios.com.br)

5. Consejos de optimización y próximos pasos

  1. Uso de diccionarios / Dictionary para mapas grandes: Si expandes total_passos a más de 10.000, verificar piso.has(...) en un Array puede volverse más lento a medida que la lista crece ($O(n)$). Reemplazar celulas_piso por un Dictionary donde las claves sean las coordenadas (celulas_piso[pos] = true) garantiza verificaciones instantáneas en $O(1)$.

  2. Auto-tiling y Terrains: En lugar de pintar directamente un único tile (0, 0), puedes vincular el relleno a los recursos de Terrains (Bitmask) de Godot 4 para que las esquinas, paredes y bordes se conecten automáticamente con arte variado y esquinas redondeadas.

  3. Aparición de jugador y enemigos: El punto de aparición inicial de tu jugador puede ser simplemente celulas_piso.front(), mientras que el portal de salida o el jefe de la fase puede posicionarse en celulas_piso.back(). Con estas técnicas, la generación procedural de mazmorras en Godot 4 te brindará la flexibilidad necesaria para construir mundos dinámicos e impredecibles en tus proyectos.

tercera mazmorra procedural en godot 4.
Fuente (Archivo personal/maiastudios.com.br)

¿Te gustó? Compártelo