Geração Procedural de Dungeon na Godot 4 com Random Walk

imagem gerada por ia sobre geração procedural de dungeon na godot-4

Aprenda como fazer geração procedural de dungeon na Godot 4 usando o algoritmo Random Walk, TileMapLayer e GDScript para criar mapas 2D velozes.

crie-geracao-procedural-dungeon-godot-4-1024x576 Geração Procedural de Dungeon na Godot 4 com Random Walk
Imagem ilustrativa gerada por IA simbolizando uma dungeon (maiastudios.com.br)

A criação automática de mapas é um dos pilares do desenvolvimento de jogos roguelike, RPGs de ação e protótipos para game jams. Desenhar dezenas de masmorras e cavernas à mão consome semanas de produção; por outro lado, dominar a geração procedural de dungeon na Godot 4 permite entregar mapas orgânicos, sempre conectáveis e infinitamente rejogáveis com poucas linhas de GDScript. Quando bem calibrado, um gerador procedural transforma cada partida em uma experiência inédita sem exigir gigabytes de fases pré-prontas.

Neste guia prático, você vai aprender do zero como implementar uma masmorra procedural rápida e robusta na Godot 4 utilizando o clássico algoritmo de Random Walk (Caminhada Aleatória, também famoso pelo jogo Nuclear Throne) combinado aos nós modernos de TileMapLayer. Além do código base, veremos como configurar colisões físicas, sementes determinísticas (seeds), otimização de busca e posicionamento inteligente de jogador, inimigos e saída da fase.

1. Como funciona a geração procedural de dungeon na Godot 4 com Random Walk?

Antes de abrir o editor e escrever código, vale entender por que o algoritmo de Random Walk (ou Drunkard’s Walk) é tão querido no desenvolvimento de jogos 2D. Diferente de algoritmos baseados em árvores binárias (Binary Space Partitioning – BSP), que dividem o mapa em retângulos rígidos e artificiais, o Random Walk simula um “minerador” ou escavador virtual que caminha passo a passo sobre uma grade bidimensional.

O ciclo de vida desse escavador segue cinco etapas fundamentais:

  1. Ponto de partida: o agente nasce exatamente no centro da matriz do mapa, garantindo espaço livre para expandir em qualquer direção.
  2. Escavação do piso: a cada passo dado, o algoritmo registra a coordenada atual Vector2i(x, y) como chão navegável.
  3. Escolha de movimento: o agente sorteia ou mantém uma das quatro direções cardinais (UP, DOWN, LEFT ou RIGHT) para avançar uma célula na grade.
  4. Probabilidade de curva: uma variável de controle define a chance de o agente mudar de direção no passo atual. Se a chance for baixa, ele escava túneis longos; se for alta, ele gira repetidamente no mesmo lugar e abre salões amplos.
  5. Envelopamento com paredes: depois que todos os passos terminam, um segundo laço percorre todas as células de piso escavadas, inspeciona as 8 direções vizinhas (incluindo as diagonais) e levanta blocos de parede onde não houver piso.

A maior qualidade matemática desse método é a conectividade garantida. Como o agente caminha continuamente de uma célula para a outra sem se teletransportar, é impossível surgir uma sala isolada inacessível ao jogador — um problema clássico que exige pós-processamento complexo quando usamos ruído de Perlin (FastNoiseLite) ou autômatos celulares (Cellular Automata).

crie-geracao-procedural-dungeon-godot-4-1-1024x576 Geração Procedural de Dungeon na Godot 4 com Random Walk
Imagem da dungeon gerada proceduralmente – Fonte (Acervo pessoal/maiastudios.com.br)

2. Preparando a cena e os nós de TileMapLayer na Godot 4

A partir da Godot 4.3, a arquitetura de mapas em grade recebeu uma melhoria importante: o antigo nó monolítico TileMap foi depreciado em favor do nó modular TileMapLayer. Em vez de esconder várias camadas dentro de um único inspetor confuso, agora cada camada do mapa é um nó independente na árvore de cenas, facilitando o controle de Z-Index, ordenação Y-Sort, materiais de shader e depuração.

