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.
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:
- Punto de partida: El agente comienza en el centro del área del mapa.
- Excavación: En cada paso, marca la posición actual como suelo/piso.
- Movimiento: Elige dar un paso hacia una de las 4 direcciones cardinales (Arriba, Abajo, Izquierda o Derecha).
- 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.
- 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.

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:
- 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)).
- En el inspector de PisoLayer, configura el TileSet del piso (usando el source_id = 1 en la coordenada atlas Vector2i(0, 0)).
- Ajusta el orden de renderizado (Z-Index) para garantizar que las capas queden alineadas según tu estilo visual.

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.

5. Consejos de optimización y próximos pasos
-
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)$.
-
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.
-
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.
