Jak mogę zaimplementować heksagonalne pobieranie tilemapów w XNA?

13

Mam mapę kafelkową sześciokąta, na której muszę sprawdzić po kliknięciu sześciokąta. Sześciokąty tak naprawdę się nie dotykają, mają raczej niewielką przerwę między każdym z nich.

Czy ktoś wie, jak mogę sprawdzić, czy kliknięto sześciokąt, nie komplikując wszystkiego?

Joel
źródło

Odpowiedzi:

13

Spójrz na to zdjęcie

rozkład heksagonalny

Jak widać, istnieje stosunkowo intuicyjny sposób odwzorowania prostokątnego układu współrzędnych x, y na sześciokątny.

Możemy mówić o „prostokątnych” nieregularnych sześciokątach, tj. Sześciokątach wpisanych w elipsy lub sześciokątach uzyskanych z regularnych sześciokątów skalowanych nieproporcjonalnie w obu kierunkach (bez ścinania-rotacji).

Prostokątny sześciokąt można zdefiniować na podstawie wysokości i szerokości prostokąta opisującego oraz szerokości prostokąta opisującego. (W, w, h)

opisywanie / opisywanie prostokątów

Najłatwiejszym sposobem na znalezienie indeksu heksagonalnego jest podzielenie przestrzeni w następujący sposób:

partycja kosmiczna

Prostokąt ma szerokość w + (W - w) / 2 = (w + W) / 2, jego wysokość wynosi h / 2; szerokość zielonego prostokąta wynosi (Ww) / 2. Łatwo dowiedzieć się, w którym prostokącie znajduje się punkt:

wprowadź opis zdjęcia tutaj

u i v są współrzędnymi przypomnienia, które wskazują, gdzie punkt znajduje się w prostokącie i, j: Używając w, możemy powiedzieć, czy jesteśmy w zielonym obszarze (u <(Ww) / 2), czy nie.

jeśli tak jest, jesteśmy w zielonym obszarze, musimy wiedzieć, czy znajdujemy się w górnej czy dolnej połowie sześciokąta: jesteśmy w górnej połowie, jeśli i i j są parzyste lub oba nieparzyste; w przeciwnym razie jesteśmy w dolnej połowie.

W obu przypadkach przydatne jest przekształcenie u i v, aby różniły się między 0 a 1:

wprowadź opis zdjęcia tutaj

jeśli jesteśmy w dolnej połowie i v <u

lub

jeśli jesteśmy w górnej połowie i (1-v)> u

następnie zmniejszamy o jeden

Teraz po prostu musimy zmniejszyć j o jeden, jeśli i jest nieparzyste, aby zobaczyć, że i jest poziomym indeksem sześciokątnym (kolumna), a liczba całkowita j / 2 jest pionowym indeksem sześciokątnym (wiersz)

wprowadź opis zdjęcia tutaj

FxIII
źródło
Dzięki! Naprawdę pomocny, miałem kilka kłopotów z dzieleniem zielonej sekcji (powinienem odjąć 1 od i czy nie), ale myślę, że było to spowodowane orientacją moich sześciokątów. Wszystko działa, dzięki!
Joel,
14

Zwykłe sześciokąty mają sześć osi symetrii, ale założę, że twoje sześciokąty mają tylko dwie osie symetrii ( tzn. Wszystkie kąty nie są dokładnie 60 stopni). Niekoniecznie dlatego, że twoja nie ma pełnej symetrii, ale ponieważ może być przydatna dla kogoś innego.

Oto parametry jednego sześciokąta. Jego środek jest O, największa szerokość 2a, wysokość 2bi długość górnej krawędzi wynosi 2c.

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

Jest to układ wierszy / kolumn, którego początek znajduje się na środku lewego dolnego sześciokąta. Jeśli konfiguracja jest inna, przetłumacz (x,y)współrzędne, aby oprzeć się na tym przypadku, lub użyj -yzamiast, yna przykład:

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

Poniższy kod da ci wiersz i kolumnę sześciokąta zawierającego punkt (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;
}

Możesz sprawdzić, czy powyższy kod rysuje idealne sześciokąty w tym przebiegu IdeOne .

sam hocevar
źródło
Po raz pierwszy słyszałem o ideOne, ale wydaje się bardzo przydatny!
David Gouveia,
@davidluzgouveia: tak, jest niesamowity. Nie przepadam za usługami internetowymi ani chmurowymi, ale ta jest pomocna.
sam hocevar,
1

Możesz umieścić 3 obrócone prostokąty w obszarze sześciokąta, a jeśli zrobisz to poprawnie, dokładnie wypełni ten obszar. Byłoby to po prostu kwestią sprawdzenia kolizji trzech prostokątów.

jgallant
źródło
1

Prawdopodobnie nie musisz wyrejestrowywać kliknięć między kafelkami. Oznacza to, że nie zaszkodzi, a może nawet pomóc graczowi, jeśli pozwolisz na klikanie spacji między kafelkami, chyba że mówisz o dużej przestrzeni między nimi wypełnionej czymś, co logicznie nie powinno zostać klikniętym. (Powiedzmy, że heksy to miasta na dużej mapie, na których między nimi znajdują się inne klikalne rzeczy, takie jak ludzie)

