Comment puis-je implémenter la sélection de tilemap hexagonale dans XNA?

13

J'ai une carte en mosaïque hexagonale dans laquelle je dois vérifier quand un hexagone est cliqué. Les hexagones ne se touchent pas réellement, ils ont plutôt un léger écart entre chacun d'eux.

Est-ce que quelqu'un sait comment pourrais-je vérifier si un hexagone est cliqué sans trop compliquer le tout?

Joel
la source

Réponses:

13

Jetez un oeil à cette photo

décomposition hexagonale

Comme vous pouvez le voir, il existe un moyen relativement intuitif de mapper le système de coordonnées rectangulaires x, y au système hexagonal.

On peut parler d'hexagones irréguliers "rect" c'est-à-dire d'hexagones inscrits dans des ellipses ou d'hexagones obtenus à partir d'hexagones réguliers à l'échelle disproportionnée (pas de rotations-cisaillements).

Un hexagone rect peut être défini par la hauteur et la largeur du rectangle circonscrit plus la largeur de celui qui l'inscrit. (W, w, h)

inscription / circonscription de rectangles

Le moyen le plus simple de connaître l'index hexagonal est de partitionner l'espace comme suit:

partition d'espace

La largeur du rectangle est w + (W - w) / 2 = (w + W) / 2, sa hauteur est h / 2; la largeur du rectangle vert est (Ww) / 2. Il est facile de savoir où dans quel rectangle se situe le point:

entrez la description de l'image ici

u et v sont les coordonnées de rappel qui indiquent où se trouve le point dans le rectangle i, j: En utilisant w, nous pouvons dire si nous sommes dans la zone verte (u <(Ww) / 2) ou non.

si c'est le cas, nous sommes dans la zone verte, nous devons savoir si nous sommes dans la moitié supérieure ou inférieure de l'hexagone: nous sommes dans la moitié supérieure si i et j sont tous les deux pairs ou impairs; nous sommes dans la moitié inférieure sinon.

Dans les deux cas, il est utile de transformer u et v pour qu'ils varient entre 0 et 1:

entrez la description de l'image ici

si nous sommes dans la moitié inférieure et v <u

ou

si nous sommes dans la moitié supérieure et (1-v)> u

puis on décrémente i d'une unité

Maintenant, nous devons simplement décrémenter j de un si i est impair pour voir que i est l'indice hexagonal horizontal (colonne) et la partie entière de j / 2 est l'indice hexagonal vertical (ligne)

entrez la description de l'image ici

FxIII
la source
Merci! Vraiment utile, j'ai eu quelques problèmes avec la division de la section verte (dois-je soustraire 1 de i ou non), mais je pense que cela était dû à l'orientation de mes hexagones. Tout fonctionne, merci!
Joel
14

Les hexagones réguliers ont six axes de symétrie, mais je suppose que vos hexagones n'ont que deux axes de symétrie ( c'est-à-dire que tous les angles ne sont pas exactement à 60 degrés). Pas nécessairement parce que la vôtre n'a pas la pleine symétrie, mais parce qu'elle peut être utile à quelqu'un d'autre.

Voici les paramètres d'un hexagone. Son centre est dedans O, la plus grande largeur est 2a, la hauteur est 2bet la longueur du bord supérieur est 2c.

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

Il s'agit de la disposition des lignes / colonnes, avec l'origine au centre de l'hexagone inférieur gauche. Si votre configuration est différente, traduisez vos (x,y)coordonnées pour vous replier sur ce cas, ou utilisez -yau lieu de ypar exemple:

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

Le code suivant vous donnera alors la ligne et la colonne de l'hexagone contenant le point (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;
}

Vous pouvez vérifier que le code ci-dessus dessine des hexagones parfaits lors de cette exécution d'IdeOne .

sam hocevar
la source
C'est la première fois que j'entends parler d'ideOne, mais cela semble vraiment utile!
David Gouveia
@davidluzgouveia: oui, c'est génial. Je n'aime pas les services Web ou cloud, mais celui-ci est utile.
sam hocevar
1

Vous pouvez placer 3 rectangles pivotés à l'intérieur de la zone de l'hexagone, et si cela est fait correctement, il remplira exactement la zone. Il s'agirait alors simplement de vérifier la collision sur les trois rectangles.

jgallant
la source
1

Vous n'avez probablement pas besoin de désenregistrer les clics entre les tuiles. C'est-à-dire que cela ne fera pas de mal et pourrait même aider le joueur si vous autorisez les espaces entre les tuiles à être cliquables également, sauf si vous parlez d'un grand espace entre eux qui est rempli de quelque chose qui ne devrait logiquement pas être cliqué. (Dites, les hexagones sont des villes sur une grande carte où entre elles se trouvent d'autres choses cliquables comme les gens)

