Isométrica 2D: coordenadas de pantalla a mosaico

9

Estoy escribiendo un juego 2D isométrico y tengo dificultades para determinar con precisión en qué mosaico está el cursor. Aquí hay un dibujo:

donde xs e ys son coordenadas de pantalla (píxeles), xt e yt son coordenadas de mosaico, W y H son ancho de mosaico y altura de mosaico en píxeles, respectivamente. Mi notación para coordenadas es (y, x) que puede ser confusa, lo siento.

Lo mejor que pude entender hasta ahora es esto:

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

Esto parece casi correcto, pero me está dando un resultado muy impreciso, lo que dificulta la selección de ciertos mosaicos, o a veces selecciona un mosaico junto al que estoy tratando de hacer clic. No entiendo por qué y me gustaría que alguien pudiera ayudarme a entender la lógica detrás de esto.

¡Gracias!

Asik
fuente

Respuestas:

2

Para una medida precisa, podríamos considerar lo siguiente:

Primero consideremos cómo transformar las coordenadas del espacio isométrico, determinado por los vectores i y j (como en isometricMap [i, j]) o como yt y xt en la pantalla, al espacio de la pantalla, determinado por x e y de la pantalla. Supongamos que el espacio de su pantalla está alineado en origen con el espacio isométrico por simplicidad.

Una forma de hacer la transformación es hacer primero una rotación, luego escalar el eje y o x. Para obtener los valores necesarios para que coincidan con su yt y xt, no puedo encontrar el lugar aquí. Puede crear una matriz para hacer esto o no y luego usar la matriz inversa, pero la operación inversa es básicamente lo que desea.

Escale el valor en reversa y luego gire hacia atrás para obtener los valores y redondear hacia abajo.

Supongo que hay otras formas de hacerlo, pero me parece lo más adecuado en este momento.

Toni
fuente
Argh. He estado revisando esta publicación muchas veces y creo que no puedo entender mi punto tan claramente como me gustaría de todos modos. Necesito dormir.
Toni
1
Gracias, las matrices son definitivamente la mejor solución aquí. ¡Tengo algo casi funcionando ahora!
Asik
4

Tuve el mismo problema para un juego que estaba escribiendo. Me imagino que este problema diferirá en función de cómo implementó exactamente su sistema isométrico, pero explicaré cómo resolví el problema.

Primero comencé con mi función tile_to_screen. (Supongo que así es como está colocando los mosaicos en la ubicación correcta en primer lugar). Esta función tiene una ecuación para calcular screen_x y screen_y. El mío se veía así (python):

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)

Tomé esas dos ecuaciones y las convertí en un sistema de ecuaciones lineales. Resuelve este sistema de ecuaciones en cualquier método que elijas. (Utilicé un método rref. Además, algunas calculadoras gráficas pueden resolver este problema).

Las ecuaciones finales se veían así:

# 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)

Como puede ver, no es tan simple como la ecuación inicial. Pero funciona muy bien para el juego que creé. ¡Gracias a Dios por el álgebra lineal!

Actualizar

Después de escribir una clase Point simple con varios operadores, simplifiqué esta respuesta a lo siguiente:

# 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)
Thane Brimhall
fuente
Sí, un sistema de dos ecuaciones lineales también debería funcionar. Teniendo en cuenta que tenemos dos vectores que no son paralelos, debería poder obtener cualquier punto en el plano utilizando los vectores unitarios de yt y xt. Aunque creo que su implementación es un poco restringida y no voy a molestarme en validarla.
Toni
2

Estás usando un buen sistema de coordenadas. Las cosas se ponen mucho más complicadas si usas columnas escalonadas.

Una forma de pensar sobre este problema es que tiene una función para convertir (xt, yt) en (xs, ys). Seguiré la respuesta de Thane y la llamaré map_to_screen.

Desea el inverso de esta función. Podemos llamarlo screen_to_map. Las funciones inversas tienen estas propiedades:

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

Esos dos son buenos elementos para la prueba unitaria una vez que haya escrito ambas funciones. ¿Cómo se escribe el inverso? No todas las funciones tienen inversas pero en este caso:

  1. Si lo escribió como una rotación seguida de una traducción, entonces el inverso es la traducción inversa (dx negativo, dy) seguido de la rotación inversa (ángulo negativo).
  2. Si lo escribió como una matriz multiplicada, entonces el inverso es la matriz inversa multiplicada.
  3. Si lo escribió como ecuaciones algebraicas que definen (xs, ys) en términos de (xt, yt), entonces el inverso se encuentra resolviendo esas ecuaciones para (xt, yt) dado (xs, ys).

Asegúrese de probar que la función inversa + original devuelve la respuesta con la que comenzó. Thane pasa ambas pruebas, si elimina el + TILE_HEIGHT/2desplazamiento de renderizado. Cuando resolví el álgebra, se me ocurrió:

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

que creo que es lo mismo que el de Thane screen_to_map.

La función convertirá las coordenadas del mouse en flotadores; use floorpara convertirlos en coordenadas de mosaico de enteros.

amitp
fuente
1
¡Gracias! Terminé usando una matriz de transformación, por lo que escribir el inverso es trivial, es decir, es solo Matrix.Invert (). Además, conduce a un estilo de codificación más declarativo (Matrix.Translate () * Matrix.Scale () * Matrix.Rotate () en lugar de un montón de ecuaciones). Sin embargo, tal vez sea un poco más lento, pero eso no debería ser un problema.
Asik