Para estruturar nosso gerador, crie uma nova cena 2D na Godot 4 com a seguinte hierarquia de nós:

DungeonGenerator (Node2D) <-- Anexe o script GDScript aqui
├── PisoLayer (TileMapLayer)
└── ParedeLayer (TileMapLayer)

Configurando o TileSet e a colisão das paredes

Para que o código consiga desenhar os blocos na tela, precisamos configurar o recurso TileSet nos dois nós TileMapLayer:

  1. Compartilhando o mesmo TileSet: selecione o nó ParedeLayer, vá até a propriedade Tile Set no Inspector e clique em New TileSet. Defina o Tile Size de acordo com a sua arte em pixel art (por exemplo, 16x16 px ou 32x32 px). Em seguida, copie esse mesmo recurso TileSet para o nó PisoLayer, garantindo que ambos usem a mesma grade métrica.
  2. Adicionando os sprites no painel TileSet: na aba inferior TileSet, arraste a textura da sua parede primeiro (ela receberá automaticamente o identificador source_id = 0 na coordenada de atlas Vector2i(0, 0)). Depois, arraste a textura do piso (que receberá o identificador source_id = 1 na coordenada Vector2i(0, 0)).
  3. Ativando a colisão física nas paredes: ainda no recurso TileSet, expanda a seção Physics Layers no Inspector e clique em Add Element. Na aba inferior TileSet -> Select, clique sobre o tile da parede (source_id = 0), abra a seção Physics -> Physics Layer 0 e desenhe o quadrado de colisão cobrindo todo o tile. Dessa forma, toda parede gerada pelo algoritmo já terá colisão física funcional para o seu CharacterBody2D!
  4. Ajustando a ordem visual: no nó ParedeLayer, configure Ordering -> Z Index como 1 (ou mantenha PisoLayer em 0 e ParedeLayer acima na ordem de renderização) para que as bordas das paredes fiquem perfeitamente destacadas.
crie-geracao-procedural-dungeon-godot-4-2-1024x576 Geração Procedural de Dungeon na Godot 4 com Random Walk
Imagem 2 da dungeon gerada proceduralmente – Fonte (Acervo pessoal/maiastudios.com.br)

3. O script completo em GDScript explicado passo a passo

Agora vamos criar o script dungeon_generator.gd e anexá-lo ao nó raiz DungeonGenerator. Note que nesta versão já utilizamos um Dictionary interno durante a caminhada para evitar verificações lentas em listas grandes, mantendo o retorno como um Array[Vector2i] ordenado para facilitar o posicionamento do jogador e da saída:

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_range(0.0, 1.0, 0.05) var mudar_direcao_chance: float = 0.25
@export var usar_seed_fixa: bool = false
@export var seed_personalizada: int = 12345

@onready var piso_layer: TileMapLayer = $PisoLayer
@onready var parede_layer: TileMapLayer = $ParedeLayer

# Vetores das 4 direções cardinais para o movimento do agente
const DIRECOES: Array[Vector2i] = [
    Vector2i.UP,
    Vector2i.DOWN,
    Vector2i.LEFT,
    Vector2i.RIGHT,
]

# 8 direções (cardinais + diagonais) para fechar as paredes sem frestas
const DIRECOES_VIZINHOS: Array[Vector2i] = [
    Vector2i.UP,
    Vector2i.DOWN,
    Vector2i.LEFT,
    Vector2i.RIGHT,
    Vector2i(-1, -1),
    Vector2i(1, -1),
    Vector2i(-1, 1),
    Vector2i(1, 1),
]


func _ready() -> void:
    if usar_seed_fixa:
        seed(seed_personalizada)
    else:
        randomize()
    gerar_dungeon()


func _unhandled_input(event: InputEvent) -> void:
    # Pressione Espaço ou Enter (ui_accept) para gerar um novo mapa em tempo real
    if event.is_action_pressed("ui_accept"):
        if not usar_seed_fixa:
            randomize()
        gerar_dungeon()