Pour faire ce qui précède, vous pouvez simplement tracer les centres de tous les hexagones, puis trouver celui le plus proche de la souris lorsque vous cliquez sur le plan de tous les hexagones. Le centre le plus proche sur un plan d'hexagones en mosaïque sera toujours le même que celui que vous survolez.

DampeS8N
la source
1

J'ai déjà répondu à une question similaire, avec des objectifs identiques, sur Stack Overflow, je vais le republier ici pour plus de commodité: (NB - tout le code est écrit et testé en Java)

Grille hexagonale avec une grille carrée superposée

Cette image montre le coin supérieur gauche d'une grille hexagonale et superposée est une grille carrée bleue. Il est facile de trouver lequel des carrés un point est à l'intérieur et cela donnerait une approximation approximative de quel hexagone aussi. Les parties blanches des hexagones montrent où la grille carrée et hexagonale partagent les mêmes coordonnées et les parties grises des hexagones montrent où elles ne le font pas.

La solution est maintenant aussi simple que de trouver la case dans laquelle se trouve un point, puis de vérifier si le point se trouve dans l'un des triangles et de corriger la réponse si nécessaire.

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);

À ce stade, nous avons la ligne et la colonne de la boîte dans laquelle se trouve notre point, ensuite nous devons tester notre point contre les deux bords supérieurs de l'hexagone pour voir si notre point se trouve dans l'un des hexagones ci-dessus:

    // 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);

Avoir des coordonnées relatives facilite la prochaine étape.

Équation générique pour une ligne droite

Comme dans l'image ci-dessus, si le y de notre point est > mx + c, nous savons que notre point se situe au-dessus de la ligne, et dans notre cas, l'hexagone au-dessus et à gauche de la ligne et de la colonne en cours. Notez que le système de coordonnées en java a y commençant à 0 en haut à gauche de l'écran et non en bas à gauche comme d'habitude en mathématiques, d'où le gradient négatif utilisé pour le bord gauche et le gradient positif utilisé pour la droite.

    // 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];
}

Une explication rapide des variables utilisées dans l'exemple ci-dessus:

entrez la description de l'image ici entrez la description de l'image ici

m est le gradient, donc m = c / halfWidth


L'ajout de NeoShamam à ce qui précède

Ceci est un addendum à la réponse de SebastianTroy. Je le laisserais comme un commentaire mais je n'ai pas encore assez de réputation.

Si vous souhaitez implémenter un système de coordonnées axiales comme décrit ici: http://www.redblobgames.com/grids/hexagons/

Vous pouvez apporter une légère modification au code.

Au lieu 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);

utilisez ceci

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

Cela fera que les coordonnées (0, 2) seront sur la même colonne diagonale que (0, 0) et (0, 1) au lieu d'être directement en dessous (0, 0).

Troyseph
la source
Je me rends compte que cela ne prend pas en compte les écarts entre les hexagones mais cela devrait aider à réduire considérablement votre problème.
Troyseph
0

Si tous vos hexagones sont faits en utilisant les mêmes proportions et le même placement, vous pouvez utiliser une sorte de superposition pour les collisions, quelque chose comme: La superposition de collision pour un hexagone

Ensuite, tout ce que vous avez à faire est de placer l'image de collision à l'endroit où se trouve votre hexagone, d'obtenir la position de la souris par rapport au coin gauche et de voir si le pixel de la position relative n'est PAS blanc (ce qui signifie qu'il y a une collision).

Code (non testé):

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;
}

Vous pouvez évidemment effectuer un contrôle de collision rectangle au préalable (de votre image hexagonale entière) pour améliorer les performances de l'ensemble du processus.

Le concept est assez simple à comprendre et à mettre en œuvre, mais ne fonctionne que si vos hexagones sont tous les mêmes. Cela pourrait également fonctionner si vous ne disposez que d'un ensemble de dimensions hexagonales possibles, ce qui signifierait alors que vous auriez besoin de plusieurs superpositions de collision.

Si je trouve que c'est une solution très simpliste à ce qui pourrait être beaucoup plus complet et réutilisable (en utilisant les mathématiques pour vraiment trouver la collision), mais cela vaut vraiment la peine d'essayer à mon avis.

Jesse Emond
la source
À votre façon, vous pouvez utiliser n'importe quel ensemble de formes irrégulières et leur donner à tous une superposition avec une couleur unique, puis mapper les couleurs à l'objet qu'ils superposent de sorte que vous choisissez une couleur dans votre tampon de superposition, puis utilisez votre carte pour obtenir le objet sous la souris. Non pas que j'approuve particulièrement cette technique, sonne un peu trop compliqué et une tentative d'optimisation prématurée à mon humble avis.
Troyseph
0

Il y a un article sur Game Programming Gems 7 intitulé For Bees and Gamers: How to Handle Hexagonal Tiles qui serait exactement ce dont vous avez besoin.

Malheureusement, je n'ai pas mon exemplaire du livre avec moi pour le moment, sinon j'aurais pu le décrire un peu.

David Gouveia
la source