Izometryczny 2D: współrzędne ekranu do kafelka


9

Piszę izometryczną grę 2D i mam trudności z dokładnym określeniem, na którym kafelku znajduje się kursor. Oto rysunek:

gdzie xs i ys to współrzędne ekranu (piksele), xt i yt to współrzędne kafelka, W i H to odpowiednio szerokość i wysokość kafelka w pikselach. Mój zapis współrzędnych to (y, x), co może być mylące, przepraszam za to.

Do tej pory najlepiej wymyśliłem:

int xtemp = xs / (W / 2);
int ytemp = ys / (H / 2);
int xt = (xs - ys) / 2;
int yt = ytemp + xt;

To wydaje się prawie poprawne, ale daje mi bardzo nieprecyzyjny wynik, co utrudnia wybranie niektórych kafelków lub czasami wybiera kafelek obok tego, który próbuję kliknąć. Nie rozumiem dlaczego i chciałbym, aby ktoś mógł mi pomóc zrozumieć logikę tego.

Dzięki!

Odpowiedzi:


2

Aby uzyskać dokładny pomiar, możemy rozważyć:

Zastanówmy się najpierw, jak przekształcić współrzędne z przestrzeni izometrycznej, określone przez wektory i oraz j (jak w izometrycznej mapie [i, j]) lub jako yt i xt na ekranie, na przestrzeń ekranową określoną przez xiy ekranu. Załóżmy, że dla uproszczenia twoja przestrzeń ekranu jest wyrównana na początku z przestrzenią izometryczną.

Jednym ze sposobów przeprowadzenia transformacji jest najpierw obrót, a następnie przeskalowanie osi y lub x. Aby uzyskać niezbędne wartości, aby dopasować twoje yt i xt, nie mogę tu znaleźć od razu. Możesz utworzyć macierz, aby to zrobić lub nie, a następnie użyć macierzy odwrotnej, ale operacja odwrotna jest w zasadzie tym, czego chcesz.

Skaluj wartość w odwrotnej kolejności, a następnie obracaj do tyłu, aby uzyskać wartości, i zaokrąglaj w dół.

Są inne sposoby, ale wydaje mi się, że jest to teraz najbardziej właściwe.


argh. Przeglądałem ten post tak wiele razy i myślę, że nie mogę całkiem dobrze zrozumieć tego, co chciałbym. Potrzebuję snu.
Toni,

1
Dzięki, matryce są tu zdecydowanie najlepszym rozwiązaniem. Mam już prawie coś do roboty!
Asik

4

Miałem ten sam problem z grą, którą pisałem. Wyobrażam sobie, że ten problem będzie się różnił w zależności od tego, jak dokładnie zaimplementowałeś system izometryczny, ale wyjaśnię, jak rozwiązałem problem.

Zacząłem od mojej funkcji tile_to_screen. (Zakładam, że tak właśnie umieszczasz kafelki w odpowiednim miejscu.) Ta funkcja ma równanie do obliczania screen_x i screen_y. Mój wyglądał tak (python):

def map_to_screen(self, point):
    x = (SCREEN_WIDTH + (point.y - point.x) * TILE_WIDTH) / 2
    y = (SCREEN_HEIGHT + (point.y + point.x) * TILE_HEIGHT) / 2
    return (x, y)

Wziąłem te dwa równania i przekształciłem je w układ równań liniowych. Rozwiąż ten układ równań dowolną wybraną metodą. (Użyłem metody RREF. Niektóre kalkulatory graficzne mogą rozwiązać ten problem.)

Końcowe równania wyglądały następująco:

# constants for quick calculating (only process once)
DOUBLED_TILE_AREA = 2 * TILE_HEIGHT * TILE_WIDTH
S2M_CONST_X = -SCREEN_HEIGHT * TILE_WIDTH + SCREEN_WIDTH * TILE_HEIGHT
S2M_CONST_Y = -SCREEN_HEIGHT * TILE_WIDTH - SCREEN_WIDTH * TILE_HEIGHT

def screen_to_map(self, point):
    # the "+ TILE_HEIGHT/2" adjusts for the render offset since I
    # anchor my sprites from the center of the tile
    point = (point.x * TILE_HEIGHT, (point.y + TILE_HEIGHT/2) * TILE_WIDTH)
    x = (2 * (point.y - point.x) + self.S2M_CONST_X) / self.DOUBLED_TILE_AREA
    y = (2 * (point.x + point.y) + self.S2M_CONST_Y) / self.DOUBLED_TILE_AREA
    return (x, y)