func gerar_dungeon() -> void:
    piso_layer.clear()
    parede_layer.clear()

    var mapa_piso: Dictionary = executar_random_walk()
    preencher_tiles_piso(mapa_piso)
    gerar_paredes(mapa_piso)


func executar_random_walk() -> Dictionary:
    # Usamos Dictionary onde a chave é Vector2i e o valor é true para busca O(1)
    var visitados: Dictionary = {}
    var posicao_atual := Vector2i(largura_mapa / 2, altura_mapa / 2)
    var direcao_atual: Vector2i = DIRECOES.pick_random()

    for passo in range(total_passos):
        visitados[posicao_atual] = true

        if randf() < mudar_direcao_chance:
            direcao_atual = DIRECOES.pick_random()

        posicao_atual += direcao_atual

        # Mantém o escavador dentro da margem de segurança para sobrar espaço para as paredes
        posicao_atual.x = clampi(posicao_atual.x, 1, largura_mapa - 2)
        posicao_atual.y = clampi(posicao_atual.y, 1, altura_mapa - 2)

    return visitados


func preencher_tiles_piso(mapa_piso: Dictionary) -> void:
    # Pinta o tile de piso (source_id = 1, coordenada atlas (0, 0))
    for pos: Vector2i in mapa_piso.keys():
        piso_layer.set_cell(pos, 1, Vector2i(0, 0))


func gerar_paredes(mapa_piso: Dictionary) -> void:
    # Para cada célula adjacente a um piso que não seja piso, posiciona uma parede
    for pos: Vector2i in mapa_piso.keys():
        for dir: Vector2i in DIRECOES_VIZINHOS:
            var vizinho: Vector2i = pos + dir
            if not mapa_piso.has(vizinho):
                # Pinta o tile de parede (source_id = 0, coordenada atlas (0, 0))
                parede_layer.set_cell(vizinho, 0, Vector2i(0, 0))

Por que verificar as 8 direções na hora de erguer as paredes?

Um detalhe técnico que costuma confundir quem está começando é a diferença entre 4 DIREÇÕES e 8 DIREÇÕES. Durante a caminhada, o minerador só pode andar na horizontal ou na vertical; se ele andasse na diagonal, abriria frestas de 1 tile por onde o colisor do jogador ficaria preso.

Porém, na hora de construir as paredes ao redor do chão (gerar_paredes), precisamos checar também as 4 diagonais (Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1) e Vector2i(1, 1)). Isso fecha perfeitamente os cantos externos dos corredores, evitando que o fundo vazio da tela apareça nas quinas das salas.

4. Calibrando os parâmetros para diferentes estilos de jogo

Uma das grandes vantagens de expor as variáveis com @export no GDScript é poder testar dezenas de arquiteturas de mapas direto no painel Inspector da Godot, apertando Espaço (ui_accept) com o jogo aberto para ver o resultado instantaneamente.

Veja na tabela abaixo como os parâmetros alteram a personalidade do nível:

Estilo de Mapatotal_passosmudar_direcao_chanceResultado no Gameplay
Caverna Claustrofóbica400 a 6000.45 a 0.60Salas curtas e densas, ideal para combates corpo a corpo rápidos.
Labirinto de Catacumbas1200 a 18000.08 a 0.15Corredores longos e retilíneos, ótimo para jogos de tiro (twin-stick shooters).
Arena Aberta de Chefe2000 a 35000.50 a 0.75Grande salão central com colunas naturais de pedra para cobertura.
Dungeon Equilibrada900 a 12000.25 a 0.30Mistura natural de túneis estreitos e câmaras médias.

Além disso, a função clampi(posicao_atual.x, 1, largura_mapa - 2) garante que o agente nunca escave exatamente na borda 0 ou largura_mapa - 1. Essa margem de 1 célula de segurança é indispensável para que a fileira externa de paredes sempre tenha onde ser desenhada sem ultrapassar os limites da matriz.

