Come posso implementare il picking hexagonal tilemap in XNA?


13

Ho una mappa piastrellata esagonale in cui devo controllare quando si fa clic su un esagono. Gli esagoni in realtà non si toccano, piuttosto hanno un leggero spazio tra ciascuno di essi.

Qualcuno sa come posso fare per verificare se si fa clic su un esagono senza complicare eccessivamente il tutto?

Risposte:


13

Dai un'occhiata a questa foto

decomposizione esagonale

Come puoi vedere, esiste un modo relativamente intuitivo per mappare il sistema di coordinate rettangolari x, y su quello esagonale.

Potremmo parlare di esagoni irregolari "retti", cioè esagoni inscritti in ellissi o esagoni ottenuti da esagoni regolari ridimensionati in entrambe le direzioni in modo sproporzionato (nessuna rotazione-taglio).

Un esagono rettangolo può essere definito dall'altezza e dalla larghezza del rettangolo circoscritto più la larghezza di quello iscritto. (W, w, h)

rettangoli di iscrizione / circoscrizione

Il modo più semplice per scoprire l'indice esagonale è di partizionare lo spazio come segue:

partizione spaziale

La larghezza del rettangolo è w + (W - w) / 2 = (w + W) / 2, la sua altezza è h / 2; la larghezza del rettangolo verde è (Ww) / 2. È facile scoprire dove in quale rettangolo cade il punto:

inserisci qui la descrizione dell'immagine

u e v sono le coordinate del promemoria che indicano dove si trova il punto all'interno del rettangolo i, j: Usando w possiamo dire se ci troviamo nell'area verde (u <(Ww) / 2) o no.

se è il caso che ci troviamo nell'area verde, dobbiamo sapere se siamo nella metà superiore o inferiore dell'esagono: siamo nella metà superiore se iej sono entrambi pari o entrambi dispari; altrimenti siamo nella metà inferiore.

In entrambi i casi è utile trasformare u e v quindi variano tra 0 e 1:

inserisci qui la descrizione dell'immagine

se siamo nella metà inferiore e v <u

o

se siamo nella metà superiore e (1-v)> u

quindi diminuiamo di uno

Ora dobbiamo semplicemente ridurre j di uno se sono strano vedere che i è l'indice esagonale orizzontale (colonna) e la parte intera di j / 2 è l'indice esagonale verticale (riga)

inserisci qui la descrizione dell'immagine


Grazie! Davvero utile, ho avuto qualche problema con la divisione della sezione verde (dovrei sottrarre 1 da me o no), ma penso che ciò sia dovuto all'orientamento dei miei esagoni. Tutto funzionante, grazie!
Gioele il

14

Gli esagoni regolari hanno sei assi di simmetria, ma suppongo che i tuoi esagoni abbiano solo due assi di simmetria ( es. tutti gli angoli non sono esattamente di 60 gradi). Non necessariamente perché il tuo non ha la simmetria completa, ma perché può essere utile a qualcun altro.

Ecco i parametri di un esagono. Il suo centro è dentro O, la larghezza maggiore è 2a, l'altezza è 2be la lunghezza del bordo superiore è 2c.

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

Questo è il layout di riga / colonna, con l'origine al centro dell'esagono in basso a sinistra. Se la tua configurazione è diversa, traduci le tue (x,y)coordinate in modo da ricorrere a questo caso o usa -yinvece yad esempio:

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

Il seguente codice ti fornirà quindi la riga e la colonna dell'esagono che contiene il punto (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;
}

Puoi verificare che il codice sopra disegna esagoni perfetti in questa corsa di IdeOne .


La prima volta che ho sentito parlare di ideOne, ma sembra davvero utile!
David Gouveia,

@davidluzgouveia: sì, è fantastico. Non mi piacciono i servizi web o cloud, ma questo è utile.
Sam Hocevar,

1

Potresti inserire 3 rettangoli ruotati all'interno dell'area dell'esagono e, se fatto correttamente, riempirebbe esattamente l'area. Quindi sarebbe semplicemente una questione di verificare la collisione sui tre rettangoli.


1

Probabilmente non è necessario annullare la registrazione dei clic tra i riquadri. Vale a dire, non farà male e potrebbe persino aiutare il giocatore se si consente anche agli spazi tra le tessere di essere cliccabili, a meno che non si parli di un ampio spazio tra loro che è riempito con qualcosa che logicamente non dovrebbe essere cliccato. (Supponiamo che gli esagoni siano città su una grande mappa in cui tra loro ci sono altre cose cliccabili come le persone)

Per fare quanto sopra, puoi semplicemente tracciare i centri di tutti gli esagoni, quindi trovare quello più vicino al mouse quando fai clic sul piano di tutti gli esagoni. Il centro più vicino su un piano di esagoni tassellati sarà sempre lo stesso su cui stai passando il mouse.