Jak widać, nie jest to proste jak początkowe równanie. Ale działa dobrze w stworzonej przeze mnie grze. Dzięki Bogu za algebrę liniową!

Aktualizacja

Po napisaniu prostej klasy Point z różnymi operatorami uprościłem tę odpowiedź na następujące:

# constants for quickly calculating screen_to_iso
TILE_AREA = TILE_HEIGHT * TILE_WIDTH
S2I_CONST_X = -SCREEN_CENTER.y * TILE_WIDTH + SCREEN_CENTER.x * TILE_HEIGHT
S2I_CONST_Y = -SCREEN_CENTER.y * TILE_WIDTH - SCREEN_CENTER.x * TILE_HEIGHT

def screen_to_iso(p):
    ''' Converts a screen point (px) into a level point (tile) '''
    # the "y + TILE_HEIGHT/2" is because we anchor tiles by center, not bottom
    p = Point(p.x * TILE_HEIGHT, (p.y + TILE_HEIGHT/2) * TILE_WIDTH)
    return Point(int((p.y - p.x + S2I_CONST_X) / TILE_AREA),
                 int((p.y + p.x + S2I_CONST_Y) / TILE_AREA))

def iso_to_screen(p):
    ''' Converts a level point (tile) into a screen point (px) '''
    return SCREEN_CENTER + Point((p.y - p.x) * TILE_WIDTH / 2,
                                 (p.y + p.x) * TILE_HEIGHT / 2)

Tak, system dwóch równań liniowych powinien również działać. Biorąc pod uwagę, że mamy dwa wektory, które nie są równoległe, powinieneś być w stanie uzyskać dowolny punkt na płaszczyźnie za pomocą wektorów jednostkowych yt i xt. Chociaż myślę, że twoja implementacja jest nieco zawężona i nie zamierzam niepokoić jej sprawdzania poprawności.
Toni,

2

Używasz dobrego układu współrzędnych. Sprawy stają się znacznie trudniejsze, jeśli używasz naprzemiennych kolumn.

Jednym ze sposobów myślenia o tym problemie jest to, że masz funkcję do zamiany (xt, yt) na (xs, ys). Podążę za odpowiedzią Thane'a i zadzwonię map_to_screen.

Chcesz odwrócić tę funkcję. Możemy to nazwać screen_to_map. Odwrotności funkcji mają następujące właściwości:

map_to_screen(screen_to_map(xs, ys)) == (xs, ys)
screen_to_map(map_to_screen(xt, yt)) == (xt, yt)

Te dwie rzeczy są dobre do przetestowania jednostkowego, gdy masz już napisane obie funkcje. Jak piszesz odwrotność? Nie wszystkie funkcje mają odwrotność, ale w tym przypadku:

  1. Jeśli napisałeś to jako obrót, po którym następuje tłumaczenie, wówczas odwrotnością jest tłumaczenie odwrotne (ujemne dx, dy), po którym następuje odwrotny obrót (kąt ujemny).
  2. Jeśli napisałeś to jako mnożenie macierzy, odwrotność jest macierzą odwrotności macierzy.
  3. Jeśli napisałeś to jako równania algebraiczne definiujące (xs, ys) w kategoriach (xt, yt), to odwrotność można znaleźć przez rozwiązanie tych równań dla (xt, yt) podanych (xs, ys).

Sprawdź, czy funkcja odwrotna + oryginalna zwraca odpowiedź, od której zacząłeś. Thane przechodzi oba testy, jeśli + TILE_HEIGHT/2usuniesz przesunięcie renderowania. Kiedy rozwiązałem algebrę, wymyśliłem:

x = (2*xs - SCREEN_WIDTH) / TILE_WIDTH
y = (2*ys - SCREEN_HEIGHT) / TILE_HEIGHT
yt =  (y + x) / 2
xt =  (y - x) / 2

który moim zdaniem jest taki sam jak Thane'a screen_to_map.

Ta funkcja zamienia współrzędne myszy na liczby zmiennoprzecinkowe; Użyj, flooraby przekonwertować je na liczby całkowite.


1
Dzięki! Skończyło się na użyciu macierzy transformacji, więc pisanie odwrotności jest trywialne, tzn. Jest to po prostu Matrix.Invert (). Ponadto prowadzi to do bardziej deklaratywnego stylu kodowania (Matrix.Translate () * Matrix.Scale () * Matrix.Rotate () zamiast szeregu równań). Może to trochę wolniej, ale nie powinno to stanowić problemu.
Asik
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.