crie-geracao-procedural-dungeon-godot-4-4-1024x576 Geração Procedural de Dungeon na Godot 4 com Random Walk
Imagem referente ao código gdscript de geração procedural – Fonte (Acervo pessoal/maiastudios.com.br)

5. Otimização avançada, Auto-tiling e Spawn de Entidades

Depois que o seu gerador básico estiver rodando em poucos milissegundos, você pode expandir o algoritmo em três frentes para deixá-lo pronto para um jogo comercial:

1. Busca com Dictionary versus Array

Se você usar um Array[Vector2i] comum e chamar piso.has(vizinho) dentro de gerar_paredes(), a Godot precisará percorrer a lista inteira a cada verificação — uma complexidade que causa travamentos visíveis quando total_passos passa de 5.000. Ao armazenar as coordenadas como chaves de um Dictionary (visitados[posicao_atual] = true), a checagem mapa_piso.has(vizinho) usa tabela hash em tempo constante, gerando mapas gigantescos em menos de 5 milissegundos.

2. Auto-tiling automático com Terrains da Godot 4

Pintar todas as paredes com um único sprite estático Vector2i(0, 0) funciona muito bem para protótipos, mas em um jogo completo você provavelmente vai querer bordas superiores, laterais e quinas arredondadas. Na Godot 4, em vez de chamar set_cell() individualmente, você pode configurar um Terrain Set (modo Match Corners and Sides) no seu TileSet e passar todas as células de uma vez para o motor de auto-tiling:

# Exemplo usando o sistema de Terrains da Godot 4 (terrain_set = 0, terrain = 0)
var lista_paredes: Array[Vector2i] = []
for pos: Vector2i in mapa_piso.keys():
    for dir: Vector2i in DIRECOES_VIZINHOS:
        var vizinho: Vector2i = pos + dir
        if not mapa_piso.has(vizinho):
            lista_paredes.append(vizinho)

parede_layer.set_cells_terrain_connect(lista_paredes, 0, 0)

3. Posicionando o jogador, inimigos e a saída da fase

Como as chaves de um Dictionary na Godot 4 preservam a ordem exata de inserção, a primeira célula gravada é sempre o centro inicial da masmorra, e é perfeita para posicionar o jogador. Para posicionar o portal de saída ou o chefe da fase, em vez de pegar apenas a última célula (que pode ter acabado perto do centro se o agente deu a volta), o ideal é escolher a célula de piso com a maior distância euclidiana em relação ao ponto de partida:

func obter_pontos_extremos(mapa_piso: Dictionary) -> Array[Vector2i]:
    var celulas: Array = mapa_piso.keys()
    var inicio: Vector2i = celulas.front()
    var saida: Vector2i = inicio
    var maior_distancia: float = 0.0

    for celula: Vector2i in celulas:
        var dist: float = Vector2(inicio).distance_squared_to(Vector2(celula))
        if dist > maior_distancia:
            maior_distancia = dist
            saida = celula

    return [inicio, saida]

Para converter a coordenada da grade em posição real no mundo 2D na hora de instanciar a cena do seu jogador, basta usar piso_layer.map_to_local(inicio).

crie-geracao-procedural-dungeon-godot-4-3-1024x576 Geração Procedural de Dungeon na Godot 4 com Random Walk
Imagem 3 da dungeon gerada proceduralmente – Fonte (Acervo pessoal/maiastudios.com.br)

Conclusão

Dominar a geração procedural de dungeon na Godot 4 com o algoritmo de Random Walk e os novos nós de TileMapLayer é uma das formas mais recompensadoras de dar vida a um roguelike ou RPG 2D. Com menos de 100 linhas de código limpo, você obtém mapas sempre conectáveis, colisões automáticas, suporte a sementes compartilháveis e performance instantânea, criando uma base sólida para evoluir as mecânicas de combate e exploração do seu jogo.

Share this content:

Publicar comentário