domingo, 11 de abril de 2010

Desarrollo de un juego 2D con XNA VI - Un poco de matemáticas

Estimados amigos, nuevamente, bienvenidos a este blog.

En el post anterior ya pasamos por el orgasmo de controlar un objeto de juego(el sprite de nuestro cañón) con nuestro teclado.

Este post será un poco menos excitante pues no trataremos temas gráficos sino que veremos temas matemáticos, los que serán esencialmente usados en el método Update() o en los métodos que nosotros creemos que sean invocados por Update().

Recordemos que el loop principal del juego consiste en el ir y venir entre el método Update() y el método Draw(), donde Update() ha de ser usado para los cálculos de posiciones, detección de colisiones y cambios de cualquier dato o inteligencia relacionado con nuestros objetos de juego, mientras que el método Draw() debe remitirse solo al volcado de los objetos a la pantalla en función de lo calculado en el método Update().

¿Qué es un vector?

Ya en  los post anteriores hemos usado la clase Vector2, la documentación en detalle de esta clase la podemos encontrar en MSDN en este link .

Algunas cosas importantes a saber en lo inmediato las trataremos aquí.

Un Vector2, es análogo a un vector, un punto dentro de un plano cartesiano, es un par ordenado de números reales. En efecto, Vector2 expone 2 propiedades: X e Y, con X e Y de tipo float. Esta estructura es ideal para el manejo de coordenadas en un plano de 2 dimensiones(2D) como es el caso del juego que estamos desarrollando. Es por lo anterior que para definir, por ejemplo, la posición de un objeto en la pantalla hemos definido propiedades como Vector2 position donde position es una variable tipo Vector2, por lo que podemos definir position.X y position.Y para indicar un punto en la pantalla.

Pero, ¿Por qué usar Vector2 en lugar de colocar X e Y directamente como variables que indiquen la posición?,esto es porque la clase Vector2, además de proveer estos valores X e Y, provee una serie de métodos optimizados para realizar cálculos de posición y otras tareas propias de un vector.


El método Distance:

public static float Distance (
         Vector2 value1,
         Vector2 value2
)

Nos entrega la distancia entre 2 vectores, si usáramos X e Y directo sobre el objeto de juego, en lugar de usar un Vector2, tendríamos que implemtnar la ecuación de la recta para calcular la distancia entre el X e Y de un objeto y el X e Y de otro. De forma un poco mas práctica, si tuviésemos los vectores de posición de una bala y de un enemigo, podríamos saber rapidamente si la bala ha alcanzado al enemigo con el método Distance.

Ejemplo:


   1:  Vector2 PosicionDeBala; //Vector que indica la posición de la bala
   2:  Vector2 PosicionEnemigo;//Vector que indica la posición de el enemigo
   3:             
   4:  //bla bla bla, ejecución del juego
   5:   
   6:  if(Vector2.Distance(PosicionDeBala,PosicionEnemigo )==0)
   7:  {
   8:           //Hemos alcanzado al enemigo
   9:  }

Vector2.Zero()

Nos devuelve un Vector2 con los valores X e Y iguales a cero.

Vector2.One()

Nos devuelve un Vector2 con los valores X e Y iguales a uno.

Vector2.Lerp(Vector1 valor1, Vector2 valor2, Float monto), con monto entre 0 y 1

Este método(Lerp) nos devolverá un nuevo Vector2 que estará mas cercano a valor1, mientras mas cercano esté monto a 0 y, viceversa, nos devolverá un Vector2 mas cercano a valor2 cuando monto tienda a 1.
¿Para qué diablos nos puede servir esto tan raro? Imaginemos que en lugar de una bala estamos disparando un misil teledirigido. podríamos, en base a la distancia existente entre el misil y algún  enemigo podríamos determinar el curso a seguir por el misil, suponiendo que si no estña cercano a un enemigo el misil deba seguir una linea recta y si está cerca de un enemigo deba avanzar hacia él.

Eso como algo muy superficial respecto a Vector2, les invito a leer sobre los demás métodos en el link de MSDN que les indiqué.

Tenemos además, otro set de métodos especiales para realizar nuestros cálculos y son ptovistos por la clase MathHelper.

MathHelper

MathHelper nos provee un conjunto de métodos triginométricos, que nos serán extremadamente útiles en el cálculo de posiciones, balística, ondas, etcétera.

Hemos de considerar que MathHelper va a operar los ángulos en base a radianes. ¿Qué diablos es eso? Bueno, cuando en la escuela nos enseñaron ángulos, normalmente se nos enseñó a medir ángulos en grados. Ahí se nos explica como, por ejemplo, un ángulo recto está compuesto por 2 rayos que componen un ángulo de 90 grados(90º), mientras que una recta son 2 rayos con un ángulo de 180 grados y una circunferencia en su totalidad conforma 360 grados. Radianes tiene la misma finalidad, medir ángulos, pero usa un sistema de numeración distinto. Podemos hacer una analogía a lo que pasa cuando medimos distancias en centimetros o en pulgadas.

