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


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

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

есть ли преимущества или недостатки метода?

5 170

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

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

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

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

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

ответ 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://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;
}

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

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