En la última entrada vimos cómo vamos a modelar a nivel de clases nuestro primer proyecto, pero también surgieron ciertos conceptos que pueden ser nuevos, como puede ser eso de "coordenadas de mundo" o cuestiones de cómo saber si dos cajas colisionan entre sí. Por ello en esta entrada vamos a ver un poco, de una manera en absoluto formal, las sencillas matemáticas que vamos a usar en los proyectos de este blog.
Sistema de coordenadas
Un sistema de coordenadas permite organizar de cierta manera el espacio, para poder colocar, mover y controlar los diferentes objetos del juego. En la mayoría de los videojuegos se usan sistemas de coordenadas cartesianos. Un sistema de este tipo en un espacio 2D son 2 ejes perpendiculares. Normalmente el eje horizontal se le llama X, y al eje vertical Y. En un espacio 3D los ejes suelen ser X, Y y Z, perpendiculares entre sí también.
Volvamos al sistema de coordenadas de SFML:
Arriba a la izquierda tenemos el origen del sistema de coordenadas de SFML, que representamos con los valores X e Y (0, 0). Esta representación se llama "vector" en matemáticas (más sobre esto después). El primer número corresponde al eje X, el segundo al eje Y. El eje X en este sistema de coordenadas crece hacia la derecha, y el eje Y hacia abajo. Para representar un punto en la esquina superior derecha usamos el vector (799, 0), por ejemplo. Si quisiésemos representar un punto en el centro de la pantalla usaríamos el vector (399, 299). Esto es simplemente el sistema de SFML pero existen otros similares pero diferentes, como por ejemplo que el eje Y crezca hacia arriba en vez de hacia abajo con el punto (0, 0) en la esquina inferior izquierda, etc.
Según las necesidades también podemos definir nuestro propio sistema de coordenadas y pasar de este al de SFML (o a la librería que estamos utilizando) o viceversa según lo que toque. Un ejemplo claro de esto sería un juego isométrico, donde el mapeado (bloques, enemigos y demás elementos) están en su propio sistema de coordenadas que al pintar los gráficos 2D pasamos a las coordenadas de la librería gráfica.
Según las necesidades también podemos definir nuestro propio sistema de coordenadas y pasar de este al de SFML (o a la librería que estamos utilizando) o viceversa según lo que toque. Un ejemplo claro de esto sería un juego isométrico, donde el mapeado (bloques, enemigos y demás elementos) están en su propio sistema de coordenadas que al pintar los gráficos 2D pasamos a las coordenadas de la librería gráfica.
Vectores
Un vector es un elemento matemático que se puede interpretar como una posición en el espacio o bien una dirección (y de cualquier otra manera según nuestras necesidades, pero aquí veremos estas dos). En un espacio 2D esto se consigue simplemente con un par de números que van "juntos". En un espacio 3D usaríamos 3, obviamente. Ya hemos visto un vector antes al definir las posiciones con la forma (X, Y). ¿Cómo se define una dirección con un vector? De la misma forma que una posición, sólo que su interpretación es diferente. Si tenemos el vector (5, 5) y lo interpretamos como posición sería un punto arriba a la izquierda de la pantalla (en el sistema de SFML). En cambio si el mismo vector lo interpretamos como una dirección, por decirlo de alguna forma tenemos una "flecha". Esta flecha la podemos imaginar como que sale de (0, 0) y va a la posición (5, 5), pero la gracia de las direcciones es que no salen de ninguna parte sino que expresan eso, una dirección, y la podemos aplicar en cualquier parte del espacio. Ya veremos cómo.
Los vectores poseen varias operaciones matemáticas: se pueden sumar, restar, multiplicar o dividir por un valor escalar (un escalar es un valor no vectorial, es decir un número "normal", aunque también vale decir que los vectores se forman con varios números escalares), calcular su producto escalar o vectorial, su módulo, etc. Con estas operaciones podremos mover nuestros elementos del juego, calcular distancias, ángulos, etc.
La suma y resta de vectores es muy intuitiva: si tenemos los vectores V1 y V2, con los valores (x1, y1) y (x2, y2), el resultado de su suma (V3 = V1 + V2) sería (x3, y3) = (x1 + x2, y1 + y2). La resta sería igual, solo que restando: (x3, y3) = (x1 - x2, y1 - y2). Obviamente en la resta el orden es importante.
La multiplicación con un valor escalar es también muy simple: V2 = V1 * a, donde "a" es el valor escalar: (x2, y2) = (x1 * a, y1 * a). La división (V2 = V1 / a) es igual: (x2, y2) = (x1 / a, y1 / a).
El módulo de un vector representa su longitud y da como resultado un valor escalar. Teniendo el vector V1 se calcula el módulo m así: m = sqrt(x1 * x1 + y1 * y1) donde la función sqrt() calcula la raíz cuadrada. En efecto es el teorema de Pitágoras. Si por ejemplo tenemos el vector (5, 5) de antes y calculamos su módulo éste nos indicará la distancia entre (0, 0) y (5, 5), que efectivamente es la longitud de la "flecha" si interpretamos este vector como una dirección. El módulo del vector V1 se representaría como |V1|
Se dice que un vector está normalizado cuando su longitud es de una unidad. Muchas veces necesitaremos que nuestros vectores estén normalizados. Para ello se calcula su módulo y con este valor se divide el vector, de forma que mida 1 pero mantenga la dirección: V1' = V1 / |V1| donde V1' es V1 normalizado.
El producto escalar ("dot product" en ingles) es un valor que se calcula con 2 vectores. Teniendo los vectores V1 y V2 su producto escalar "d" se calcularía así: d = x1 * x2 + y1 * y2. Esto también se expresa de esta forma: V1 · V2 = x1 * x2 + y1 * y2. El punto "·" es el que le da el nombre a esta operación en inglés. El producto vectorial lo dejamos para otro día ;)
Bueno, ya hemos visto ciertas operaciones que se pueden hacer con los vectores. ¿Para qué sirven o qué sentido tienen? Vamos a ver todo esto aunque no de una forma demasiado profunda o académica (para esto podéis consultar la wikipedia o cualquier libro de matemáticas).
La suma y resta de vectores podemos usarlas para mover nuestros elementos del juego. Si tenemos nuestro sprite en (0, 0) y queremos moverlo 1 unidad a la derecha, le sumamos el vector de dirección (1, 0): (0, 0) + (1, 0) = (1, 0). Si tenemos el sprite en (60, 40) y lo queremos mover en diagonal 5 unidades en cada eje le sumamos (5, 5): (60, 40) + (5, 5) = (65, 45)
La multiplicación y división permite escalar (agrandar o reducir) el módulo de un vector. Esto es muy útil por ejemplo para controlar las velocidades: si tenemos un vector que representa la velocidad de nuestro sprite, con multiplicar por dos este vector se moverá el doble de rápido. Si lo dividimos por dos a su vez tendremos un vector con la mitad del módulo.
El cálculo del módulo de un vector nos permite saber la distancia entre dos puntos del espacio. Esto es útil para colisiones, comportamiento de los enemigos, etc.
El producto escalar de 2 vectores representa una proyección de un vector en el otro (entre otras cosas), pero también satisface esta igualdad: V1 · V2 = cos(a) * |V1| * |V2|. Esto significa que el producto escalar entre los vectores V1 y V2 es igual a la multiplicación del módulo de V1 con el módulo de V2 y cos(a), donde "a" es el ángulo entre los vectores V1 y V2. Si V1 y V2 están normalizados significa que su módulo es 1, por lo que tendríamos V1 · V2 = cos(a) * 1 * 1, es decir V1 · V2 = cos(a). Esto nos da el coseno del ángulo de los vectores V1 y V2, por lo que si hacemos arccos(V1 · V2) = a, es decir el ángulo entre V1 y V2, algo muy usado en los videjuegos y en otros muchos campos (gráficos 3D, etc etc etc).
Coordenadas "de mundo"
Ya hemos visto los sistemas de coordenadas y los vectores un poco por encima (si los ve algún matemático igual me pega y todo, ejem...) pero todavía no hemos visto eso de "coordenadas de mundo" que se habló en la anterior entrada. Coordenadas de mundo, de pantalla, de sprite, etc, es una forma de decir que nuestro sistema de coordenadas es relativo a algo. Tomemos el caso de un enemigo que tenemos en un mapa de un juego de plataformas. El mapa mide 2400x600 unidades, que al corresponder a píxeles (en este ejemplo) equivalen a 3 pantallas de ancho y uno de alto, asumiendo una resolución de pantalla de 800x600. Si tenemos el enemigo justo en la mitad del mapa, sus coordenadas por ejemplo podrían ser (1200, 300). Estas coordenadas son coordenadas mundo o de mapa. Si el jugador se acerca al enemigo y es visible, éste se deberá pintar en alguna parte de la pantalla. Tenemos que pasar de coordenadas de mundo a coordenadas de pantalla. Por ejemplo según la posición del scroll y demás el enemigo lo pintaríamos en la posición (200, 100) de la pantalla. Como se puede ver, el enemigo "tiene" varias coordenadas, ya sea la (1200, 300) o la (200, 100).
¡Pero aún hay más! Imaginemos que este enemigo es un escorpión gigante. El pincho de la cola es lo único que nos daña, el resto del enemigo lo podemos tocar sin dañarnos. De alguna manera debemos hacer esto posible. Una solución es definir una caja (de las que hablamos en la entrada anterior) que englobe esta parte de la imagen del escorpión y si colisiona con la caja del protagonista que le haga daño. Ahora viene la pregunta: ¿En qué coordenadas definimos la caja? La solución es en coordenadas de sprite: la caja tendrá sus datos relativos a la imagen del escorpión, y luego el código que gestiona estas cosas se encargará de pasar de estas coordenadas a las de mundo para comparar esta caja con el del sprite del personaje protagonista, también definida en coordenadas de sprite y pasada a coordenadas de mundo.
Veamos esto con ejemplos gráficos. Aquí tenemos el PNG donde tenemos nuestro escorpión:
La caja roja indica la zona que daña al protagonista. Lo hemos definido en coordenadas de sprite, es decir, sus coordenadas dentro del PNG que lo contiene. Asumimos que el sistema es igual que al de SFML, es decir, la coordenada (0, 0) está arriba a la izquierda y los ejes crecen de izquierda y derecha y de arriba abajo.
La siguiente imagen muestra todo el montaje. Aquí también seguimos el sistema de coordenadas de SFML, aunque para el mapa podríamos usar otro cualquiera:
La caja negra representa el tamaño del mapa, 2400x600 píxeles, y la caja naranja representa la pantalla de 800x600. El escorpión está en el mapa, con su caja roja. Partiendo de su posición en coordenadas de mundo podemos calcular sus coordenadas de pantalla. También hay que calcular, como hemos visto, la posición en coordenadas de mundo que tiene su caja para saber si colisiona o no con la del protagonista. Hemos dicho que las cajas se pasan a coordenadas de mundo para su comprobación pero también sería posible pasarlas a coordenadas de pantalla y comprobar su colisión.
La conversión entre las diferentes coordenadas (también llamado mapeo o proyección según el contexto) en este caso serían simples sumas y restas. En el caso de un juego isométrico o 3D por ejemplo habría que hacer cálculos más sofisticados, ya que las coordenadas de mundo son en 3D y las de pantalla en 2D.
Los vectores poseen varias operaciones matemáticas: se pueden sumar, restar, multiplicar o dividir por un valor escalar (un escalar es un valor no vectorial, es decir un número "normal", aunque también vale decir que los vectores se forman con varios números escalares), calcular su producto escalar o vectorial, su módulo, etc. Con estas operaciones podremos mover nuestros elementos del juego, calcular distancias, ángulos, etc.
La suma y resta de vectores es muy intuitiva: si tenemos los vectores V1 y V2, con los valores (x1, y1) y (x2, y2), el resultado de su suma (V3 = V1 + V2) sería (x3, y3) = (x1 + x2, y1 + y2). La resta sería igual, solo que restando: (x3, y3) = (x1 - x2, y1 - y2). Obviamente en la resta el orden es importante.
La multiplicación con un valor escalar es también muy simple: V2 = V1 * a, donde "a" es el valor escalar: (x2, y2) = (x1 * a, y1 * a). La división (V2 = V1 / a) es igual: (x2, y2) = (x1 / a, y1 / a).
El módulo de un vector representa su longitud y da como resultado un valor escalar. Teniendo el vector V1 se calcula el módulo m así: m = sqrt(x1 * x1 + y1 * y1) donde la función sqrt() calcula la raíz cuadrada. En efecto es el teorema de Pitágoras. Si por ejemplo tenemos el vector (5, 5) de antes y calculamos su módulo éste nos indicará la distancia entre (0, 0) y (5, 5), que efectivamente es la longitud de la "flecha" si interpretamos este vector como una dirección. El módulo del vector V1 se representaría como |V1|
Se dice que un vector está normalizado cuando su longitud es de una unidad. Muchas veces necesitaremos que nuestros vectores estén normalizados. Para ello se calcula su módulo y con este valor se divide el vector, de forma que mida 1 pero mantenga la dirección: V1' = V1 / |V1| donde V1' es V1 normalizado.
El producto escalar ("dot product" en ingles) es un valor que se calcula con 2 vectores. Teniendo los vectores V1 y V2 su producto escalar "d" se calcularía así: d = x1 * x2 + y1 * y2. Esto también se expresa de esta forma: V1 · V2 = x1 * x2 + y1 * y2. El punto "·" es el que le da el nombre a esta operación en inglés. El producto vectorial lo dejamos para otro día ;)
Bueno, ya hemos visto ciertas operaciones que se pueden hacer con los vectores. ¿Para qué sirven o qué sentido tienen? Vamos a ver todo esto aunque no de una forma demasiado profunda o académica (para esto podéis consultar la wikipedia o cualquier libro de matemáticas).
La suma y resta de vectores podemos usarlas para mover nuestros elementos del juego. Si tenemos nuestro sprite en (0, 0) y queremos moverlo 1 unidad a la derecha, le sumamos el vector de dirección (1, 0): (0, 0) + (1, 0) = (1, 0). Si tenemos el sprite en (60, 40) y lo queremos mover en diagonal 5 unidades en cada eje le sumamos (5, 5): (60, 40) + (5, 5) = (65, 45)
La multiplicación y división permite escalar (agrandar o reducir) el módulo de un vector. Esto es muy útil por ejemplo para controlar las velocidades: si tenemos un vector que representa la velocidad de nuestro sprite, con multiplicar por dos este vector se moverá el doble de rápido. Si lo dividimos por dos a su vez tendremos un vector con la mitad del módulo.
El cálculo del módulo de un vector nos permite saber la distancia entre dos puntos del espacio. Esto es útil para colisiones, comportamiento de los enemigos, etc.
El producto escalar de 2 vectores representa una proyección de un vector en el otro (entre otras cosas), pero también satisface esta igualdad: V1 · V2 = cos(a) * |V1| * |V2|. Esto significa que el producto escalar entre los vectores V1 y V2 es igual a la multiplicación del módulo de V1 con el módulo de V2 y cos(a), donde "a" es el ángulo entre los vectores V1 y V2. Si V1 y V2 están normalizados significa que su módulo es 1, por lo que tendríamos V1 · V2 = cos(a) * 1 * 1, es decir V1 · V2 = cos(a). Esto nos da el coseno del ángulo de los vectores V1 y V2, por lo que si hacemos arccos(V1 · V2) = a, es decir el ángulo entre V1 y V2, algo muy usado en los videjuegos y en otros muchos campos (gráficos 3D, etc etc etc).
Coordenadas "de mundo"
Ya hemos visto los sistemas de coordenadas y los vectores un poco por encima (si los ve algún matemático igual me pega y todo, ejem...) pero todavía no hemos visto eso de "coordenadas de mundo" que se habló en la anterior entrada. Coordenadas de mundo, de pantalla, de sprite, etc, es una forma de decir que nuestro sistema de coordenadas es relativo a algo. Tomemos el caso de un enemigo que tenemos en un mapa de un juego de plataformas. El mapa mide 2400x600 unidades, que al corresponder a píxeles (en este ejemplo) equivalen a 3 pantallas de ancho y uno de alto, asumiendo una resolución de pantalla de 800x600. Si tenemos el enemigo justo en la mitad del mapa, sus coordenadas por ejemplo podrían ser (1200, 300). Estas coordenadas son coordenadas mundo o de mapa. Si el jugador se acerca al enemigo y es visible, éste se deberá pintar en alguna parte de la pantalla. Tenemos que pasar de coordenadas de mundo a coordenadas de pantalla. Por ejemplo según la posición del scroll y demás el enemigo lo pintaríamos en la posición (200, 100) de la pantalla. Como se puede ver, el enemigo "tiene" varias coordenadas, ya sea la (1200, 300) o la (200, 100).
¡Pero aún hay más! Imaginemos que este enemigo es un escorpión gigante. El pincho de la cola es lo único que nos daña, el resto del enemigo lo podemos tocar sin dañarnos. De alguna manera debemos hacer esto posible. Una solución es definir una caja (de las que hablamos en la entrada anterior) que englobe esta parte de la imagen del escorpión y si colisiona con la caja del protagonista que le haga daño. Ahora viene la pregunta: ¿En qué coordenadas definimos la caja? La solución es en coordenadas de sprite: la caja tendrá sus datos relativos a la imagen del escorpión, y luego el código que gestiona estas cosas se encargará de pasar de estas coordenadas a las de mundo para comparar esta caja con el del sprite del personaje protagonista, también definida en coordenadas de sprite y pasada a coordenadas de mundo.
Veamos esto con ejemplos gráficos. Aquí tenemos el PNG donde tenemos nuestro escorpión:
La caja roja indica la zona que daña al protagonista. Lo hemos definido en coordenadas de sprite, es decir, sus coordenadas dentro del PNG que lo contiene. Asumimos que el sistema es igual que al de SFML, es decir, la coordenada (0, 0) está arriba a la izquierda y los ejes crecen de izquierda y derecha y de arriba abajo.
La siguiente imagen muestra todo el montaje. Aquí también seguimos el sistema de coordenadas de SFML, aunque para el mapa podríamos usar otro cualquiera:
La caja negra representa el tamaño del mapa, 2400x600 píxeles, y la caja naranja representa la pantalla de 800x600. El escorpión está en el mapa, con su caja roja. Partiendo de su posición en coordenadas de mundo podemos calcular sus coordenadas de pantalla. También hay que calcular, como hemos visto, la posición en coordenadas de mundo que tiene su caja para saber si colisiona o no con la del protagonista. Hemos dicho que las cajas se pasan a coordenadas de mundo para su comprobación pero también sería posible pasarlas a coordenadas de pantalla y comprobar su colisión.
La conversión entre las diferentes coordenadas (también llamado mapeo o proyección según el contexto) en este caso serían simples sumas y restas. En el caso de un juego isométrico o 3D por ejemplo habría que hacer cálculos más sofisticados, ya que las coordenadas de mundo son en 3D y las de pantalla en 2D.
Espero que esta entrada haya sido interesante. Como siempre para cualquier duda o pregunta ahí tenéis los comentarios. ¡Hasta la próxima entrega!


No hay comentarios:
Publicar un comentario