Como posso implementar a seleção hexagonal de tilemap no XNA?

13

Eu tenho um mapa em hexágono lado a lado no qual preciso verificar quando um hexágono é clicado. Na verdade, os hexágonos não se tocam, mas sim um pequeno espaço entre cada um deles.

Alguém sabe como eu poderia verificar se um hexágono é clicado sem complicar demais a coisa toda?

Joel
fonte

Respostas:

13

Dê uma olhada nesta foto

decomposição hexagonal

Como você pode ver, existe uma maneira relativamente intuitiva de mapear o sistema de coordenadas retangulares x, y para o hexagonal.

Podemos falar sobre hexágonos irregulares "retos", ou seja, hexágonos inscritos em elipses ou hexágonos obtidos de hexágonos regulares escalados desproporcionalmente em ambas as direções (sem rotações-cisalhamento).

Um hexágono reto pode ser definido pela altura e largura do retângulo circunscrito mais a largura do retângulo de inscrição. (W, W, H)

retângulos de inscrição / circunscrição

A maneira mais fácil de descobrir o índice hexagonal é particionar o espaço da seguinte maneira:

partição espacial

A largura do retângulo é w + (W - w) / 2 = (w + W) / 2, sua altura é h / 2; a largura do retângulo verde é (Ww) / 2. É fácil descobrir em que ponto do retângulo o ponto está:

insira a descrição da imagem aqui

u e v são as coordenadas do lembrete que indicam onde o ponto está dentro do retângulo i, j: usando w, podemos dizer se estamos na área verde (u <(Ww) / 2) ou não.

se for o caso de estarmos na área verde, precisamos saber se estamos na metade superior ou inferior do hexágono: estamos na metade superior se i e j forem pares ou ímpares; estamos na metade inferior caso contrário.

Nos dois casos, é útil transformar u e v para que eles variem entre 0 e 1:

insira a descrição da imagem aqui

se estamos na metade inferior e v <u

ou

se estamos na metade superior e (1-v)> u

então decrementamos i por um

Agora, simplesmente precisamos decrementar j por um, se i for estranho ver que i é o índice hexagonal horizontal (coluna) e a parte inteira de j / 2 é o índice hexagonal vertical (linha)

insira a descrição da imagem aqui

FxIII
fonte
Obrigado! Realmente útil, tive alguns problemas decorrentes da divisão da seção verde (devo subtrair 1 de i ou não), mas acho que isso foi devido à orientação dos meus hexágonos. Tudo funcionando, obrigado!
Joel
14

Hexágonos regulares têm seis eixos de simetria, mas assumirei que seus hexágonos têm apenas dois eixos de simetria ( ou seja, todos os ângulos não são exatamente de 60 graus). Não necessariamente porque a sua não tem a simetria completa, mas porque pode ser útil para outra pessoa.

Aqui estão os parâmetros de um hexágono. Seu centro está O, a maior largura 2a, a altura 2be o comprimento da borda superior 2c.

         Y ^
           |
       ____|____
      /  b |   |\
     /     |   | \
    /      |   |  \
---(-------+---+---)------>
    \     O|   c  / a      X
     \     |     /
      \____|____/
           |

Esse é o layout de linha / coluna, com a origem no centro do hexágono inferior esquerdo. Se sua configuração for diferente, traduza suas (x,y)coordenadas para recorrer a este caso ou use em -yvez de, ypor exemplo:

col 0
 | col 1
 |   | col 2
 |   |  |
 __  | __    __    __    __   
/  \__/  \__/  \__/  \__/  \__
\__/  \__/  \__/  \__/  \__/  \
/  \__/  \__/  \__/  \__/  \__/
\__/  \__/  \__/  \__/  \__/  \
/  \__/  \__/  \__/  \__/  \__/_ _ line 2
\__/  \__/  \__/  \__/  \__/  \ _ _ _ line 1
/ .\__/  \__/  \__/  \__/  \__/_ _ line 0
\__/  \__/  \__/  \__/  \__/

O código a seguir fornecerá a linha e a coluna do ponto que contém o hexágono (x,y):

