2D изометрия: координаты экрана

9

Я пишу изометрическую 2D-игру, и мне трудно точно определить, на какой плитке находится курсор. Вот рисунок:

где xs и ys - координаты экрана (в пикселях), xt и yt - координаты элемента мозаичного изображения, W и H - ширина элемента мозаичного изображения и высота элемента в пикселях соответственно. Моя запись для координат (у, х), которая может сбивать с толку, извините за это.

Лучшее, что я мог понять, это:

int xtemp = xs / (W / 2);
int ytemp = ys / (H / 2);
int xt = (xs - ys) / 2;
int yt = ytemp + xt;

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

Спасибо!

Asik
источник

Ответы:

2

Для точного измерения мы могли бы рассмотреть следующее:

Давайте сначала рассмотрим, как преобразовать координаты из изометрического пространства, определяемого векторами i и j (как в isometricMap [i, j]) или как yt и xt на экране, в пространство экрана, определяемое по x и y экрана. Предположим, что ваше пространство экрана выровнено в начале координат с изометрическим пространством для простоты.

Один из способов сделать преобразование - сначала выполнить вращение, а затем масштабировать ось y или x. Чтобы получить необходимые значения, совпадающие с вашими yt и xt, я не могу придумать тут то же самое. Вы можете создать матрицу для этого или нет, а затем использовать обратную матрицу, но обратная операция в основном то, что вы хотите.

Масштабируйте значение в обратном порядке, а затем поверните назад, чтобы получить значения и округлить вниз.

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

Toni
источник
Argh. Я пересматривал этот пост очень много раз, и я думаю, что не могу донести свою мысль так аккуратно, как хотелось бы в любом случае. Мне нужно поспать.
Тони
1
Спасибо, матрицы, безусловно, лучшее решение здесь. У меня сейчас что-то почти работает!
Асик
4

У меня была такая же проблема для игры, которую я писал. Я предполагаю, что эта проблема будет отличаться в зависимости от того, как именно вы внедрили свою изометрическую систему, но я объясню, как я решил проблему.

Я впервые начал с моей функции tile_to_screen. (Я предполагаю, что именно так вы и размещаете плитки в нужном месте.) Эта функция имеет уравнение для вычисления screen_x и screen_y. Моя выглядела так (питон):

def map_to_screen(self, point):
    x = (SCREEN_WIDTH + (point.y - point.x) * TILE_WIDTH) / 2
    y = (SCREEN_HEIGHT + (point.y + point.x) * TILE_HEIGHT) / 2
    return (x, y)

Я взял эти два уравнения и превратил их в систему линейных уравнений. Решите эту систему уравнений любым методом, который вы выберете. (Я использовал метод rref. Кроме того, некоторые графические калькуляторы могут решить эту проблему.)

Окончательные уравнения выглядели так:

# constants for quick calculating (only process once)
DOUBLED_TILE_AREA = 2 * TILE_HEIGHT * TILE_WIDTH
S2M_CONST_X = -SCREEN_HEIGHT * TILE_WIDTH + SCREEN_WIDTH * TILE_HEIGHT
S2M_CONST_Y = -SCREEN_HEIGHT * TILE_WIDTH - SCREEN_WIDTH * TILE_HEIGHT

def screen_to_map(self, point):
    # the "+ TILE_HEIGHT/2" adjusts for the render offset since I
    # anchor my sprites from the center of the tile
    point = (point.x * TILE_HEIGHT, (point.y + TILE_HEIGHT/2) * TILE_WIDTH)
    x = (2 * (point.y - point.x) + self.S2M_CONST_X) / self.DOUBLED_TILE_AREA
    y = (2 * (point.x + point.y) + self.S2M_CONST_Y) / self.DOUBLED_TILE_AREA
    return (x, y)

Как видите, это не так просто, как исходное уравнение. Но это хорошо работает для игры, которую я создал. Слава Богу за линейную алгебру!

Обновить

После написания простого класса Point с различными операторами я упростил этот ответ до следующего:

# constants for quickly calculating screen_to_iso
TILE_AREA = TILE_HEIGHT * TILE_WIDTH
S2I_CONST_X = -SCREEN_CENTER.y * TILE_WIDTH + SCREEN_CENTER.x * TILE_HEIGHT
S2I_CONST_Y = -SCREEN_CENTER.y * TILE_WIDTH - SCREEN_CENTER.x * TILE_HEIGHT

def screen_to_iso(p):
    ''' Converts a screen point (px) into a level point (tile) '''
    # the "y + TILE_HEIGHT/2" is because we anchor tiles by center, not bottom
    p = Point(p.x * TILE_HEIGHT, (p.y + TILE_HEIGHT/2) * TILE_WIDTH)
    return Point(int((p.y - p.x + S2I_CONST_X) / TILE_AREA),
                 int((p.y + p.x + S2I_CONST_Y) / TILE_AREA))

def iso_to_screen(p):
    ''' Converts a level point (tile) into a screen point (px) '''
    return SCREEN_CENTER + Point((p.y - p.x) * TILE_WIDTH / 2,
                                 (p.y + p.x) * TILE_HEIGHT / 2)
Тейн Бримхолл
источник
Да, система двух линейных уравнений также должна работать. Учитывая, что у нас есть два вектора, которые не параллельны, вы сможете получить любую точку на плоскости, используя единичные векторы yt и xt. Хотя я думаю, что ваша реализация немного ограничена, и я не собираюсь ее проверять.
Тони
2

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

Одним из способов решения этой проблемы является то, что у вас есть функция для преобразования (xt, yt) в (xs, ys). Я последую ответу Тейна и позвоню map_to_screen.

Вы хотите обратную функцию. Мы можем это назвать screen_to_map. Обратные функции имеют следующие свойства:

map_to_screen(screen_to_map(xs, ys)) == (xs, ys)
screen_to_map(map_to_screen(xt, yt)) == (xt, yt)

Эти две вещи полезны для модульного тестирования, если у вас есть обе функции. Как ты пишешь обратное? Не у всех функций есть обратные, но в этом случае:

  1. Если вы записали его как вращение с последующим переводом, то обратный - это обратный перевод (отрицательный dx, dy), за которым следует обратный поворот (отрицательный угол).
  2. Если вы записали это как матричное умножение, то обратное - это матричное обратное умножение.
  3. Если вы написали это как алгебраические уравнения, определяющие (xs, ys) в терминах (xt, yt), то обратное вычисляется путем решения этих уравнений для (xt, yt) данных (xs, ys).

Не забудьте проверить, что обратная + оригинальная функция возвращает ответ, с которого вы начали. Тейн проходит оба теста, если вы + TILE_HEIGHT/2уберете смещение рендера. Когда я решил алгебру, я придумал:

x = (2*xs - SCREEN_WIDTH) / TILE_WIDTH
y = (2*ys - SCREEN_HEIGHT) / TILE_HEIGHT
yt =  (y + x) / 2
xt =  (y - x) / 2

который я считаю такой же, как у Тейна screen_to_map.

Функция превратит координаты мыши в поплавки; используйте floorдля преобразования их в целочисленные координаты плитки.

amitp
источник
1
Спасибо! В итоге я использовал матрицу преобразования, так что написание обратного тривиально, то есть это просто Matrix.Invert (). Кроме того, это приводит к более декларативному стилю кодирования (Matrix.Translate () * Matrix.Scale () * Matrix.Rotate (), а не к группе уравнений). Может быть, это немного медленнее, но это не должно быть проблемой.
Асик