Рисование Изометрических игровых миров

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

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

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

5 ответов

Решение

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

Возможно, преимущество техники зигзага для отображения плиток на экране можно сказать, что плитка 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 координаты плиток на карте будут увеличиваться в виде диагональных линий, что может затруднить визуальное сопоставление местоположения на экране с картой, представленной в виде массива:

Изображение карты тайла

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

Результирующее изображение из неправильного порядка рендеринга

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

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

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

Результирующее изображение из правильного порядка рендеринга

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

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

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

Зигзагообразный подход к рендерингу кажется компактным

Недостаток:

Недостатком может быть то, что при попытке реализовать технику зигзага немного сложнее написать код рендеринга, поскольку он не может быть написан так просто, как вложенный. 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
        )

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

Координаты при зигзагообразном заказе

Примечание. Иллюстрации, включенные в этот ответ, были созданы с использованием 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 которые используются в приведенных выше примерах кода, относятся к ширине и высоте основного фрагмента изображения, представляющего фрагмент:

Изображение, показывающее ширину и высоту плитки

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

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

..  ..  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...
..234..
.56789.
..abc..
...d...

Вы можете использовать евклидово расстояние от самой высокой и ближайшей к зрителю точки, но это не совсем верно. Это приводит к сферическому порядку сортировки. Вы можете исправить это, посмотрев издалека. По мере удаления кривизна становится более плоской. Поэтому просто добавьте, скажем, 1000 к каждому из компонентов x,y и z, чтобы получить x ', y' и z'. Сортировка по x'*x'+y'*y'+z'*z'.

Ответ Coobird правильный, полный. Тем не менее, я объединил его подсказки с подсказками с другого сайта, чтобы создать код, который работает в моем приложении (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:
//      http://stackru.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;
}

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

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

Другие вопросы по тегам