static void GetHex(float x, float y, out int row, out int column)
{
  // Find out which major row and column we are on:
  row = (int)(y / b);
  column = (int)(x / (a + c));

  // Compute the offset into these row and column:
  float dy = y - (float)row * b;
  float dx = x - (float)column * (a + c);

  // Are we on the left of the hexagon edge, or on the right?
  if (((row ^ column) & 1) == 0)
      dy = b - dy;
  int right = dy * (a - c) < b * (dx - c) ? 1 : 0;

  // Now we have all the information we need, just fine-tune row and column.
  row += (column ^ row ^ right) & 1;
  column += right;
}

Você pode verificar se o código acima desenha hexágonos perfeitos nesta execução do IdeOne .

sam hocevar
fonte
Primeira vez que ouvi sobre o ideOne, mas parece realmente útil!
David Gouveia
@davidluzgouveia: sim, é incrível. Não gosto de serviços da web ou em nuvem, mas este é útil.
Sam Hocevar
1

Você pode colocar três retângulos girados dentro da área do hexágono e, se feito corretamente, preencherá a área exatamente. Então seria simplesmente uma questão de verificar se havia colisão nos três retângulos.

jgallant
fonte
1

Você provavelmente não precisa cancelar o registro de cliques entre os blocos. Ou seja, não vai doer e pode até ajudar o jogador se você permitir que os espaços entre os blocos sejam clicáveis ​​também, a menos que você esteja falando de um grande espaço entre eles preenchido com algo que logicamente não deveria ser clicado. (Digamos, os hexágonos são cidades em um mapa grande, onde estão outras coisas que podem ser clicadas, como pessoas)

Para fazer o acima, você pode simplesmente traçar os centros de todos os hexágonos e, em seguida, encontrar o mais próximo ao mouse quando clicar no plano de todos os hexágonos. O centro mais próximo em um plano de hexágonos em mosaico será sempre o mesmo sobre o qual você está passando o mouse.

DampeS8N
fonte
1

Já respondi a uma pergunta semelhante, com objetivos idênticos, no Stack Overflow . Vou repassá-la aqui por conveniência: (NB - todo o código é escrito e testado em Java)

Grade hexagonal com uma grade quadrada sobreposta

Esta imagem mostra o canto superior esquerdo de uma grade hexagonal e sobreposta é uma grade quadrada azul. É fácil encontrar em quais quadrados um ponto está dentro e isso daria uma aproximação aproximada de qual hexágono também. As partes brancas dos hexágonos mostram onde a grade quadrada e hexagonal compartilham as mesmas coordenadas e as partes cinza dos hexágonos mostram onde não.

A solução agora é tão simples quanto encontrar em qual caixa um ponto está, verificar se o ponto está em um dos triângulos e corrigir a resposta, se necessário.

private final Hexagon getSelectedHexagon(int x, int y)
{
    // Find the row and column of the box that the point falls in.
    int row = (int) (y / gridHeight);
    int column;

    boolean rowIsOdd = row % 2 == 1;

    // Is the row an odd number?
    if (rowIsOdd)// Yes: Offset x to match the indent of the row
        column = (int) ((x - halfWidth) / gridWidth);
    else// No: Calculate normally
        column = (int) (x / gridWidth);

Nesse ponto, temos a linha e a coluna da caixa em que está o ponto; em seguida, precisamos testar nosso ponto contra as duas arestas superiores do hexágono para ver se nosso ponto está em um dos hexágonos acima:

    // Work out the position of the point relative to the box it is in
    double relY = y - (row * gridHeight);
    double relX;

    if (rowIsOdd)
        relX = (x - (column * gridWidth)) - halfWidth;
    else
        relX = x - (column * gridWidth);

Ter coordenadas relativas facilita o próximo passo.

Equação genérica para uma linha reta

Como na imagem acima, se o y do nosso ponto é > mx + c , sabemos que o ponto está acima da linha e, no nosso caso, o hexágono acima e à esquerda da linha e coluna atuais. Observe que o sistema de coordenadas em java tem y começando em 0 no canto superior esquerdo da tela e não no canto inferior esquerdo como é habitual em matemática, portanto, o gradiente negativo usado para a borda esquerda e o gradiente positivo usado para a direita.

    // Work out if the point is above either of the hexagon's top edges
    if (relY < (-m * relX) + c) // LEFT edge
        {
            row--;
            if (!rowIsOdd)
                column--;
        }
    else if (relY < (m * relX) - c) // RIGHT edge
        {
            row--;
            if (rowIsOdd)
                column++;
        }

    return hexagons[column][row];
}