Aby to zrobić, możesz po prostu wykreślić środki wszystkich heksów, a następnie znaleźć najbliższy kursor myszy po kliknięciu na płaszczyźnie wszystkich heksów. Najbliższe centrum na płaszczyźnie mozaik sześciokąta będzie zawsze tym samym, nad którym unosisz się.

DampeS8N
źródło
1

Odpowiedziałem już na podobne pytanie, z identycznymi celami, nad przepełnieniem stosu. Ponownie opublikuję je tutaj dla pewności: (Uwaga - cały kod jest napisany i przetestowany w Javie)

Sześciokątna siatka z nałożoną kwadratową siatką

Ten obraz pokazuje lewy górny róg sześciokątnej siatki, a nałożony jest niebieski kwadrat. Łatwo jest ustalić, który kwadrat jest w środku, a to dałoby przybliżone przybliżenie, który sześciokąt też. Białe części sześciokątów pokazują, gdzie kwadratowa i sześciokątna siatka mają te same współrzędne, a szare części sześciokątów pokazują, gdzie nie.

Rozwiązanie jest teraz tak proste, jak znalezienie pola, w którym znajduje się punkt, a następnie sprawdzenie, czy punkt znajduje się w którymś z trójkątów, i poprawienie odpowiedzi w razie potrzeby.

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

W tym momencie mamy wiersz i kolumnę pola, w którym znajduje się nasz punkt, następnie musimy przetestować nasz punkt na dwóch górnych krawędziach sześciokąta, aby zobaczyć, czy nasz punkt leży w którymś z sześciokątów powyżej:

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

Posiadanie współrzędnych względnych ułatwia następny krok.

Równanie ogólne dla linii prostej

Podobnie jak na powyższym obrazku, jeśli y naszego punktu wynosi > mx + c , wiemy, że nasz punkt leży powyżej linii, aw naszym przypadku sześciokąt powyżej i na lewo od bieżącego wiersza i kolumny. Zauważ, że układ współrzędnych w java ma y od 0 w lewym górnym rogu ekranu, a nie w lewym dolnym rogu, jak to zwykle jest w matematyce, stąd gradient ujemny użyty dla lewej krawędzi i gradient dodatni użyty dla prawej.

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

Szybkie wyjaśnienie zmiennych użytych w powyższym przykładzie:

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

m jest gradientem, więc m = c / halfWidth


Dodatek NeoShamam do powyższego

To jest dodatek do odpowiedzi SebastianTroy. Zostawiłbym to jako komentarz, ale nie mam jeszcze wystarczającej reputacji.

Jeśli chcesz wdrożyć osiowy układ współrzędnych, jak opisano tutaj: http://www.redblobgames.com/grids/hexagons/

Możesz wprowadzić niewielką modyfikację kodu.

Zamiast

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

Użyj tego

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

Spowoduje to, że współrzędne (0, 2) będą znajdować się w tej samej ukośnej kolumnie, co (0, 0) i (0, 1), zamiast znajdować się bezpośrednio poniżej (0, 0).

Troyseph
źródło
Zdaję sobie sprawę, że nie bierze to pod uwagę przerw między sześciokątami, ale powinno znacznie pomóc zawęzić twój problem.
Troyseph
0

Jeśli wszystkie twoje sześciokąty są wykonane przy użyciu tych samych proporcji i rozmieszczenia, możesz użyć jakiegoś zasobu nakładki dla kolizji, coś w stylu: Nakładka kolizyjna dla sześciokąta

Następnie wystarczy umieścić obraz kolizji w miejscu, w którym znajduje się sześciokąt, uzyskać pozycję myszy względem lewego rogu i sprawdzić, czy piksel pozycji względnej NIE jest biały (co oznacza kolizję).

Kod (nie testowany):

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

Oczywiście można wcześniej wykonać kontrolę kolizji prostokąta (całego obrazu sześciokąta), aby poprawić wydajność całego procesu.

Koncepcja jest dość prosta do zrozumienia i wdrożenia, ale działa tylko wtedy, gdy wszystkie sześciokąty są takie same. Może to również działać, jeśli masz tylko zestaw możliwych wymiarów sześciokąta, co oznaczałoby, że potrzebujesz więcej niż jednej nakładki kolizyjnej.

Jeśli okaże się, że jest to bardzo uproszczone rozwiązanie tego, co może być o wiele bardziej kompletne i wielokrotnego użytku (używanie matematyki, aby naprawdę znaleźć kolizję), ale moim zdaniem zdecydowanie warto spróbować.

Jesse Emond
źródło
Na swój sposób możesz użyć dowolnego zestawu nieregularnych kształtów i nadać im wszystkim nakładkę o unikalnym kolorze, a następnie odwzorować kolory na obiekt, który nakładają, aby wybrać kolor z bufora nakładki, a następnie użyć mapy, aby uzyskać obiekt pod myszą. Nie dlatego, że szczególnie popieram tę technikę, brzmi to trochę zbyt skomplikowane i próba przedwczesnej optymalizacji IMHO.
Troyseph
0

Jest artykuł na temat Gems Programming Game 7 pt. „ Pszczoły i gracze: jak obchodzić się z sześciokątnymi kafelkami”, który byłby dokładnie tym, czego potrzebujesz.

Niestety w tej chwili nie mam ze sobą egzemplarza książki, inaczej mógłbym ją trochę opisać.

David Gouveia
źródło