Aquí dejo una imagen que encontré en wikipedia que muestra la conversión entre radianes y ángulos, además nos indica el comportamiento de las funciones trigonométriicas esenciales(seno, coseno, tangente, etcétera) .


Acá, un diagrama mas sencillo de como se divide una circunferencia en radianes.


Para ahondar mas en el tema pueden leer esta entrada de wikipedia que está bastante clara.

La documentación de MathHelper en MSDN la podemos encontrar aquí.

Y en ella podemos ver los siguientes miembros.


NombreDescripción
Public FieldERepresenta la constante matemática e
Public FieldLog10ERepresenta el logarítmo base 10 de e.
Public FieldLog2ERepresenta el logarísmo base 2 de e
Public FieldPiRepresenta el valor de Pi.
Public FieldPiOver2Representa el valor de Pi medio (Pi dividido por 2).
Public FieldPiOver4Representa el valor de Pi cuarto (Pi dividido por 4).
Public FieldTwoPiRepresenta el valor de 2 Pi (2 veces Pi).

Y sus métodos. Algunos métodos que nos pueden resultar útiles son los siguientes:

MathHelper.Clamp



   1:  public static float Clamp (
   2:           float value,
   3:           float min,
   4:           float max
   5:  )

Clamp nos entregará:

si value<=min = min
si value>=max = max
sino =value

Esto nos servirá por ejemplo, cuando queremos acotar los rangos de la pantalla, por ejemplo, que cuando se llegue al tope superior o inferior de la pantalla nuestro Vector2.Y no exceda de este rango.

MathHelper.Lerp

MathHelper tambien provee una interpolación lineal, pero en este caso, sobre valores float en lugar de sobre vectores.

La fórmula que Lerp utiliza internamente es esta

value1 + (value2 - value1) * amount 


Aquí algunos ejemplos de como funcionaría en base a algunos valores:


value1 value2 amount value1 + (value2 - value1) * amount 
2 6 0 2
2 6 0,2 2,8
2 6 0,3 3,2
2 6 0,4 3,6
2 6 0,5 4
2 6 0,7 4,8
2 6 1 6

Como ven, dando los valores 2 y 6, en función de como varíe amount va a cambiar el resultado.

Esto nos podría servir, por ejemplo, para calcular el daño provocado. Si 2 y 6 fuesen el mínimo y máximo daño hecho por un arma, podríamos definir el amount en base a, por ejemplo, la distancia entre el arma y el objetivo(una escopeta podría hacer mas daño a quemarropa que a 20 metros) o en base a la armadura utilizada por el objetivo. A modo de analogía, esto nos da esa aleatoriedad que apunta al realismo que se puede ver en las tiradas de dados de un juego RPG.

Bueno, eso por hoy. espero no haber dado tanta lata con esto de la matemática, puede ser un poco agotadora pero es lo que nos permitirá dar realismo a nuestros juegos.

Consideremos que los videojuegos, como cualquier otro sistema informático, busca representar una realidad y en esto, la física y las matemáticas son herramientas esenciales. Imaginen un Pro Evolution Soccer donde no se contemple el efecto del pasto mojado o del viento sobre el índice de velocidad de los jugadores o del curso tomado por un balón o un Grand Theft Auto donde el peso de un vehículo no sea contemplado al lanzarse cerro abajo, o un shooter donde los misiles salgan sismpre disparados en linea recta en lugar de contemplar la el ángulo de lanzamiento, la distancia  o el alcance del arma en cuestión no tendrían ni la mitad del atractivo que tienen.

Recuerdo que cuando estaba en enseñanza media(16 años apróx.) y ya estaba aburrido de programar juegos 2D con Ascii Art y quise empezar a programar juegos 3D con la librería TurboGraph de Turbo Pascal no tenía,o al menos desconocía, un conjunto de métodos matemáticos como los provistos por MathHelper o estas clases de vectores. Entonces me vi en la necesidad de ir creando yo mismo librerías que, por ejemplo, miplementarán formulas de recta, cálculo de distancia entre pares ordenados y la verdad fue una tarea árdua y que terminó siendo insuficiente pues estaba limitado a los escasos conocimientos matemáticos que un chico "normal" de 16 años puede tener. Rotar una recta me resultaba trazada con pascal usando estas funciones creadas por mi resultaba complejísimo, mientras en el post anterior pudimos rotar un objeto completo respecto de un eje de una forma simple, casi trivial, gracias a esas herramientas.

Como siempre, gracias por la paciencia y el apoyo, un abrazo y jueguen harto.

Manuel Gatica Holtmann

2 comentarios:

  1. codigo fuente para descargar??

    ResponderEliminar
  2. muchas gracias, estoy aprendiendo y de este post me han resultado muy útiles las funciones Vector2.Lerp y MathHelper.Clamp, yo había implementado mis historias raras para hacer esto, si lo llego a saber...

    ResponderEliminar

-__-