Guia prático para resolver o problema do minotauro preso no labirinto
O minotauro preso no labirinto é uma situação comum em jogos de Roguelike e simulações de pathfinding. O conceito é simples: você tem uma entidade (o minotauro) e um espaço fechado (o labirinto), e o objetivo é fazer com que o minotauro encontre a saída ou seja contido pelo jogador. Na prática, isso exige entender como a IA de navegação funciona e como os limites do mapa são tratados. Muitos tutoriais tratam isso como um exercício acadêmico de teoria dos grafos, mas o problema real aparece quando você tenta implementar em engines como Unity ou Godot. O minotauro fica preso nos cantos, entra em loop, ou simplesmente ignora o objetivo. Isso acontece porque a navegação baseada em NavMesh ou A* não lida bem com espaços fechados sem uma configuração adequada de waypoints.
Configurando o minotauro preso no labirinto no Unity
O primeiro passo é criar o labirinto. Você pode usar um gerador procedural ou um mapa estático. O que importa é que cada célula do labirinto seja representada como um nó em uma grade. Se você estiver usando NavMesh, certifique-se de que os obstáculos estejam marcados corretamente como "Geometry" e não como "Walkable Area Blocked." Erro comum: deixar passagens estreitas com menos de 1 metro de largura. O NavMesh vai gerar buracos e o minotauro vai tentar atravessar e falhar. Depois, configure o componente NavMeshAgent no minotauro. Defina o speed para algo entre 3 e 5 unidades por segundo. O acceleration deve ficar em 10. Se colocar valores muito altos, o agente vai oscilar ao redor do waypoint em vez de seguir uma trajetória suave. O Radius e o Height precisam corresponder ao tamanho da collisor do seu modelo 3D. Se o radius for menor que a metade da largura de um corredor, o minotauro vai tentar passar por espaços impossíveis e ficar travado.
Aqui vai um detalhe que quase ninguém menciona: o minotauro precisa de um sistema de estados. Ele não pode simplesmente chamar SetDestination o tempo todo. Se o destino for bloqueado por um obstáculo temporário, o agente entra em um estado de "stuck" após alguns segundos. Você precisa detectar isso e aplicar um workaround. No meu projeto, usei um Timer de 3 segundos. Se o minotauro não se movimenta significativamente nesse período, eu chamo ClearPath e recalcular o caminho a partir de uma posição ligeiramente deslocada. Isso resolve 90% dos casos de travamento.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Implementando o algoritmo de fuga
Se o objetivo é fazer o minotauro escapar do labirinto, a abordagem mais eficiente é usar o algoritmo de busca em largura (BFS) para encontrar o caminho mais curto até a saída. BFS é guarantee de optimalidade em grafos não ponderados, o que significa que ele sempre encontrará a rota mais curta em termos de número de passos. A desvantagem é que BFS consume mais memória que DFS em labirintos grandes. Para um grid de 50x50, a diferença é insignificante. Para grids acima de 100x100, considere usar Bidirectional BFS, que reduz drasticamente o tempo de busca. Código base em Cpara BFS:
var queue = new Queue<Vector2Int>(); var visited = new HashSet<Vector2Int>(); queue.Enqueue(startPosition); visited.Add(startPosition); while (queue.Count > 0) { var current = queue.Dequeue(); if (current == exitPosition) break; foreach (var neighbor in GetNeighbors(current)) { if (!visited.Contains(neighbor) && IsWalkable(neighbor)) { visited.Add(neighbor); queue.Enqueue(neighbor); } } } Esse algoritmo retorna a posição de saída, mas não a sequência de movimentos. Para obter o path completo, você precisa manter um dicionário de pai-pai durante o traversal. Cada nó armazena qual nó o precedeu. Após alcançar a saída, você rastreia de volta até o início e inverte a lista. Esse overhead é pequeno — cerca de 2 a 5 milissegundos para um labirinto de 80x80 em uma máquina média.
O problema dos corredores em U e armadilhas
Labirintos gerados proceduralmente com geradores como Recursive Backtracking produzem corredores em U que parecem saídas legítimas mas levam a becos sem saída. O minotauro entra nesse beco, calcula o caminho de volta, e o jogador ganha tempo. Se você quer que o minotauro seja uma ameaça real, precisa evitar esse padrão. Uma solução simples é pós-processar o labirinto e identificar todas as células que têm exatamente um vizinho walkable (folhas no grafo). Essas células são becos sem saída reais. Marque-as como dead ends no NavMesh e remova-as da lista de destinos válidos para o minotauro. Outro problema sério: minimapas. Se o jogador tem acesso a um minimapa que revela todo o labirinto, o minotauro perde a vantagem de surpresa. Eu resolvi isso limitando o raio de visão do minimapa ao redor do jogador e adicionando um delay de 2 segundos entre atualizações. Assim, o jogador nunca tem informação perfeita, e o minotauro pode flanquear usando rotas que o jogador não viu recentemente.
Download e recursos
O asset completo com o sistema de pathfinding, o gerador de labirinto e o comportamento do minotauro está disponível no repositório oficial do projeto. A versão 2.3 adiciona suporte a múltiplos minotauros com comportamento de swarm e sincronização de pathfinding em rede. A instalação leva cerca de 10 minutos. Você precisa do Unity 2022.3 LTS ou superior. Não há dependências de terceiros além do paquete padrão de Collection utilities. Se você está começando do zero, o primeiro erro mais comum é tentar programar tudo desde o. Use um gerador de labirinto pronto primeiro. Entenda como o grid é construído, como os nós são conectados, e só então implemente a IA. Esse processo leva de 2 a 3 horas no início, mas evita dias de debugging depois.