Rysowanie izometrycznych światów gier

Jaki jest prawidłowy sposób rysowania płytek izometrycznych w grze 2D?

Czytałem referencje (takie jak ten), które sugerują, że kafelki są renderowane w sposób, który zygzakuje każdą kolumnę w reprezentacji tablicy 2D mapy. Wyobrażam sobie, że powinny być rysowane bardziej w sposób diamentowy, gdzie to, co jest rysowane na ekranie, bardziej odnosi się do tego, jak wyglądałaby tablica 2D, tylko trochę obrócona.

Czy są zalety lub wady albo metoda?

 170
Author: Timotei, 2009-05-21

5 answers

Aktualizacja: Poprawiono algorytm renderowania map, Dodano więcej ilustracji, zmieniono formatowanie.

Być może zaletą techniki "zig-zag" mapowania płytek na ekranie można powiedzieć, że współrzędne płytki x i y są na osi pionowej i poziomej.

"rysunek w Diamencie" podejście:

Rysując mapę izometryczną za pomocą "drawing in a diamond", co moim zdaniem odnosi się do samego renderowania mapy za pomocą zagnieżdżone for - pętla nad tablicą dwuwymiarową, tak jak w tym przykładzie:

tile_map[][] = [[...],...]

for (cellY = 0; cellY < tile_map.size; cellY++):
    for (cellX = 0; cellX < tile_map[cellY].size cellX++):
        draw(
            tile_map[cellX][cellY],
            screenX = (cellX * tile_width  / 2) + (cellY * tile_width  / 2)
            screenY = (cellY * tile_height / 2) - (cellX * tile_height / 2)
        )

korzyść:

Zaletą tego podejścia jest to, że jest to prosta zagnieżdżona pętla for z dość prostą logiką, która działa konsekwentnie we wszystkich kafelkach.

Jedną z wad tego podejścia jest to, że współrzędne x i y płytek na mapie zwiększą się w ukośnych liniach, co może utrudnić na ekranie wyświetlana jest mapa przedstawiająca się w postaci tablicy:

Zdjęcie mapy płytek

Będzie jednak pułapka w implementacji powyższego przykładowego kodu - kolejność renderowania spowoduje, że kafelki, które powinny znajdować się za pewnymi kafelkami, będą rysowane na górze kafelków z przodu: {]}

Wynikający obraz z nieprawidłowej kolejności renderowania

W celu poprawienia tego problemu, wewnętrzny for-kolejność pętli musi być odwrócona -- zaczynając od najwyższej wartości, a renderowanie w kierunku niższej wartości:

tile_map[][] = [[...],...]

for (i = 0; i < tile_map.size; i++):
    for (j = tile_map[i].size; j >= 0; j--):  // Changed loop condition here.
        draw(
            tile_map[i][j],
            x = (j * tile_width / 2) + (i * tile_width / 2)
            y = (i * tile_height / 2) - (j * tile_height / 2)
        )

Z powyższą poprawką należy poprawić renderowanie mapy:

Wynikający obraz z poprawnej kolejności renderowania

"Zig-zag" podejście:

korzyść:

[19]} być może zaletą podejścia "zig-zag" jest to, że renderowana mapa może wydawać się nieco bardziej zwarta pionowo niż podejście "diamentowe":]}

Zig-zag podejście do renderowania wydaje się kompaktowe

Od próby zaimplementować technikę zig-zag, wadą może być to, że jest trochę trudniej napisać kod renderujący, ponieważ nie może być napisany tak prosto, jak zagnieżdżona for - pętla nad każdym elementem w tablicy:

tile_map[][] = [[...],...]

for (i = 0; i < tile_map.size; i++):
    if i is odd:
        offset_x = tile_width / 2
    else:
        offset_x = 0

    for (j = 0; j < tile_map[i].size; j++):
        draw(
            tile_map[i][j],
            x = (j * tile_width) + offset_x,
            y = i * tile_height / 2
        )
W przeciwieństwie do innych elementów, nie jest to możliwe w przypadku, gdy nie jest to możliwe, ale w przypadku, gdy nie jest to możliwe, nie jest to możliwe.]}

Współrzędne na zygzakowatym renderowaniu porządku

Uwaga: ilustracje zawarte w tej odpowiedzi zostały stworzone przy użyciu implementacji Javy na mapie znajduje się tablica int:

tileMap = new int[][] {
    {0, 1, 2, 3},
    {3, 2, 1, 0},
    {0, 0, 1, 1},
    {2, 2, 3, 3}
};

Obrazy płytek to:

  • tileImage[0] -> pudełko Z pudełkiem w środku.
  • tileImage[1] -> czarna skrzynka.
  • tileImage[2] -> białe pudełko.
  • Pudełko z wysokim, szarym przedmiotem.

Uwaga na szerokości i wysokości płytek

Zmienne tile_width i tile_height użyte w powyższych przykładach kodu odnoszą się do szerokości i wysokości podłoża dachówka na obrazku przedstawiającym dachówkę:

