Рисование Изометрических игровых миров - PullRequest
175 голосов
/ 21 мая 2009

Как правильно рисовать изометрические плитки в 2D игре?

Я прочитал ссылки (такие как этот ), которые предполагают, что листы будут отображаться таким образом, что будет зигзагообразно каждый столбец в представлении 2D-массива карты. Я полагаю, что они должны быть нарисованы более алмазным способом, где то, что рисуется на экране, более тесно связано с тем, как будет выглядеть двумерный массив, просто немного повернутый.

Есть ли преимущества или недостатки любого из этих методов?

Ответы [ 5 ]

490 голосов
/ 21 мая 2009

Обновление: исправлен алгоритм рендеринга карты, добавлено больше иллюстраций, изменено форматирование.

Возможно, преимущество техники зигзага для отображения тайлов на экране можно сказать, что координаты x и y тайла находятся на вертикальной и горизонтальной осях.

Подход "Рисование в алмазе":

Рисуя изометрическую карту, используя «рисование в ромбе», что, как я полагаю, относится только к визуализации карты с использованием вложенного for -попа над двумерным массивом, такого как этот пример:

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

Преимущество:

Преимущество этого подхода заключается в том, что он представляет собой простой вложенный for -петл с довольно простой логикой, которая работает последовательно на всех тайлах.

Недостатки:

Недостатком этого подхода является то, что координаты плиток на карте x и y будут увеличиваться в виде диагональных линий, что может затруднить визуальное сопоставление местоположения на экране с картой, представленной в виде массив:

Image of tile map

Однако при реализации приведенного выше примера кода будет ловушка - порядок рендеринга приведет к тому, что плитки, которые должны находиться за определенными плитками, будут нарисованы поверх плиток впереди:

Resulting image from incorrect rendering order

Чтобы исправить эту проблему, внутренний порядок for -циклов должен быть обращен в обратном направлении - начиная с самого высокого значения и рендерившись к более низкому значению:

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

С помощью вышеуказанного исправления рендеринг карты должен быть исправлен:

Resulting image from correct rendering order

Подход "зигзаг":

Преимущество:

Возможно, преимущество подхода "зигзаг" в том, что визуализированная карта может показаться немного более вертикально компактной, чем подход "алмаз":

Zig-zag approach to rendering seems compact

Неудобство:

Из-за попытки реализовать технику зигзага, недостатком может быть то, что немного сложнее написать код рендеринга, потому что он не может быть написан так просто, как вложенный for -попад над каждым элементом в массиве. :

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
        )

Кроме того, может быть немного трудно определить координату тайла из-за смещенного характера порядка рендеринга:

Coordinates on a zig-zag order rendering

Примечание. Иллюстрации, включенные в этот ответ, были созданы с использованием Java-реализации представленного кода рендеринга плитки со следующим массивом int в качестве карты:

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

Изображения плитки:

  • tileImage[0] -> Коробка с коробкой внутри.
  • tileImage[1] -> Черный ящик.
  • tileImage[2] -> Белая коробка.
  • tileImage[3] -> Коробка с высоким серым предметом внутри.

Примечание по ширине и высоте плитки

Переменные tile_width и tile_height, которые используются в приведенных выше примерах кода, относятся к ширине и высоте основного фрагмента изображения, представляющего фрагмент:

Image showing the tile width and height

Использование размеров изображения будет работать до тех пор, пока размеры изображения и размеры плитки совпадают. В противном случае карта тайлов может быть визуализирована с промежутками между тайлами.

10 голосов
/ 21 мая 2009

В любом случае, работа выполнена. Я предполагаю, что под зигзагом вы подразумеваете что-то вроде этого: (числа - это порядок рендеринга)

..  ..  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  ..  ..

А под алмазом вы подразумеваете:

..  ..  ..  ..  ..
  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
..  ..  ..  ..  ..

Для первого метода требуется больше визуализированных плиток, чтобы рисовать весь экран, но вы можете легко выполнить проверку границ и пропустить любые плитки полностью за пределами экрана. Оба метода потребуют некоторого сокращения чисел, чтобы выяснить, где находится фрагмент 01. В итоге оба метода примерно равны с точки зрения математики, необходимой для определенного уровня эффективности.

1 голос
/ 25 сентября 2014

Если у вас есть несколько плиток, которые превышают границы вашего бриллианта, я рекомендую рисовать в порядке глубины:

...1...
..234..
.56789.
..abc..
...d...
0 голосов
/ 09 января 2015

Реальная проблема в том, что вам нужно нарисовать несколько плиток / спрайтов, пересекающих / охватывающих две или более других плиток.

После 2 (тяжелых) месяцев личного анализа проблемы я наконец нашел и реализовал «правильный чертеж рендеринга» для моей новой игры cocos2d-js. Решение состоит в том, чтобы отобразить для каждой плитки (восприимчивой), какие спрайты являются «спереди, сзади, сверху и сзади». Сделав это, вы можете нарисовать их, следуя «рекурсивной логике».

0 голосов
/ 11 ноября 2014

Ответ Коберда правильный, полный. Тем не менее, я объединил его подсказки с подсказками с другого сайта, чтобы создать код, который работает в моем приложении (iOS / Objective-C), которым я хотел поделиться с любым, кто приходит сюда, ища такую ​​вещь. Пожалуйста, если вам нравится / проголосуйте за этот ответ, сделайте то же самое для оригиналов; все, что я сделал, это «встал на плечи великанов».

Что касается порядка сортировки, моя техника представляет собой модифицированный алгоритм художника: каждый объект имеет (а) высоту основания (я называю «уровень») и (б) X / Y для «базы» или « стопы изображения (примеры: основание аватара у его ног; основание дерева у его корней; основание самолета - центральное изображение и т. д.) Затем я просто сортирую от самого низкого до самого высокого уровня, затем от самого низкого (самый высокий на экране) до самая высокая база-Y, затем самая низкая (самая левая) к самой высокой базе-X. Это делает плитки так, как можно было бы ожидать.

Код для преобразования экрана (точки) в плитку (ячейку) и обратно:

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:
//      /771974/risovanie-izometricheskih-igrovyh-mirov
// 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;
}
...