Wie kann ich die hexagonale Tilemap-Auswahl in XNA implementieren?

13

Ich habe eine mit Sechsecken gekachelte Karte, in der ich überprüfen muss, wann auf ein Sechseck geklickt wird. Die Sechsecke berühren sich eigentlich nicht, sondern haben jeweils eine kleine Lücke.

Weiß jemand, wie ich überprüfen könnte, ob ein Sechseck angeklickt wurde, ohne das Ganze zu komplizieren?

Joel
quelle

Antworten:

13

Schauen Sie sich dieses Bild an

hexagonale Zersetzung

Wie Sie sehen, gibt es eine relativ intuitive Möglichkeit, das x, y-Rechteckkoordinatensystem dem hexagonalen zuzuordnen.

Wir können von "geraden" unregelmäßigen Sechsecken sprechen, dh Sechsecken, die in Ellipsen oder Sechsecken eingeschrieben sind, die aus regelmäßigen Sechsecken erhalten werden, die in beide Richtungen überproportional skaliert sind (keine Rotationsscherung).

Ein rechtwinkliges Sechseck kann durch die Höhe und Breite des umschreibenden Rechtecks ​​plus der Breite des einschreibenden Rechtecks ​​definiert werden. (W, W, H)

Rechtecke beschriften / umschreiben

Der hexagonale Index lässt sich am einfachsten wie folgt aufteilen:

Raumaufteilung

Die Rechteckbreite ist w + (W - w) / 2 = (w + W) / 2, ihre Höhe ist h / 2; Die Breite des grünen Rechtecks ​​beträgt (Ww) / 2. Es ist einfach herauszufinden, in welches Rechteck der Punkt fällt:

Bildbeschreibung hier eingeben

u und v sind die Erinnerungskoordinaten, die angeben, wo sich der Punkt innerhalb des i, j-Rechtecks ​​befindet: Mit w können wir sagen, ob wir uns im grünen Bereich befinden (u <(Ww) / 2) oder nicht.

Wenn es so ist, dass wir uns im grünen Bereich befinden, müssen wir wissen, ob wir uns in der oberen oder unteren Hälfte des Sechsecks befinden: Wir befinden uns in der oberen Hälfte, wenn i und j beide gerade oder beide ungerade sind. wir sind sonst in der unteren hälfte.

In beiden Fällen ist es nützlich, u und v zu transformieren, damit sie zwischen 0 und 1 variieren:

Bildbeschreibung hier eingeben

wenn wir in der unteren Hälfte sind und v <u

oder

wenn wir in der oberen Hälfte sind und (1-v)> u

dann dekrementieren wir i um eins

Jetzt müssen wir einfach j um eins dekrementieren, wenn i ungerade ist, um zu sehen, dass i der horizontale Hexagonindex (Spalte) und der ganzzahlige Teil von j / 2 der vertikale Hexagonindex (Zeile) ist.

Bildbeschreibung hier eingeben

FxIII
quelle
Vielen Dank! Wirklich hilfreich, hatte ein paar Probleme mit der Aufteilung des grünen Abschnitts (sollte ich 1 von i subtrahieren oder nicht), aber ich denke, das lag an der Ausrichtung meiner Sechsecke. Alles funktioniert, danke!
Joel
14

Normale Sechsecke haben sechs Symmetrieachsen, aber ich gehe davon aus, dass Ihre Sechsecke nur zwei Symmetrieachsen haben ( dh, alle Winkel sind nicht genau 60 Grad). Nicht unbedingt, weil Ihre nicht die volle Symmetrie haben, sondern weil es für andere nützlich sein kann.

Hier sind die Parameter eines Sechsecks. Seine Mitte ist in O, die größte Breite ist 2a, die Höhe ist 2bund die Länge der Oberkante ist 2c.

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

Dies ist das Zeilen- / Spaltenlayout mit dem Ursprung in der Mitte des linken unteren Sechsecks. Wenn Ihr Setup anders ist, übersetzen Sie Ihre (x,y)Koordinaten, um auf diesen Fall zurückzugreifen, oder verwenden Sie -yanstelle von yzum Beispiel:

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

Der folgende Code gibt Ihnen dann die Zeile und die Spalte des hexagonalen Punkts an (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;
}

Sie können überprüfen, ob der obige Code perfekte Sechsecke auf diesem IdeOne-Lauf zeichnet .

sam hocevar
quelle
Ich habe zum ersten Mal von ideOne gehört, aber es scheint wirklich nützlich zu sein!
David Gouveia
@davidluzgouveia: ja, es ist großartig. Ich mag keine Web- oder Cloud-Dienste, aber dieser ist hilfreich.
Sam Hocevar
1

Sie könnten 3 gedrehte Rechtecke in den Bereich des Sechsecks einfügen, und wenn dies richtig gemacht würde, würde es den Bereich genau ausfüllen. Dann müsste nur noch geprüft werden, ob die drei Rechtecke kollidieren.

jgallant
quelle
1

Sie müssen wahrscheinlich keine Klicks zwischen den Kacheln abmelden. Das heißt, es tut nicht weh und kann dem Spieler sogar helfen, wenn Sie zulassen, dass die Zwischenräume zwischen den Kacheln auch durch Klicken geöffnet werden können, es sei denn, Sie sprechen von einem großen Zwischenraum zwischen ihnen, der mit etwas gefüllt ist, das logischerweise nicht sein sollte angeklickt werden. (Sagen wir, die Felder sind Städte auf einer großen Karte, zwischen denen sich andere klickbare Dinge wie Menschen befinden.)

