Еще раз немного про изометрию, перерасчет координат и вращение камеры

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

Постановка задачи

Для начала определимся, с какой изометрией мы работает. Мне пришлась по душе изометрия 2:1. Здесь 2:1 означает соотношение длины к ширине поверхности тайлового куба.
 
Вот пример такого кубика:
 
 
Следует прояснить некоторые моменты.
 
  • Перерасчитываются координаты для рисования поверхности куба. Т.е. код подойдет и для случая, если Ваши тайлы плоские
 
Размер одного такого тайла — 64x64 пикселя. Отсюда следует запомнить несколько важных констант.
  • CellDiagonalInPixels = 32 - длина «меньшей» диагонали верхней стороны куба 
  • CellStep = CellCartesianDiagonalInPixels / 2 - шаг рисования каждого тайла по обоим осям
  • CellHalfWidth = 32 - половина ширины тайла
  • CellHalfHeight = 16 - половины высоты тайла поверхности кубика
 

Базовый перевод координат

Теперь поговорим о переводе координат. Для определенности — используется классическая система координат (diamond, а не zig-zag). На просторах интернета, после изучения всех тонкостей вопроса преобразования координат, самая точная и самая приятная статья - Isometric Tiles Math. Приведу здесь конкретный адаптированный код для перевода между системами кординат.
 
public Vector2 MapToScreen(Vector2 source)
{
    return new Vector2(source.X * GameConstants.CellHalfWidth — source.Y * GameConstants.CellHalfWidth,
        source.X * GameConstants.CellHalfHeight + source.Y * GameConstants.CellHalfHeight);
}
 
Собственно, здесь приведен код, взятый из статьи. Он рассчитывает экранные координаты для проекции следующего вида:
Картинка из статьи
 
Мы получили возможность переводить координаты из изометрических в экранные. Теперь определим обратное преобразование!
 
public Vector2 ScreenToMap(Vector2 screenSouce)
{
    return new Vector2((source.X / GameConstants.CellHalfWidth + source.Y / GameConstants.CellHalfHeight) / 2.0f,
        (source.Y / GameConstants.CellHalfHeight — source.X / GameConstants.CellHalfWidth) / 2.0f);
}
 
Как можно заметить, этот код идентичен переводу в экранные координаты с разницей лишь в том, что тут все действия выполняются с точностью до наоборот.
 
Отлично, мы определили перевод. Но как же сделать вращение?
 

Вращение камеры

Чтобы сделать вращение камеры, прежде всего определим ее направление. Я определил следующие направления (довольно таки очевидные):

public enum FaceDirection: byte
{   
    NorthWest = 0,
    EastNorth = 1,
    SouthEast = 2,
    WestSouth = 3
}
 
Очевидно, что определенные выше переводы между координатами относятся к случаю, когда камера смотрит на северо-запад (если предположить, что верхняя сторона квадрата в двумерной проекции смотрит на север, а левая сторона квадрата смотрит на запад). Но как же перерасчитать координаты для других направлений?
 
В статье Rotating a 2.5D isometric map неплохо объясняется, как это вращение надо делать, но оно не подхоит из-за того, что вылезают параметры длины и ширины. Путем магических танцев с бубнами, экспериментов и рандомного перебора были получены рабочие методы перерасчета координат карты в экранные координаты:
 
 
А также перерасчет из экранных координат в координаты карты:
 
 
В этих исходных кодах CameraDirection — это переменная типа FaceDirection, которая определяет, куда смотрит камера.
 
Ну и в конце — полученные результаты!

 

CameraDirectionNorthWest

CameraDirectionEastNorth

CameraDirectionSouthEast

CameraDirectionWestSouth

 
 Спасибо за внимание и до новых встреч!

Похожие статьи

  • Генерация мира: Ландшафт и характеристики местности
    Что нам стоит мир построить?Задачи генерации целого мира в игре никогда не давала мне покоя. Полностью созданный мир на основе псевдо-случайных чисел делает опыт каждого игрока ...
  • Аудио-спектральный принтер
    Вы когда-нибудь слышали о скрытых посланиях в музыке? Я слышал. Больше всего меня заинтересовало, каким образом можно вставить изображение в трек, как в этих случаях. Я узнал,...
  • Курс по разработки игр на примере XNA
    Есть такой сайт, coursera.org, на нём университеты выкладывают курсы, в них люди смотрят видео, записанные разными профессорами, и каждую неделю выполняют задания, которые...
  • В поисках света и тени
    СинопсисНа этот раз я хочу рассказать, как ковалась сталь и как закалялся металл… Хотя пожалуй об этом как нибудь в другой раз, а сейчас я лучше расскажу о том как реализован...
7 комментариев
Xitilon

Я тут подумал — тэги у нас в постах используются русские. Возможно, наличие английских тэгов как-то помогает Гуглу и компании искать этот пост, но сам пост ведь на русском. Так что есть соображение либо дублировать на русском, либо вовсе только на нём и писать. Так как на Коленке нет практики английских тэгов, то блок «Похожие статьи» высвечивает только статьи по C#… хотя так-то других упомянутых в этих двух постах тэгов на сайте пока что нет и на русском. Но кто знает, могут быть потом. (хотя нет, тэг «генерация» явно есть у Tekx'а моего)

CellDiagonalInPixels = 32 — длина «меньшей» диагонали квадрата 

 Вот этого момента я не понял. У нас по факту есть:

  • Куб, который предполагается изобразить
  • Его проекция — шестиугольник, который по сути ограничен квадратом.
  • Тайл грани куба (непонятно, квадрат или нет?)

Напрашивается вопрос — «меньшая» диагональ какого квадрата, и почему «меньшая». Из контекста я не понял. Может, пропустил что, не знаю.

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

denisbarrett

Спасибо за отзыв!

Да, действительно есть некоторая неразбериха, я обновил формулировку

CellDiagonalInPixels = 32 - длина «меньшей» диагонали верхней стороны куба 

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

 

Теги — поправил, теперь буду использовать русские.

Xitilon

Верх самого куба — квадрат, да; однако верх куба, который проецируется в 2Д как шестиугольник — ромб. Вот это слово я бы тогда сразу понял, о чём оно. Этот кубик бы в пост вставить, и там написать что визуально это ромб — тогда отлично!

Xitilon

Вообще, как только увидел этот пост, сразу вспомнил игру RollerCoaster Tycoon:

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

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

Raseri

>>столько воды с тех пор утекло, а жанров так много, а я один. Как-то недосуг пока.

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

Xitilon

Вообще это оффтоп, но знаешь, отсутствующие иконки это наверное наименьшая из моих (твоих) проблем.

Подбей кого-нибудь другого нарисовать в крайнем случае.

Xitilon
Я перенёс этот пост из блога «Игра изнутри» в «Программирование», так как первый подразумевает, что рассматривается более-менее конкретная, устоявшаяся игра и её разработка. Пожалуй, так будет правильней по категориям.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.