1

Ho già risposto a una domanda simile, con obiettivi identici, sopra Stack Overflow lo ripubblicherò qui per comodità: (NB - tutto il codice è scritto e testato in Java)

Griglia esagonale con una griglia quadrata sovrapposta

Questa immagine mostra l'angolo in alto a sinistra di una griglia esagonale e sovrapposta è una griglia quadrata blu. È facile trovare quale dei quadrati è presente un punto e ciò darebbe una approssimazione approssimativa di quale esagono. Le porzioni bianche degli esagoni mostrano dove la griglia quadrata ed esagonale condividono le stesse coordinate e le porzioni grigie degli esagoni mostrano dove non lo fanno.

La soluzione ora è semplice come trovare la casella in cui si trova un punto, quindi verificare se il punto si trova in uno dei triangoli e correggere la risposta, se necessario.

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

A questo punto abbiamo la riga e la colonna della casella in cui si trova il nostro punto, quindi dobbiamo testare il nostro punto contro i due bordi superiori dell'esagono per vedere se il nostro punto si trova in uno degli esagoni sopra:

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

Avere coordinate relative semplifica il passaggio successivo.

Equazione generica per una linea retta

Come nell'immagine sopra, se y del nostro punto è > mx + c sappiamo che il nostro punto si trova sopra la linea e, nel nostro caso, l'esagono sopra e a sinistra della riga e della colonna correnti. Si noti che il sistema di coordinate in Java ha y che inizia da 0 nella parte superiore sinistra dello schermo e non nella parte inferiore sinistra, come al solito in matematica, quindi il gradiente negativo usato per il bordo sinistro e il gradiente positivo usato per quello destro.

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

Una rapida spiegazione delle variabili utilizzate nell'esempio sopra:

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

m è il gradiente, quindi m = c / halfWidth


L' aggiunta di NeoShamam a quanto sopra

Questa è un'aggiunta alla risposta di SebastianTroy. Lo lascerei come commento ma non ho ancora abbastanza reputazione.

Se si desidera implementare un sistema di coordinate assiali come descritto qui: http://www.redblobgames.com/grids/hexagons/

Puoi apportare una leggera modifica al codice.

Invece di

// 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 questo

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

Ciò farà sì che le coordinate (0, 2) si trovino sulla stessa colonna diagonale di (0, 0) e (0, 1) anziché essere direttamente sotto (0, 0).


Mi rendo conto che ciò non tiene conto degli spazi tra gli esagoni, ma dovrebbe aiutare a ridurre notevolmente il problema.
Troyseph,

0

Se tutti i tuoi esagoni sono realizzati usando le stesse proporzioni e posizioni, potresti usare una sorta di risorsa di sovrapposizione per le collisioni, qualcosa sulla falsariga di: La sovrapposizione di collisione per un esagono

Quindi, tutto ciò che devi fare è posizionare l'immagine di collisione nel punto in cui si trova il tuo esagono, ottenere la posizione del mouse rispetto all'angolo sinistro e vedere se il pixel della posizione relativa NON è bianco (il che significa che c'è una collisione).

Codice (non testato):

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

Ovviamente è possibile eseguire preventivamente un controllo delle collisioni rettangolari (dell'intera immagine esagonale) per migliorare le prestazioni dell'intero processo.

Il concetto è abbastanza semplice da capire e implementare, ma funziona solo se i tuoi esagoni sono tutti uguali. Potrebbe funzionare anche se hai solo un set di possibili dimensioni esagonali, il che significherebbe che avresti bisogno di più di un overlay di collisione.

Se trovo che sia una soluzione molto semplicistica a ciò che potrebbe essere molto più completo e riutilizzabile (usando la matematica per trovare davvero la collisione), ma a mio parere vale sicuramente la pena provare.


A modo tuo, potresti usare qualsiasi set di forme irregolari e dare a tutti una sovrapposizione con un colore unico, quindi mappare i colori sull'oggetto che stanno sovrapponendo in modo da scegliere un colore dal buffer di sovrapposizione e quindi utilizzare la mappa per ottenere il oggetto sotto il mouse. Non che io stia sostenendo particolarmente questa tecnica, sembra un po 'troppo complicato e un tentativo di ottimizzazione prematura dell'IMHO.
Troyseph,

0

C'è un articolo su Game Programming Gems 7 chiamato For Bees and Gamers: How to Hand Hexagonal Tiles che sarebbe esattamente ciò di cui hai bisogno.

Purtroppo al momento non ho la mia copia del libro, altrimenti avrei potuto descriverlo un po '.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.