jueves, 29 de abril de 2010

canal youtube

Colegas, además de en el blog, los videos capturados los estoy subiendo en este canal de youtube

http://www.youtube.com/manuelgaticaholtmann

Saludos

miércoles, 28 de abril de 2010

Desarrollo de un juego 2D con XNA VIII - Disparando balas


Estimados amigos, nuevamente, bienvenidos a este, su blog, XNAExplorer.

En el último post, quedamos con un cañón que rota entre 0 y 90 grados sobre un fondo espacial. Nada muy excitante aún.

Bien, en el capítulo de hoy vamos(por fín) a disparar.

Lo primero que debemos considerar es que nuestra clase GameObject queda corta para describir los comportamientos de una bala pues no contiene las propiedades que un objeto que se mueve por la pantalla posee. Hasta ahora no hemos lanzado nuestro cañón por los aires asi que no había sido necesario. A diferencia del cañón, una bala tendrá una trayectoria, bien podríamos ir representando cada punto de una trayectoria actualizando el valor de position de nunestro objeto de juego, pero aún es insuficiente. Por otro lado, la bala disparada, en algún momento "muere" ya sea al abandonar la pantalla o al colisionar con algún objeto la bala debe dejar de estar activa y por tanto dejar de ser dibujada.

Para cubrir estas necesidades, modificaremos nuestra clase GameObject agregando las propiedades: Velocity y Alive. donde Velocity nos permitirá ir calculando la trayectoria sumándola a una posición inicial de lanzamiento y Alive nos permitirá saber si el objeto está activo o no para dibujarlo.

Entonces, la clase GameObject quedará de este modo:



   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using Microsoft.Xna.Framework;
   5:  using Microsoft.Xna.Framework.Audio;
   6:  using Microsoft.Xna.Framework.Content;
   7:  using Microsoft.Xna.Framework.GamerServices;
   8:  using Microsoft.Xna.Framework.Graphics;
   9:  using Microsoft.Xna.Framework.Input;
  10:  using Microsoft.Xna.Framework.Media;
  11:  using Microsoft.Xna.Framework.Net;
  12:  using Microsoft.Xna.Framework.Storage;
  13:   
  14:  namespace JuegoXNADemo1
  15:  {
  16:      class GameObject
  17:      {
  18:          public Texture2D Sprite;
  19:          public Vector2 Position;
  20:          public float Rotation;
  21:          public Vector2 Center;
  22:          public Vector2 Velocity;
  23:          public bool Alive;
  24:   
  25:          public GameObject(Texture2D LoadedTexture)
  26:          {
  27:              Rotation = 0.0f;
  28:              Position = Vector2.Zero;
  29:              Sprite = LoadedTexture;
  30:              Center = new Vector2(Sprite.Width / 2, Sprite.Height / 2);
  31:              Velocity = Vector2.Zero;
  32:              Alive = false;
  33:          }
  34:   
  35:      }
  36:  }

Ahora, en nuestra clase Game1 agregaremos una constante que limite la cantidad de balas que pueden estar simultáneamente en pantalla, para el caso del ejemplo, fijaremos este valor en 3. Si quieren que el cañçon pueda disparar mas o menos balas por vez, tan solo cambien el valor de la constante y voilá.

Además, agregaremos un array de GameObject que represente nuestro conjunto de balas en juego.

