Como criar uma dungeon procedural rápida na godot 4 com algoritmo de random walk
Neste artigo, você vai aprender a implementar uma masmorra procedural simples e veloz no Godot 4 utilizando o algoritmo de Random Walk (Caminhada Aleatória) e os nós modernos de TileMapLayer.
A geração procedural de mapas é um dos pilares do desenvolvimento de jogos roguelike, RPGs de exploração e protótipos de game jams. Criar masmorras e cavernas manualmente consome muito tempo; no entanto, algoritmos simples e diretos conseguem entregar layouts orgânicos, navegáveis e infinitamente rejogáveis com pouquíssimas linhas de código.
Neste artigo, você vai aprender a implementar uma masmorra procedural simples e veloz no Godot 4 utilizando o algoritmo de Random Walk (Caminhada Aleatória) e os nós modernos de TileMapLayer.
1. O Conceito: Como Funciona o Random Walk?
O algoritmo de Random Walk funciona simulando um "minerador" ou agente que se move em uma grade: 1. Ponto de Partida: O agente começa no centro da área do mapa. 2. Escavação: A cada passo, ele marca a posição atual como chão/piso. 3. Movimento: Ele escolhe dar um passo para uma das 4 direções cardinais (Cima, Baixo, Esquerda ou Direita). 4. Mudança de Direção: Há uma probabilidade configurada para o agente manter o rumo ou virar para um novo lado, criando corredores ou salas mais densas. 5. Geração das Paredes: Ao final dos passos, o algoritmo analisa todas as células adjacentes (as 8 direções em volta de cada chão) e posiciona blocos de parede onde ainda não houver chão.

2. Preparando a Cena na Godot 4
Com a arquitetura moderna do Godot 4, o antigo nó único de TileMap deu lugar ao uso modular de TileMapLayer, que oferece melhor organização, performance e controle de camadas. Estrutura de Nós Crie uma nova cena 2D com a seguinte árvore de nós:
DungeonGenerator (Node2D) - Anexe o script aqui
├── PisoLayer (TileMapLayer)
└── ParedeLayer (TileMapLayer)
Configuração dos Tilesets:
- No inspetor do ParedeLayer, crie ou atribua um TileSet com o seu sprite de parede (usando o source_id = 0 na coordenada atlas Vector2i(0, 0)).
-
No inspetor do PisoLayer, configure o TileSet do piso (usando o source_id = 1 na coordenada atlas Vector2i(0, 0)).
-
Ajuste a ordem de renderização (Z-Index) para garantir que as camadas fiquem alinhadas conforme seu estilo visual.

3. O Script Completo (GDScript)
Anexe o seguinte script ao nó raiz da sua cena (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
# Vetores de direção cardinal (para o random walk)
const DIRECOES = [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT]
# 8 direções (incluindo diagonais) para envolver completamente os pisos com 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:
# Pressione Espaço / Enter (ui_accept) para regenerar o mapa em tempo 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 o agente aos limites da 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 vizinha a um piso que não seja piso, coloca uma parede
for pos in celulas_piso:
for dir in DIRECOES_VIZINHOS:
var vizinho = pos + dir
if not celulas_piso.has(vizinho):
# Tile de parede (source_id = 0, coordenada atlas (0, 0))
parede_layer.set_cell(vizinho, 0, Vector2i(0, 0))
4. Entendendo os Parâmetros
Você pode customizar o estilo da sua dungeon diretamente no painel Inspector da Godot, sem precisar alterar código: total_passos: Define a extensão da masmorra. Valores baixos (ex: 400 a 600) criam cavernas compactas e claustrofóbicas; valores altos (ex: 1500 a 3000) geram catacumbas extensas e intrincadas. mudar_direcao_chance: Valores baixos (0.05 a 0.15): O agente mantém a direção por mais tempo, criando corredores longos e retos. Valores altos (0.4 a 0.6): O agente muda constantemente de direção, cavando câmaras abertas e salões amplos. largura_mapa / altura_mapa: Mantêm o gerador confinado dentro de uma margem segura via clampi(), impedindo que os tiles se espalhem indefinidamente.

5. Dicas de Otimização e Próximos Passos
-
Uso de Dicionários / Dictionary para Mapas Grandes: Se você expandir total_passos para mais de 10.000, verificar piso.has(...) em um Array pode ficar mais lento à medida que a lista cresce ($O(n)$). Substituir celulas_piso por um Dictionary onde as chaves são as coordenadas (celulas_piso[pos] = true) garante verificações instantâneas em $O(1)$.
-
Auto-tiling e Terrains: Ao invés de pintar diretamente um único tile (0, 0), você pode vincular o preenchimento aos recursos de Terrains (Bitmask) do Godot 4 para que as quinas, paredes e bordas se conectem
com artes variadas e cantos arredondados automaticamente. 3. Spawn de Jogador e Inimigos: O ponto de spawn inicial do seu jogador pode ser simplesmente celulas_piso.front(), enquanto o portal de saída ou o chefe da fase pode ser posicionado em celulas_piso.back().