Um dies zu tun, können Sie einfach die Zentren aller Felder zeichnen und dann das der Maus am nächsten liegende Feld finden, wenn Sie auf die Ebene aller Felder klicken. Das nächste Zentrum auf einer Ebene mit tessellierten Sechsecken ist immer dasselbe, über dem Sie schweben.

DampeS8N
quelle
1

Ich habe bereits eine ähnliche Frage mit identischen Zielen beantwortet. Zum besseren Verständnis werde ich sie hier erneut veröffentlichen: (Hinweis: Der gesamte Code ist in Java geschrieben und getestet.)

Sechseckiges Gitter mit einem überlagerten quadratischen Gitter

Dieses Bild zeigt die linke obere Ecke eines hexagonalen Gitters und darüber ein blaues quadratisches Gitter. Es ist leicht zu finden, in welchem ​​der Quadrate sich ein Punkt befindet, und dies würde auch eine grobe Annäherung an welches Sechseck ergeben. Die weißen Teile der Sechsecke zeigen, wo das quadratische und das sechseckige Gitter die gleichen Koordinaten haben, und die grauen Teile der Sechsecke zeigen, wo sie keine Koordinaten haben.

Die Lösung ist jetzt so einfach, dass Sie herausfinden, in welchem ​​Feld sich ein Punkt befindet, prüfen, ob sich der Punkt in einem der Dreiecke befindet, und gegebenenfalls die Antwort korrigieren.

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

An diesem Punkt haben wir die Zeile und Spalte des Feldes, in dem sich unser Punkt befindet. Als nächstes müssen wir unseren Punkt an den beiden oberen Kanten des Sechsecks testen, um festzustellen, ob unser Punkt in einem der obigen Sechsecke liegt:

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

Relative Koordinaten zu haben, erleichtert den nächsten Schritt.

Generische Gleichung für eine gerade Linie

Wenn das y unseres Punktes wie im obigen Bild > mx + c ist , wissen wir, dass unser Punkt über der Linie liegt, und in unserem Fall das Sechseck über und links von der aktuellen Zeile und Spalte. Beachten Sie, dass das Koordinatensystem in Java in der oberen linken Ecke des Bildschirms mit y beginnt und nicht wie in der Mathematik üblich unten links, daher der negative Farbverlauf für den linken Rand und der positive Farbverlauf für den rechten Rand.

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

Eine kurze Erklärung der im obigen Beispiel verwendeten Variablen:

Bildbeschreibung hier eingeben Bildbeschreibung hier eingeben

m ist der Gradient, also m = c / halbe Breite


NeoShamams Ergänzung zu den oben genannten

Dies ist ein Nachtrag zu SebastianTroys Antwort. Ich würde es als Kommentar hinterlassen, aber ich habe noch nicht genug Ruf.

Wenn Sie ein axiales Koordinatensystem wie hier beschrieben implementieren möchten: http://www.redblobgames.com/grids/hexagons/

Sie können den Code leicht ändern.

Anstatt von

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

benutze das

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

Dadurch befindet sich die Koordinate (0, 2) in der gleichen diagonalen Spalte wie (0, 0) und (0, 1), anstatt direkt unter (0, 0) zu liegen.

Troyseph
quelle
Mir ist klar, dass dies die Lücken zwischen den Sechsecken nicht berücksichtigt, aber es sollte helfen, Ihr Problem erheblich einzugrenzen.
Troyseph
0

Wenn alle Ihre Sechsecke mit den gleichen Proportionen und der gleichen Platzierung erstellt wurden, können Sie für die Kollisionen eine Art Überlagerungselement verwenden, etwa wie folgt: Die Kollisionsüberlagerung für ein Sechseck

Dann müssen Sie nur das Kollisionsbild an der Stelle platzieren, an der sich Ihr Sechseck befindet, die Mausposition relativ zur linken Ecke ermitteln und prüfen, ob das Pixel der relativen Position NICHT weiß ist (was bedeutet, dass eine Kollision vorliegt).

Code (nicht getestet):

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

Sie könnten natürlich vorher eine Rechteckkollisionsprüfung (Ihres gesamten Sechseckbildes) durchführen, um die Leistung des gesamten Prozesses zu verbessern.

Das Konzept ist recht einfach zu verstehen und umzusetzen, funktioniert aber nur, wenn Ihre Sechsecke alle gleich sind. Es könnte auch funktionieren, wenn Sie nur einen Satz möglicher Sechskantabmessungen haben, was dann bedeuten würde, dass Sie mehr als eine Kollisionsüberlagerung benötigen würden.

Wenn es eine sehr vereinfachte Lösung für das ist, was viel vollständiger und wiederverwendbarer sein könnte (unter Verwendung von Mathematik, um die Kollision wirklich zu finden), dann ist es meiner Meinung nach definitiv einen Versuch wert.

Jesse Emond
quelle
Mit Ihrer Methode können Sie einen beliebigen Satz von unregelmäßigen Formen verwenden und allen eine Überlagerung mit einer eindeutigen Farbe zuweisen. Ordnen Sie dann die Farben dem Objekt zu, das sie überlagern, und wählen Sie dann eine Farbe aus dem Überlagerungspuffer aus Objekt unter der Maus. Nicht, dass ich diese Technik besonders unterstütze, sie klingt ein bisschen zu kompliziert und ist meiner Meinung nach ein Versuch einer vorzeitigen Optimierung.
Troyseph
0

Es gibt einen Artikel über Game Programming Gems 7 mit dem Titel " Für Bienen und Gamer: Umgang mit hexagonalen Kacheln", der genau das ist, was Sie brauchen.

Leider habe ich mein Exemplar des Buches im Moment nicht bei mir, sonst hätte ich es ein wenig beschreiben können.

David Gouveia
quelle