Entonces, la sección de variables globales nos quedará así:



   1:  public class Game1 : Microsoft.Xna.Framework.Game
   2:      {
   3:          GraphicsDeviceManager graphics;
   4:          SpriteBatch spriteBatch;
   5:          Texture2D backgroundTexture;
   6:          Rectangle viewPortRect;
   7:          GameObject Cannon;
   8:          const int MaxCannonBalls = 3;
   9:          GameObject[] CannonBalls;
  10:   
  11:   
  12:  //Resto del programa

Como podrán suponer en base a lo visto en los post anteriores, la mecánica general es:

Actualizar datos en el método Update() y luego dibujar en base a estos datos en el método Draw()

Así es y seguiremos haciéndolo aunque con una salvedad.

Verán, en la medida que uno comienza a desarrollar y el código comienza a crecer y crecer el código se va haciendo menos mantenible(Esto es: se hace mas dificil encontrar errores o secciones de interés y mas dificil de modificar sin provocar errores) para evitar esto se acostumbra ir encerrando aquellos fragmentos de código relacionados con una tarea específica dentro de métodos específicos. Por ejemplo, si en el método update y en el método draw colocamos el código completo necesario para hacer lo propio con cada uno de los distintos objetos manejados(por ejemplo cañón, balas, naves, textos, otros enemigos, etc) Estos metodos crecerán tanto que luego se nos hará dificil de leer.

Si se hace dificil leer un parrafo como el anterior, que está en español y lenguaje natural, imaginen lo que pasa con código de programación.

Para concluir la idea anterior, diré que agregaremos 2 métodos que se ocuparan de:
1 - Actualizar los datos de posición y status de las balas(UpdateCannonBall())
2 - De inicializar los datos de las balas cuando se presione la barra espaciadora para dispararlas(FireCannonBall())

Los métodos son estos:


   1:  public void FireCannonBall()
   2:          { 
   3:              foreach (GameObject cannonBall in CannonBalls )
   4:              {
   5:                  if (!cannonBall.Alive)
   6:                  {
   7:                      //activamos la bala
   8:                      cannonBall.Alive = true;
   9:                      //Establecemos posición inicial segun la posición del cañón
  10:                      cannonBall.Position = Cannon.Position - cannonBall.Center;
  11:                      //Determinamos la velocidad de la bala segun la posición del cañón
  12:                      cannonBall.Velocity = new Vector2((float)Math.Cos(Cannon.Rotation ),
  13:                          (float)Math.Sin(Cannon.Rotation )) * 5;
  14:                      return;
  15:                  }
  16:              }
  17:          }
  18:          public void UpdateCannonBall()
  19:          {
  20:              //Revisaremos cada cannoBall existente dentro del array de GameObject llamado CannonBalls
  21:              foreach (GameObject cannonBall in CannonBalls)
  22:              {
  23:                  if (cannonBall.Alive)//Si la bala está activa, modificaremos su posición
  24:                  {
  25:                      cannonBall.Position += cannonBall.Velocity;
  26:                  }
  27:                  if (!viewPortRect.Contains(new Point((int)cannonBall.Position.X,(int)cannonBall.Position.Y)))
  28:                  {
  29:                     //Si la bala sale de la pantalla, la desactivaremos
  30:                      cannonBall.Alive = false;
  31:                  }
  32:              }
  33:          }

La gracia de definir estos métodos es que luego podemos invocarlos por su nombre y así ejecutar el código que contienen sin contaminar al método que los invoque con código que pudiera hacer compleja su lectura.

En este caso, ambos métodos serán invocados por el método Update(), cuyo código dejo aquí:



   1:  protected override void Update(GameTime gameTime)
   2:          {
   3:              // Allows the game to exit
   4:              if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
   5:                  this.Exit();
   6:   
   7:              KeyboardState keyboardState = Keyboard.GetState();
   8:              if(keyboardState.IsKeyDown(Keys.Left ))
   9:              {
  10:                  Cannon.Rotation -= 0.1f;
  11:              }
  12:              if (keyboardState.IsKeyDown(Keys.Right ))
  13:              {
  14:                  Cannon.Rotation += 0.1f;
  15:              }
  16:              if (keyboardState.IsKeyDown(Keys.Space) && !previousKeyboardState.IsKeyDown(Keys.Space ))
  17:              {
  18:                  //si la barra espaciadora se ha presionado y antes no estaba presionada
  19:                  //Disparamos una bala
  20:                  FireCannonBall();
  21:              }
  22:              // TODO: Add your update logic here
  23:              Cannon.Rotation = MathHelper.Clamp(Cannon.Rotation, -MathHelper.PiOver2, 0);
  24:              //Actualizaremos los estados y posiciones de las balas invocando el método UpdateCannonBall
  25:              UpdateCannonBall();
  26:              //Almacenamos el estado actual del teclado
  27:              previousKeyboardState = keyboardState;
  28:              base.Update(gameTime);
  29:          }

Por si no lo notaron, ha aparecido una nueva variable( previousKeyboardState) que es una variable global de tipo KeyboardState, lo que hace es, en cada ciclo, almacenar el último estado del teclado. La usaremos para controlar que las balas se disparen solo cuando se presiona la barra espaciadora y no cuando se deja presionada, sin este control, dada la velocidad a la que se ejecutan estos métodos, las tres balas saldrían disparadas juntas y nos daría la impresión de ser una única bala alargada.

Para que no les de error, han de agregarla tambien a la sección de variables globales, con lo que el encabezado de la clase Game1 queda así:



   1:      public class Game1 : Microsoft.Xna.Framework.Game
   2:      {
   3:          GraphicsDeviceManager graphics;
   4:          SpriteBatch spriteBatch;
   5:          Texture2D backgroundTexture;
   6:          Rectangle viewPortRect;
   7:          GameObject Cannon;
   8:          const int MaxCannonBalls = 3;
   9:          GameObject[] CannonBalls;
  10:          KeyboardState previousKeyboardState = Keyboard.GetState();
  11:  //Resto del programa

Ahora, solo nos queda modificar el método draw para que revise el status de las balas y si están activas, las dibuje en pantalla.

Entonces, el nuevo método Draw es este:



   1:  protected override void Draw(GameTime gameTime)
   2:          {
   3:              GraphicsDevice.Clear(Color.CornflowerBlue);
   4:   
   5:              spriteBatch.Begin();
   6:              spriteBatch.Draw(backgroundTexture, viewPortRect, Color.White);
   7:              spriteBatch.Draw(Cannon.Sprite, Cannon.Position, null, Color.White, Cannon.Rotation, Cannon.Center, 1.0f, SpriteEffects.None, 0);
   8:              //Revisamos cada cannonBall existente en el array CannonBalls
   9:              foreach (GameObject cannonball in CannonBalls)
  10:              {
  11:                  if (cannonball.Alive)//Si la bala está activa
  12:                  {
  13:                      //La dibujamos
  14:                      spriteBatch.Draw(cannonball.Sprite, cannonball.Position, null, Color.White, cannonball.Rotation, cannonball.Center, 1.0f, SpriteEffects.None, 0);
  15:                  }
  16:              }
  17:   
  18:              spriteBatch.End();
  19:   
  20:              // TODO: Add your drawing code here
  21:              base.Draw(gameTime);
  22:          }

Bueno, ahora, si han modificado sus códigos del modo correcto, nada mas presionen F5, con las teclas de dirección muevan su cañón y con la barra espaciadora disparen sus balas. ¿A qué ahora si da mas gustito?.. Como diría don Raúl Guerrero:"Tasstyy! "("teeeiiistii!").

Aviso: Por una molesta tos que me impide hablar 5 minutos continuos sin que me de un ataque horripilante de abuelito montepiado, solo incluiré código por ahora, los videos se los debo.

Como siempre, gracias por su tiempo e interés, un abrazo y recuerden "El volar es un arte o, mejor dicho, un don. El don consiste en aprender a tirarse al suelo y fallar."

Bonus track

JA, aqui un extracto de uno de mis libros favoritos de Douglas Adams, "La guía del viajero intergaláctico"

Instrucciones de como Volar

El volar es un arte o, mejor dicho, un don.
El don consiste en aprender a tirarse al suelo y fallar.
Elija un día que haga bueno -sugiere- e inténtelo.
La primera parte es fácil.
Lo único que se necesita es simplemente la habilidad de tirarse hacia adelante con todo el peso del cuerpo, y buena voluntad para que a uno no le importe que duela.
Es decir, dolerá si no se logra evitar el suelo.
La mayoría de la gente no consigue evitar el suelo, y si de verdad lo intenta como es debido, lo más probable es que no logra evitarlo de ninguna manera.
Está claro que la segunda parte, la de evitar el suelo, es la que presenta dificultades.


....

jueves, 22 de abril de 2010

Sobre el mercado de videojuegos en el mundo (Ahora un poco de estadística)

Estimados, nuevamente, bienvenidos a XNAExplorer. Esta vez plantearé un tema distinto a las aristas técnicas que siempre estamos tratando y se trata de estadísticas que no son sino una imagen global del mercado al que estamos apuntando con esto que estamos creando. Porque nos guste o no, ese programita que nos quita el sueño y que mimamos como un hijo poniéndole un detallito aquí y otro detallito allá es a la vez un producto que puede salr al mercado y aparte de ego, retribuir económicamente nuestro sudor, lágrimas y tendinitis.

No solo de punk vive el hombre y por mucho que nos gusten los videojuegos y el desarrollo de los mismos no hay que dejar de considerar que esta es un área de negocio.

Al menos eso es lo que tuve que considerar ahora que estoy trabajando como proyecto de título en el desarrollo de un videojuego.

Mi profesor me dice: "Ya, es bonita tu idea, pero dime en que sustentas el que pueda ser un proyecto económicamente viable."

Entonces tuve que preguntarme cosas como:
  1. ¿Manuel, Qué sabes de la demanda por videojuegos mas allá de que te encanta Metal Gear (Kojima es mi copiloto), que piensas que Silent Hill Shattered Memories es una delicia, que te encanta salir a pasear en bicicleta en GTA San Andreas o que gastaste un dineral en tu infancia y adolescencia en los locales de videojuegos del barrio?"
  2. ¿Manuel, en base a qué puedes decir que puedes vender 1, 10, 20 o un millón de copias?
  3. ¿Cómo se segmenta el mercado?
  4. ¿Quien compra mas?¿Donde compra mas?
  5. etc
Luego de darme cuenta que mas allá de mis febriles delirios quijotescos no tenía una idea concreta tuve que ponerme a buscar. ¿Sabían ustedes que generalmente esta información se encuentra en estudios de mercado o benchmarks que son pagados?  Yo tenía alguna idea pero cuando empecé a googlear y me di cuenta que para casi cualquier cosa que querñia saber tenía que abrir la billetera la verdad me empecé a desalentar un poco.

Afortunadamente di con el sitio de la Federación Europea de Software Interactivo(Interactive Software Federation of Europe aka ISFE) cuyo sitio es http://www.isfe-eu.org/  donde encontré una serie de estudios, algunos en español otros en inglés sobre la situación de Europa en general y algunos específicos sobre España.

De acá pude extraer interesante información como la siguiente:



    * Los videojuegos son una de las actividades de ocio más comunes entre los europeos. El 40% de los jugadores les dedica entre 6 y 14 horas semanales, al mismo nivel que el tiempo que pasa viendo la televisión, navegando por Internet o visitando a familiares y amigos.
    * La percepción general es que los videojuegos ofrecen ventajas únicas en relación con otras actividades de entretenimiento habituales como la televisión o el cine. El 72% juega por placer, el 57% como una forma de estimular la imaginación y el 45% afirma que los juegos le hacen pensar. El aspecto social de jugar online con otras personas es un importante factor secundario para los jugadores.
    * En casa, los europeos juegan de forma responsable. El 81% de los padres que juegan lo hacen con sus hijos y más de la mitad de los padres jugadores controlan siempre los juegos que compran y a los que juegan sus hijos.
    * Entre los no jugadores, no parece existir ningún sentimiento negativo hacia los videojuegos. Casi la mitad de los que no juegan (48%) citan como motivo la falta de tiempo.
    * En España, el 28% de la población con edades comprendidas entre los 16 y los 49 años se describe a sí misma como jugadores activos, el 16% de de entre 30 y 49 años.
    * Las mujeres representan una proporción destacable del mercado de los videojuegos: en España el 18% de las mujeres de entre 16 y 49 años se consideran jugadoras activas.
    * Más del 60% de los jugadores afirma jugar online.
    * El 72% de los jugadores utiliza su consola como dispositivo multimedia para otras actividades.

También tienen una sección de Hechos sobre el mercado, donde indican lo siguiente:

1.    Uno de cada 3 europeos usa juegos de computadora o consola regularmente.
2.    Los jugadores, regularmente tienen entre 20 y 30 años de edad, jugando 6 horas a la semana.
3.    Aún con sus treinta años, la industria del video juego sigue convocando y asombrando.
4.    Un asombroso crecimiento de 12%  se determinó el 2002. Los rangos de crecimiento, se espera, crecerán sobre esto en los próximos años  con Europa liderando la demanda.
5.    La industria del videojuego generó 18.1 billones de euros el 2001. 6,7 de los cuales fueron generados tan solo en Europa.
6.    Los videojuegos generan mas dividendos que el cine o el dvd.
7.    Los videojuegos estimulan nuevas tecnologías de comunicación. Pueden ser jugados en múltiples plataformas, incluso celulares y crean demanda por mayores anchos de banda.

Tienen además una zona de descarga de papers que están accesibles sin costo donde se puede ahondar mas aún. Está buenísimo, denle un vistazo en estos links

Industria y jugadores
Estadístico$$
Violencia y uso excesivo
 Educación

Bueno, como pueden ver, hay montón por leer y considerar. Que esto no es solo ponerse a dibujar y programar.

Espero estos datos les sean útiles, como siempre, un abrazo y hasta pronto.

Manuel Gatica Holtmann

Desarrollo de un juego 2D con XNA VII - MathHelper.Clamp aplicado en el control de rotación de un objeto

Estimados amigos, nuevamente bienvenidos a XNAExplorer.

Lamento haber bajado el ritmo esta última semana pero por motivos laborales con jornadas de 15 horas diarias ante pronta entrega de un sitio en el que estoy trabajando se me ha hecho algo complejo, no obstante, como no quiero perder la periodicidad, dejo un pequeño entremes mientras tentenpie algo de material mas extenso y contundente.

En el post anterior los estuve lateando quizas con mucha teoria y matemáticas,que mathHelper, que radianes, que vectores y blah blah blah.

En esta ocasión, en un video muy breve mostraré como con MathHelper.Clamp es posible, de una manera muy simple(apenas una línea de código), acotar el ángulo de rotación de un objeto, en este caso, de nuestro cañón. La idea es que el cañón siempre dispare hacia el cielo, es decir con un ángulo entre 0 y 90 grados( entre cero y pi medio radianes). Para esto, justo despues de actualizar el ángulo de rotación en base a las teclas presionadas, incluiremos la siguiente línea en el código:

Cannon.Rotation = MathHelper.Clamp(Cannon.Rotation, -MathHelper.PiOver2, 0);

Como ven, muy simple, ahora como se ve esto, pueden apreciarlo en el video que dejo a continuación:




Eso por hoy.

No quiero despedirme sin antes agradecer el apoyo, los comentarios y la buena onda de amigos y colegas que se han interesado por este tema. En especial a Jesús Bosch, cuyo blog he estado leyendo y está buenísimo, sobre todo para quien quiera explotar XNA en 3d, denle un vistazo a este link http://geeks.ms/blogs/jbosch/default.aspx, tambien a Maic, a Ariel Martinez, Jonathan Monroy y  a Sergio Pinto.

Otro sitio con el que di esta semana, gracias al grupo de facebook XNA Chile, fue a GameDev Chile, está interesante, denle una vuelta tambien.

Saludos y hasta pronto.

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

jueves, 8 de abril de 2010

Desarrollo de un juego 2D con XNA V - Insertar y controlar un sprite



Estimados amigos

Nuevamente, bienvenidos a este blog.

En el post de hoy seguiremos extendiendo el caso presentado anteriormente. La última vez quedamos con un juego que solo muestra un fondo espacial. Hoy, agregaremos un sprite, un objeto de juego que puede ser controlado por teclado. El sprite en cuestión será un cañon, el que al presionar las teclas izquierda o derecha rotara sobre su centro.

Tal como indiqué en el post anterior, los sprites, como los fondos son texturas(Texture2D) pero los sprites, al no ser estáticos sino que tienen posiciones, rotaciones, transformaciones que a lo largo del juego varían han de implementarse de una forma un tanoto distinta.

Para empezar, vamos a crear una clase que contendrá los datos pertinentes a posición, rotación, su centro y a la imagen en si que representará a nuestro objeto de juego. Implementada esta clase y su constructor, veremos las modificaciones necesarias para desplegarlo en pantalla y para controlar su movimiento con nuestro teclado.

Al igual que en el post anterior, dejo acá la lista de videos de lo hecho para que sea mas fácil seguirlo y a continuación los codigos fuentes modificados.

LISTA DE VIDEOS


Acá el código de nuestra clase GameObject



   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using Microsoft.Xna.Framework;
   5:  using Microsoft.Xna.Framework.Audio;
   6:  using Microsoft.Xna.Framework.Content;
   7:  using Microsoft.Xna.Framework.GamerServices;
   8:  using Microsoft.Xna.Framework.Graphics;
   9:  using Microsoft.Xna.Framework.Input;
  10:  using Microsoft.Xna.Framework.Media;
  11:  using Microsoft.Xna.Framework.Net;
  12:  using Microsoft.Xna.Framework.Storage;
  13:   
  14:  namespace JuegoXNADemo1
  15:  {
  16:      class GameObject
  17:      {
  18:          public Texture2D Sprite;
  19:          public Vector2 Position;
  20:          public float Rotation;
  21:          public Vector2 Center;
  22:   
  23:          public GameObject(Texture2D LoadedTexture)
  24:          {
  25:              Rotation = 0.0f;
  26:              Position = Vector2.Zero;
  27:              Sprite = LoadedTexture;
  28:              Center = new Vector2(Sprite.Width / 2, Sprite.Height / 2);
  29:          }
  30:   
  31:      }
  32:  }

Y ahora el código de nuestra clase Game1



   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using Microsoft.Xna.Framework;
   5:  using Microsoft.Xna.Framework.Audio;
   6:  using Microsoft.Xna.Framework.Content;
   7:  using Microsoft.Xna.Framework.GamerServices;
   8:  using Microsoft.Xna.Framework.Graphics;
   9:  using Microsoft.Xna.Framework.Input;
  10:  using Microsoft.Xna.Framework.Media;
  11:  using Microsoft.Xna.Framework.Net;
  12:  using Microsoft.Xna.Framework.Storage;
  13:   
  14:  namespace JuegoXNADemo1
  15:  {
  16:   
  17:      public class Game1 : Microsoft.Xna.Framework.Game
  18:      {
  19:          GraphicsDeviceManager graphics;
  20:          SpriteBatch spriteBatch;
  21:          Texture2D backgroundTexture;
  22:          Rectangle viewPortRect;
  23:          GameObject Cannon;
  24:   
  25:          public Game1()
  26:          {
  27:              graphics = new GraphicsDeviceManager(this);
  28:              Content.RootDirectory = "Content";
  29:          }
  30:   
  31:          /// <summary>
  32:          /// Allows the game to perform any initialization it needs to before starting to run.
  33:          /// This is where it can query for any required services and load any non-graphic
  34:          /// related content.  Calling base.Initialize will enumerate through any components
  35:          /// and initialize them as well.
  36:          /// </summary>
  37:          protected override void Initialize()
  38:          {
  39:              // TODO: Add your initialization logic here
  40:   
  41:              base.Initialize();
  42:          }
  43:   
  44:          /// <summary>
  45:          /// LoadContent will be called once per game and is the place to load
  46:          /// all of your content.
  47:          /// </summary>
  48:          protected override void LoadContent()
  49:          {
  50:              // Create a new SpriteBatch, which can be used to draw textures.
  51:              spriteBatch = new SpriteBatch(GraphicsDevice);
  52:              backgroundTexture = Content.Load<Texture2D>("background");
  53:              Cannon = new GameObject(Content.Load<Texture2D>("cannon"));
  54:              Cannon.Position = new Vector2(120,graphics.GraphicsDevice.Viewport.Height-80);
  55:              viewPortRect = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height);
  56:              // TODO: use this.Content to load your game content here
  57:          }
  58:   
  59:          /// <summary>
  60:          /// UnloadContent will be called once per game and is the place to unload
  61:          /// all content.
  62:          /// </summary>
  63:          protected override void UnloadContent()
  64:          {
  65:              // TODO: Unload any non ContentManager content here
  66:          }
  67:   
  68:          /// <summary>
  69:          /// Allows the game to run logic such as updating the world,
  70:          /// checking for collisions, gathering input, and playing audio.
  71:          /// </summary>
  72:          /// <param name="gameTime">Provides a snapshot of timing values.</param>
  73:          protected override void Update(GameTime gameTime)
  74:          {
  75:              // Allows the game to exit
  76:              if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
  77:                  this.Exit();
  78:   
  79:              KeyboardState keyboardState = Keyboard.GetState();
  80:              if(keyboardState.IsKeyDown(Keys.Left ))
  81:              {
  82:                  Cannon.Rotation -= 0.1f;
  83:              }
  84:              if (keyboardState.IsKeyDown(Keys.Right ))
  85:              {
  86:                  Cannon.Rotation += 0.1f;
  87:              }
  88:              // TODO: Add your update logic here
  89:   
  90:              base.Update(gameTime);
  91:          }
  92:   
  93:          /// <summary>
  94:          /// This is called when the game should draw itself.
  95:          /// </summary>
  96:          /// <param name="gameTime">Provides a snapshot of timing values.</param>
  97:          protected override void Draw(GameTime gameTime)
  98:          {
  99:              GraphicsDevice.Clear(Color.CornflowerBlue);
 100:   
 101:              spriteBatch.Begin();
 102:              spriteBatch.Draw(backgroundTexture,viewPortRect,Color.White );
 103:              spriteBatch.Draw(Cannon.Sprite,Cannon.Position, null, Color.White, Cannon.Rotation, Cannon.Center, 1.0f,SpriteEffects.None, 0);
 104:              spriteBatch.End();
 105:   
 106:              // TODO: Add your drawing code here
 107:   
 108:   
 109:              base.Draw(gameTime);
 110:          }
 111:      }
 112:  }

Bueno, como siempre, gracias por su atención, un abrazo y hasta la próxima.

Manuel Gatica Holtmann

lunes, 5 de abril de 2010

Desarrollo de un cuego 2D con XNA IV - Colocando un fondo

Estimados

Ya vimos en el post la estructura de un proyecto XNA en blanco, revisamos sus métodos y tuvimos la vertiginosa experiencia(jaja) de verlo funcionando.

Pues bien, en esta ocasión haremos algo un poco menos abúlico y se trata de colocar una imagen de fondo.

Me he valido para esto de unas imágenes y código que están en los proyectos de de la guía para principiantes del sitio de XnaCreators. Este sitio, que por cierto está buenísimo, introduce al desarrollo de juegos tanto en 2D como en 3D por medio de una serie de videos que están muy bien explicados, aunque en inglés lo que para algunos puede ser un poco incómodo.

Siguiendo elejemplo de XnaCreators he decidido presentar lo hecho en los videos que dejo a continuación.

LISTA DE VIDEOS



Les contaré que para grabar estos videos he usado una herramienta que me ha recomentado mi buen amigo Maic, se trata de JING, de los mismos creadores de Camtasia. Excelente y gratuita.

El código que contiene la clase Game1.cs de nuestro anterior proyecto en blanco, luego de este ejemplo es el siguiente:



   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using Microsoft.Xna.Framework;
   5:  using Microsoft.Xna.Framework.Audio;
   6:  using Microsoft.Xna.Framework.Content;
   7:  using Microsoft.Xna.Framework.GamerServices;
   8:  using Microsoft.Xna.Framework.Graphics;
   9:  using Microsoft.Xna.Framework.Input;
  10:  using Microsoft.Xna.Framework.Media;
  11:  using Microsoft.Xna.Framework.Net;
  12:  using Microsoft.Xna.Framework.Storage;
  13:   
  14:  namespace JuegoXNADemo1
  15:  {
  16:   
  17:      public class Game1 : Microsoft.Xna.Framework.Game
  18:      {
  19:          GraphicsDeviceManager graphics;
  20:          SpriteBatch spriteBatch;
  21:          Texture2D backgroundTexture;
  22:          Rectangle viewPortRect;
  23:   
  24:          public Game1()
  25:          {
  26:              graphics = new GraphicsDeviceManager(this);
  27:              Content.RootDirectory = "Content";
  28:          }
  29:   
  30:          /// <summary>
  31:          /// Allows the game to perform any initialization it needs to before starting to run.
  32:          /// This is where it can query for any required services and load any non-graphic
  33:          /// related content.  Calling base.Initialize will enumerate through any components
  34:          /// and initialize them as well.
  35:          /// </summary>
  36:          protected override void Initialize()
  37:          {
  38:              // TODO: Add your initialization logic here
  39:   
  40:              base.Initialize();
  41:          }
  42:   
  43:          /// <summary>
  44:          /// LoadContent will be called once per game and is the place to load
  45:          /// all of your content.
  46:          /// </summary>
  47:          protected override void LoadContent()
  48:          {
  49:              // Create a new SpriteBatch, which can be used to draw textures.
  50:              spriteBatch = new SpriteBatch(GraphicsDevice);
  51:              backgroundTexture = Content.Load<Texture2D>("background");
  52:              viewPortRect = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height);
  53:              // TODO: use this.Content to load your game content here
  54:          }
  55:   
  56:          /// <summary>
  57:          /// UnloadContent will be called once per game and is the place to unload
  58:          /// all content.
  59:          /// </summary>
  60:          protected override void UnloadContent()
  61:          {
  62:              // TODO: Unload any non ContentManager content here
  63:          }
  64:   
  65:          /// <summary>
  66:          /// Allows the game to run logic such as updating the world,
  67:          /// checking for collisions, gathering input, and playing audio.
  68:          /// </summary>
  69:          /// <param name="gameTime">Provides a snapshot of timing values.</param>
  70:          protected override void Update(GameTime gameTime)
  71:          {
  72:              // Allows the game to exit
  73:              if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
  74:                  this.Exit();
  75:              
  76:              // TODO: Add your update logic here
  77:   
  78:              base.Update(gameTime);
  79:          }
  80:   
  81:          /// <summary>
  82:          /// This is called when the game should draw itself.
  83:          /// </summary>
  84:          /// <param name="gameTime">Provides a snapshot of timing values.</param>
  85:          protected override void Draw(GameTime gameTime)
  86:          {
  87:              GraphicsDevice.Clear(Color.CornflowerBlue);
  88:   
  89:              spriteBatch.Begin();
  90:              spriteBatch.Draw(backgroundTexture,viewPortRect,Color.White );
  91:              spriteBatch.End();
  92:   
  93:              // TODO: Add your drawing code here
  94:   
  95:   
  96:              base.Draw(gameTime);
  97:          }
  98:      }
  99:  }

Nuevamente y como siempre, saludos y gracias por darse el tiempo de leer este blog y que(como diría don Raúl Guerrero) nunca les falte "la pasión del desarrollador".

Manuel Gatica Holtmann

-__-