Edit: Ok, acabei de ler em um de seus comentários que você não deseja navegação automática. Nesse caso, considere apenas a seção final deste post ("Percorrendo esse caminho") com o caso simples de que a lista armazena apenas o local em que o mouse clicou e se limpa quando um obstáculo é encontrado. Também notei que você mencionou peças uma vez no post. Nesse caso, não há necessidade de um gráfico de visibilidade, a própria grade pode ser usada para rodar uma estrela A. Enfim, ainda estou postando isso como uma solução mais geral para o ponto 2D e clique no problema de navegação.
O que você está pedindo é como fazer a busca de caminhos em um ambiente 2D. Eu escrevi um artigo antes de descrever uma técnica que é capaz de resolver esse problema. Começarei estabelecendo um link para o artigo e adicionarei uma breve explicação do algoritmo.
http://www.david-gouveia.com/pathfinding-on-a-2d-polygonal-map/
Obviamente, essa não é a única maneira de resolvê-lo. Estou usando um gráfico de visibilidade. Você também pode usar uma malha de navegação. Ou uma grade. O gráfico de visibilidade tem uma vantagem particular: ele sempre retorna o caminho mais direto entre os pontos, sem a necessidade de corrigir o caminho. E construindo o gráfico de visibilidade no topo de um polígono, você pode especificar as regiões que podem ser percorridas com precisão.
Conceito
A idéia principal aqui é representar sua região percorrível como um polígono e construir um gráfico de visibilidade usando os vértices côncavos do polígono . Se o polígono contiver orifícios, você também usará seus vértices convexos .
Criar um gráfico de visibilidade significa pegar todos os nós (ou vértices, neste caso) do gráfico e conectá-los a todos os outros vértices que ele pode "ver". Você precisa de uma verificação da linha de visão para fazer isso. O que eu usei foi construído sobre um teste simples de interseção de segmento de linha, com algumas verificações adicionais.
Então, sempre que você quiser encontrar o caminho entre dois locais, adicione-os temporariamente ao gráfico de visibilidade e execute um algoritmo A * pathfinding clássico .
Aqui está a aparência de toda a estrutura:
Onde:
- As linhas amarelas são o polígono que representa onde você pode andar.
- Os círculos brancos são os vértices poligonais que compõem o gráfico de visibilidade (nós)
- As linhas roxas conectam vértices que estão alinhados entre si (arestas).
- A linha azul clara é um exemplo de localização de um caminho entre dois locais (ponto verde e ponto vermelho).
- As linhas verdes claras são arestas temporárias adicionadas ao gráfico junto com os nós inicial e final no caminho (ponto verde e ponto vermelho).
Implementação
1) Representação
Para implementar isso, primeiro você precisa ter uma maneira de representar um polígono para o seu piso. A seguinte classe deve ser suficiente:
public class Polygon
{
public class SimplePolygon
{
List<Vector2> Vertices;
}
List<SimplePolygon> Outlines;
List<SimplePolygon> Holes;
}
2) Selecionando nós
Depois, você precisa percorrer cada um dos vértices do polígono e decidir se eles devem ser nós no gráfico de visibilidade. O critério para isso é vértices côncavos no contorno e vértices convexos nos furos. Eu uso uma função como esta:
public static bool IsVertexConcave(IList<Vector2> vertices, int vertex)
{
Vector2 current = vertices[vertex];
Vector2 next = vertices[(vertex + 1) % vertices.Count];
Vector2 previous = vertices[vertex == 0 ? vertices.Count - 1 : vertex - 1];
Vector2 left = new Vector2(current.X - previous.X, current.Y - previous.Y);
Vector2 right = new Vector2(next.X - current.X, next.Y - current.Y);
float cross = (left.X * right.Y) - (left.Y * right.X);
return cross < 0;
}
3) Selecionando arestas
Agora você precisará examinar cada par desses vértices e decidir se eles estão na linha de visão ou não. Eu usei o seguinte método como ponto de partida para essa verificação:
static bool LineSegmentsCross(Vector2 a, Vector2 b, Vector2 c, Vector2 d)
{
float denominator = ((b.X - a.X) * (d.Y - c.Y)) - ((b.Y - a.Y) * (d.X - c.X));
if (denominator == 0)
{
return false;
}
float numerator1 = ((a.Y - c.Y) * (d.X - c.X)) - ((a.X - c.X) * (d.Y - c.Y));
float numerator2 = ((a.Y - c.Y) * (b.X - a.X)) - ((a.X - c.X) * (b.Y - a.Y));
if (numerator1 == 0 || numerator2 == 0)
{
return false;
}
float r = numerator1 / denominator;
float s = numerator2 / denominator;
return (r > 0 && r < 1) && (s > 0 && s < 1);
}
Mas teve que recorrer ao uso de alguns outros hacks para obter estabilidade em casos extremos, por isso não está em boas condições para postar. Ainda tentando descobrir uma solução limpa e estável para o problema.
4) Construa o gráfico e execute uma estrela A
Você precisará criar um gráfico de visibilidade usando esses vértices e arestas e executar A * nele. Para aprender a construir um gráfico e aplicar A *, recomendo estudar o seguinte artigo:
http://blogs.msdn.com/b/ericlippert/archive/2007/10/02/path-finding-using-a-in-c-3-0.aspx
Você pode querer encapsular tudo isso em uma única classe, para ter uma interface fácil de usar, como:
public class Floor
{
public Floor(Polygon polygon)
{
_polygon = polygon;
BuildGraph();
}
public IEnumerable<Vector> GetPath(Vector2 start, Vector2 end)
{
// Add start and end as temporary nodes and connect them to the graph
// Run A* on the graph
// Remove temporary nodes and edges
}
private Polygon _polygon;
private Graph _graph;
}
Dessa forma, você só precisa criar uma instância do Floor e chamar o método GetPath sempre que precisar encontrar o caminho entre dois locais.
5) Trilhando esse caminho
Finalmente, você precisa fazer seu personagem percorrer o caminho gerado. Ele precisa ter algum tipo de memória interna para isso, mas não é muito difícil implementá-lo. Por exemplo:
- Adicione uma lista dentro do personagem para armazenar o caminho que ele está seguindo no momento.
- Em cada ciclo de atualização, pegue o primeiro valor da lista e mova seu personagem para ele.
- Se ele chegar perto o suficiente do destino, remova o primeiro elemento da lista e repita.
- Se a lista ficar vazia, ele chegou ao seu destino.