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

178

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

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

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

Бенни Халлетт
источник

Ответы:

504

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

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

Недостаток:

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

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

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

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

Чтобы исправить эту проблему, 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)
        )

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

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

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

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

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

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

Недостаток:

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

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

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

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

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

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

coobird
источник
136
Вы даже рисовали картины. Это усилие.
Заратустра
Ура coobird. Я использую алмазный подход для текущей игры, которую я разрабатываю, и она приносит удовольствие. Еще раз спасибо.
Бенни Халлетт
3
Как насчет нескольких слоев высоты? Могу ли я нарисовать их с улыбкой, начиная с самого низкого уровня и продолжая до достижения самого высокого уровня?
NagyI
2
@DomenicDatti: Спасибо за добрые слова :)
coobird
2
Это круто. Я просто использовал свой алмазный подход , а также, в случае , если кто - то должен получить сетки COORDS от положения экрана, я сделал это: j = (2 * x - 4 * y) / tilewidth * 0.5; i = (p.x * 2 / tilewidth) - j;.
Кыр Дуненкофф
10

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

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

Заратустра
источник
14
Я на самом деле имел в виду наоборот. Форма ромба (который оставляет края карты гладкими) и зигзагообразный метод, оставляя острие краев
Бенни Халлетт
1

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

...1...
..234..
.56789.
..abc..
...d...
Воутер Ливенс
источник
1

Ответ 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;
}
Olie
источник
1

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

Сиобхан О'Коннор
источник
0

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

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

Карлос Лопес
источник