Uma explicação rápida das variáveis ​​usadas no exemplo acima:

insira a descrição da imagem aqui insira a descrição da imagem aqui

m é o gradiente, então m = c / halfWidth


A adição do NeoShamam ao acima

Este é um adendo à resposta de SebastianTroy. Gostaria de deixar como comentário, mas ainda não tenho reputação suficiente.

Se você deseja implementar um sistema de coordenadas axiais, conforme descrito aqui: http://www.redblobgames.com/grids/hexagons/

Você pode fazer uma ligeira modificação no código.

Ao invés de

// Is the row an odd number?
if (rowIsOdd)// Yes: Offset x to match the indent of the row
    column = (int) ((x - halfWidth) / gridWidth);
else// No: Calculate normally
    column = (int) (x / gridWidth);

usa isto

float columnOffset = row * halfWidth;
column = (int)(x + columnOffset)/gridWidth; //switch + to - to align the grid the other way

Isso fará com que a coordenada (0, 2) esteja na mesma coluna diagonal que (0, 0) e (0, 1), em vez de ficar diretamente abaixo (0, 0).

Troyseph
fonte
Sei que isso não leva em consideração as lacunas entre os hexágonos, mas deve ajudar a diminuir consideravelmente o seu problema.
Troyseph
0

Se todos os seus hexágonos forem feitos com as mesmas proporções e colocação, você poderá usar algum tipo de recurso de sobreposição para as colisões, algo como: A sobreposição de colisão para um hexágono

Então, tudo o que você precisa fazer é colocar a imagem de colisão onde está o hexágono, obter a posição do mouse em relação ao canto esquerdo e ver se o pixel da posição relativa NÃO é branco (o que significa que há uma colisão).

Código (não testado):

bool IsMouseTouchingHexagon(Vector2 mousePosition, Vector2 hexagonPosition,
    Rectangle hexagonRectangle, Texture2D hexagonImage)
{
    Vector2 mousePositionToTopLeft = mousePosition - hexagonPosition;

    // We make sure that the mouse is over the hexagon's rectangle.
    if (mousePositionToTopLeft.X >= 0 && mousePositionToTopLeft.X < hexagonRectangle.Width && 
        mousePositionToTopLeft.Y >= 0 && mousePositionToTopLeft.Y < hexagonRectangle.Height)
    {
        // Where "PixelColorAt" returns the color of a pixel of an image at a certain position.
        if (PixelColorAt(hexagonImage, mousePositionToTopLeft) == Color.White)
        {
            // If the color is not white, we are colliding with the hexagon
            return true;
        }
    }

    // if we get here, it means that we did not find a collision.
    return false;
}

Obviamente, você poderia executar uma verificação de colisão de retângulo com antecedência (de toda a imagem hexagonal) para melhorar o desempenho de todo o processo.

O conceito é bastante simples de entender e implementar, mas só funciona se os hexágonos forem todos iguais. Também poderia funcionar se você tivesse apenas um conjunto de dimensões possíveis do hexágono, o que significaria que seria necessário mais de uma sobreposição de colisão.

Se achar que é uma solução muito simplista para o que poderia ser muito mais completo e reutilizável (usando a matemática para realmente encontrar a colisão), mas definitivamente vale a pena tentar na minha opinião.

Jesse Emond
fonte
Do seu jeito, você pode usar qualquer conjunto de formas irregulares e aplicar uma sobreposição em todas elas com uma cor única. Em seguida, mapeie as cores para o objeto que elas estão sobrepondo, para que você escolha uma cor no buffer de sobreposição e use o mapa para obter a cor desejada. objeto sob o mouse. Não que eu esteja particularmente endossando essa técnica, parece um pouco complicado demais e uma tentativa de otimização prematura do IMHO.
Troyseph
0

Há um artigo sobre Gemas de programação de jogos 7, intitulado Para abelhas e jogadores: Como lidar com ladrilhos hexagonais, que seria exatamente o que você precisa.

Infelizmente, não tenho minha cópia do livro comigo no momento, caso contrário, eu poderia ter descrito um pouco.

David Gouveia
fonte