Obraz pokazujący szerokość i wysokość płytki

Używanie wymiarów obrazu będzie działać, o ile wymiary obrazu i wymiary kafelka będą zgodne. W przeciwnym razie Mapa kafelków może być renderowana z przerwami między kafelkami.

 483
Author: coobird,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-21 22:04:37

/ Align = "left" / Zakładam, że przez zygzak masz na myśli coś takiego: (liczby są kolejnością renderowania)

..  ..  01  ..  ..
  ..  06  02  ..
..  11  07  03  ..
  16  12  08  04
21  17  13  09  05
  22  18  14  10
..  23  19  15  ..
  ..  24  20  ..
..  ..  25  ..  ..

I przez diament masz na myśli:

..  ..  ..  ..  ..
  01  02  03  04
..  05  06  07  ..
  08  09  10  11
..  12  13  14  ..
  15  16  17  18
..  19  20  21  ..
  22  23  24  25
..  ..  ..  ..  ..

Pierwsza metoda wymaga więcej kafelków renderowanych tak, aby cały ekran był rysowany, ale możesz łatwo sprawdzić granice i pominąć wszystkie kafelki całkowicie poza ekranem. Obie metody będą wymagały pewnego chrupania liczb, aby dowiedzieć się, jaka jest lokalizacja płytki 01. W końcu obie metody są mniej więcej równe pod względem matematycznym wymagane dla pewnego poziomu wydajności.

 10
Author: zaratustra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-21 17:39:44

Jeśli masz jakieś płytki, które przekraczają granice Twojego diamentu, polecam rysowanie w kolejności głębokiej:

...1...
..234..
.56789.
..abc..
...d...
 1
Author: Wouter Lievens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-24 21:59:02

ODPOWIEDŹ Coobirda jest prawidłowa, pełna. Jednak połączyłem jego wskazówki z tymi z innej strony, aby stworzyć kod, który działa w mojej aplikacji (iOS/Objective-C), którą chciałem podzielić się z każdym, kto tu przychodzi, szukając czegoś takiego. Proszę, Jeśli podoba ci się/up-zagłosuj na tę odpowiedź, zrób to samo dla oryginałów; wszystko, co zrobiłem, to " stand on the shoulders of giants."

Jeśli chodzi o kolejność sortowania, moja technika jest zmodyfikowanym algorytmem malarza: każdy obiekt ma (a) wysokość podstawy (nazywam "poziom") i (b) X/Y dla "podstawy" lub "stopy" obrazu (przykłady: baza awatara jest u jego stóp; baza drzewa jest u jego korzeni; baza samolotu jest środkiem obrazu, itp.) Następnie sortuję od najniższego do najwyższego poziomu, następnie od najniższego (od najwyższego na ekranie) do najwyższej bazy-Y, następnie od najniższego (od lewej-most) do najwyższej bazy-X. To sprawia, że płytki są takie, jakich można się spodziewać.

Kod do konwersji ekranu (punktu) na kafelek (komórkę) i z powrotem:

typedef struct ASIntCell {  // like CGPoint, but with int-s vice float-s
    int x;
    int y;
} ASIntCell;

// Cell-math helper here:
//      http://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-a-primer-for-game-developers--gamedev-6511
// Although we had to rotate the coordinates because...
// X increases NE (not SE)
// Y increases SE (not SW)
+ (ASIntCell) cellForPoint: (CGPoint) point
{
    const float halfHeight = rfcRowHeight / 2.;

    ASIntCell cell;
    cell.x = ((point.x / rfcColWidth) - ((point.y - halfHeight) / rfcRowHeight));
    cell.y = ((point.x / rfcColWidth) + ((point.y + halfHeight) / rfcRowHeight));

    return cell;
}


// Cell-math helper here:
//      http://stackoverflow.com/questions/892811/drawing-isometric-game-worlds/893063
// X increases NE,
// Y increases SE
+ (CGPoint) centerForCell: (ASIntCell) cell
{
    CGPoint result;

    result.x = (cell.x * rfcColWidth  / 2) + (cell.y * rfcColWidth  / 2);
    result.y = (cell.y * rfcRowHeight / 2) - (cell.x * rfcRowHeight / 2);

    return result;
}
 0
Author: Olie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-11 16:30:33

Prawdziwy problem polega na tym, że musisz narysować kilka płytek / spritów przecinających / obejmujących dwa lub więcej innych płytek.

Po 2 (ciężkich) miesiącach osobistych analiz problemu w końcu znalazłem i zaimplementowałem "poprawny rysunek renderujący" dla mojej nowej gry cocos2d-js. Rozwiązanie polega na mapowaniu, dla każdej płytki (podatnej), której sprity są "przód, tył, góra i tył". Gdy to zrobisz, możesz narysować je zgodnie z "logiką rekurencyjną".

 0
Author: Carlos Lopez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-09 16:22:01