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

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

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

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

Ответ 1

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

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

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

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

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 -loop с довольно прямой логикой, которая работает последовательно во всех фрагментах.

Неудобство:

Один недостаток этого подхода состоит в том, что координаты 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)
        )

С приведенным выше исправлением рендеринг карты следует скорректировать:

Результат из правильного порядка сортировки

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

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

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

Zig-zag подход к рендерингу кажется компактным

Неудобство:

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

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
        )

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

Координаты при визуализации zig-zag order

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

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

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

Ответ 2

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

..  ..  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. В конце концов, оба метода примерно равны по математике, необходимой для определенного уровня эффективности.

Ответ 3

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

...1...
..234..
.56789.
..abc..
...d...

Ответ 4

Ответ 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;
}

Ответ 5

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

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