Estou desenvolvendo meu próprio algoritmo de busca de caminhos para um jogo baseado em grade 2D no Unity 2D e me deparei com um problema que afeta a maneira como os inimigos navegam em direção ao alvo.
Problema Atualmente, quando um inimigo calcula um caminho para seu alvo (por exemplo, o jogador), ele considera apenas um único bloco da posição do alvo, ignorando o fato de que o alvo pode ocupar vários blocos na grade.
Exemplo:
Se o alvo tiver um tamanho de 3x2 peças, o inimigo pode calcular o caminho mais curto para apenas uma peça daquele espaço, em vez da peça mais próxima ou mais acessível dentro da área ocupada.
Isso faz com que o inimigo nem sempre escolha a melhor rota e, em alguns casos, se sobreponha ao alvo em vez de parar em um ladrilho adjacente.
O que eu preciso
Uma maneira de considerar eficientemente todos os blocos ocupados pelo alvo na grade.
Certifique-se de que o inimigo escolha o caminho mais curto para o ladrilho mais acessível dentro daquela área.
Evite que o inimigo se sobreponha ao alvo ao chegar ao seu destino.
Qual seria a melhor abordagem para resolver isso? Como posso modificar meu algoritmo de pathfinding para lidar corretamente com um alvo que ocupa vários tiles?
Aqui está meu código atual:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class AStarPathfinder
{
private NavigationRegion _region;
public AStarPathfinder(NavigationRegion region)
{
_region = region;
}
public List<Vector3Int> FindPath(Vector3Int start, Vector3Int target)
{
Dictionary<Vector3Int, Node> nodes = new Dictionary<Vector3Int, Node>();
List<Node> openList = new List<Node>();
HashSet<Vector3Int> closedSet = new HashSet<Vector3Int>();
Node startNode = new Node(start);
startNode.GCost = 0;
startNode.HCost = GetManhattanDistance(start, target);
nodes[start] = startNode;
openList.Add(startNode);
while (openList.Count > 0)
{
Node currentNode = openList.OrderBy(x => x.FCost).First();
openList.Remove(currentNode);
closedSet.Add(currentNode.Position);
if (currentNode.Position == target)
{
return RetracePath(startNode, currentNode);
}
foreach (Vector3Int neighborPos in GetNeighbors(currentNode.Position))
{
if (closedSet.Contains(neighborPos) || !_region.IsWalkable(neighborPos))
continue;
int tentativeGCost = currentNode.GCost + 1;
Node neighbor;
if (!nodes.TryGetValue(neighborPos, out neighbor))
{
neighbor = new Node(neighborPos);
nodes[neighborPos] = neighbor;
}
if (tentativeGCost < neighbor.GCost)
{
neighbor.GCost = tentativeGCost;
neighbor.HCost = GetManhattanDistance(neighborPos, target);
neighbor.Parent = currentNode;
if (!openList.Contains(neighbor))
openList.Add(neighbor);
}
}
}
return null;
}
private List<Vector3Int> RetracePath(Node start, Node end)
{
List<Vector3Int> path = new List<Vector3Int>();
Node currentNode = end;
while (currentNode != start)
{
path.Add(currentNode.Position);
currentNode = currentNode.Parent;
}
path.Reverse();
return path;
}
private int GetManhattanDistance(Vector3Int a, Vector3Int b)
{
return Mathf.Abs(a.x - b.x) + Mathf.Abs(a.y - b.y);
}
private List<Vector3Int> GetNeighbors(Vector3Int pos)
{
return new List<Vector3Int>
{
new Vector3Int(pos.x + 1, pos.y, pos.z),
new Vector3Int(pos.x - 1, pos.y, pos.z),
new Vector3Int(pos.x, pos.y + 1, pos.z),
new Vector3Int(pos.x, pos.y - 1, pos.z)
};
}
}
É bastante trivial estender seu A* típico para aceitar múltiplos alvos. Basta mudar
para
e
para
Presumindo que seu Vector3Int seja adequado para uso em um HashSet, ou seja, o código hash nunca muda, implementação de igualdade válida etc. Você também pode estendê-lo para aceitar vários nós iniciais apenas adicionando todos eles ao conjunto aberto no início.
Para a heurística, você pode simplesmente pegar a estimativa mínima para todos os alvos. Contanto que você subestime o custo, você deve ficar bem.
Note que classificar todo o conjunto aberto a cada iteração é bastante ineficiente. A recomendação típica é usar um min heap, seja o seu ou o priority queue integrado .