martes, 8 de junio de 2010

Programando videojuegos: Tutorial XNA Game Studio 4.0 parte 2

crab-icon ball c# Un día después, en The Code Crab, continuamos lo que hemos dejado a medias.

image

Tenemos nuestra navecita, que ya se ve en la pantalla (preciosa ella), y nuestro código perfectamente organizado, con una clase Game1 que llama a los respectivos métodos de cada objeto que tiene (es decir de la nave de momento) y con una clase nave que maneja todo lo relativo a la navecita. Es hora de implementar el Update de la clase Nave para poder moverla por la pantallita, pero antes necesitaremos preparar unos cuantos atributos mas:

        private const int anchoImagen = 42;
private const int altoImagen = 44;
private Texture2D imagen;
private Vector2 posicion;
private int altoVentana;
private int anchoVentana;
public Nave(int altoVentana, int anchoVentana)
{
this.altoVentana = altoVentana;
this.anchoVentana = anchoVentana;
posicion = new Vector2(altoVentana - altoImagen * 2, (anchoVentana - anchoImagen) / 2);
CrearRectangulo(anchoImagen, altoImagen * 2);
}


Añadimos 4 enteros: en anchoImagen y altoImagen almacenaremos el tamaño en pixels de la imagen, lo cual simplificara bastante las cosas a la hora de mostrarlo por pantalla (y de paso si cambias de imagen no te tienes que volver loco buscando todos los 42 y 44 que pusiste). En height y width almacenamos el tamaño de la ventana en pixels, el cual ya pasábamos como parámetro, pero esta vez lo almacenamos, pues lo necesitaremos para que la nave no se salga de la pantalla.



Con todo esto ya estamos listos para implementar Update, donde manejaremos la nave en función de las teclas que se pulsen:



        public void Update()
{
UpdatePosition();
}
private void UpdatePosition()
{
if (Keyboard.GetState().IsKeyDown(Keys.Left) && posicion.X > 5)
posicion.X -= 5;
if (Keyboard.GetState().IsKeyDown(Keys.Right) && posicion.X < (anchoVentana - anchoImagen))
posicion.X += 5;
if (Keyboard.GetState().IsKeyDown(Keys.Up) && posicion.Y > 5)
posicion.Y -= 5;
if (Keyboard.GetState().IsKeyDown(Keys.Down) && posicion.Y < (altoVentana - altoImagen))
posicion.Y += 5;
}


Que es todo esto, os preguntareis. Pues bien, es bastante sencillo. Con el método estático Keyboard.GetState() obtenemos el estado actual del teclado, y con el método IsKeyDown(Keys.X) sabemos si una tecla X esta pulsada o no. Una vez sabemos esto es fácil intuir que lo que hacemos es cambiar la posición en función de que tecla se pulse, aumentando X si se pulsa derecha, reduciéndolo si se pulsa izquierda, aumentando Y si se pulsa abajo y reduciéndolo si se pulsa Up. Yo tengo puesto un aumento/disminución de 5, pero si cambiáis el numerito estaréis cambiando la velocidad de la nave.



En cuanto a la otra condición se trata de comprobar que la imagen no se salga de los bordes (Para esto necesitábamos el tamaño de la ventana). Cuando se comprueba que no se salga de los bordes inferior y derecho, hay que restarle al tamaño de la ventana el tamaño de la imagen por un motivo simple: La posición que le indicamos a la imagen es la coordenada de su esquina superior izquierda, por lo que si dejamos que esta coordenada llegue a ser el ancho o el alto de la ventana, la imagen se saldrá de la pantalla.



Tras esto si probamos a ejecutar, ya podemos mover nuestra nave (divertido, ¿No?), pero aquí falta algo. Para que tener varias imágenes distintas para la nave en función de hacia donde se este moviendo si no lo utilizamos. Pues bien, vamos a ello. Una vez mas lo primero que hay que hacer será añadir un nuevo atributo para representar el estado.



¿Como representamos el estado de la nave? Podemos usar prácticamente cualquier cosa, siempre que tengamos claro lo que estamos usando, como un string o incluso un int, pero la opción mas sencilla sera almacenar un objeto Rectangle con la sección de la imagen que dibujaremos, para no tener que complicarnos la vida en el Draw. También tendremos que modificar el constructor para inicializar la variable:



       private Rectangle rectangulo;
private int ancho = 42;
private int alto = 44;
private Texture2D imagen;
private Vector2 posicion;
private int height;
private int width;
public Nave(int height, int width)
{
this.height = height;
this.width = width;
posicion = new Vector2(height-alto*2, (width-ancho)/2);
estado = Estado.Normal;
}


Ya que nuestra imagen es una cuadricula de cuadros 42x44, image tenemos que fijarnos en que imagen queremos dibujar según hacia donde este la nave avanzando. Si enumeramos las diferentes imágenes que tenemos vemos que de 1 a 3 son las diferentes imágenes retrocediendo (Sin propulsión), de 4 a 6 son las imágenes avanzando (Mucha propulsión) y de 7 a 9 son las imágenes normales (Poca propulsión). Entonces ¿Como hacemos que nuestra imagen cambie según en que dirección avance la nave?.



Es fácil, ¿Recordáis los parámetros de SpriteBatch.draw? El primer parámetro es la imagen a dibujar, el siguiente las coordenadas donde se debe dibujar, y el tercero un objeto Rectangle que indique que parte de la imagen se dibujara, cuyo constructor pide como parámetros la coordenada de donde tiene que empezar a coger la imagen (esquina superior izquierda, como siempre) y la altura y la anchura de esta. La altura y la anchura serán siempre las mismas, mientras que la coordenada variara en función de que imagen queremos. Como sabemos la dirección en la que avanza la nave es fácil saber estas coordenadas utilizando múltiplos de la anchura y altura, ya que es una cuadricula (Por ejemplo, si queremos la coordenada de la imagen 3 tiene que ser (anchura*2, 0), mientras que para la imagen 8 sería (anchura, altura*2)). Lo único que nos hace falta es almacenar estas coordenadas en la variable rectangulo, definida anteriormente, y actualizarla en función de que teclas se estén pulsando.



Con esto tenemos una variable estado en la cual almacenamos que sección de la imagen queremos dibujar. Tras esto, lo que tenemos que hacer es añadir mas cosas al método Update, para que en función de hacia que dirección se este moviendo cambie el rectángulo:



        public void Update()
{
UpdatePosition();
UpdateRectangle();
}
private void UpdatePosition()
{
if (Keyboard.GetState().IsKeyDown(Keys.Left) && posicion.X > 5)
posicion.X -= 5;
if (Keyboard.GetState().IsKeyDown(Keys.Right) && posicion.X < (anchoVentana - anchoImagen))
posicion.X += 5;
if (Keyboard.GetState().IsKeyDown(Keys.Up) && posicion.Y > 5)
posicion.Y -= 5;
if (Keyboard.GetState().IsKeyDown(Keys.Down) && posicion.Y < (altoVentana - altoImagen))
posicion.Y += 5;
}
private void UpdateRectangle()
{
//a partir de aquí escojemos la parte de imagen que queremos dibujar y la almacenamos en rectangle,
// en funcion de la combinaion de botones que se esten pulsando.

if (Keyboard.GetState().IsKeyDown(Keys.Left) && Keyboard.GetState().IsKeyDown(Keys.Up))
{
CrearRectangulo(0, altoImagen);
}
else if (Keyboard.GetState().IsKeyDown(Keys.Right) && Keyboard.GetState().IsKeyDown(Keys.Up))
{
CrearRectangulo(anchoImagen * 2, altoImagen);
}
else if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
CrearRectangulo(anchoImagen, altoImagen);
}
else if (Keyboard.GetState().IsKeyDown(Keys.Left) && Keyboard.GetState().IsKeyDown(Keys.Down))
{
CrearRectangulo(0, 0);
}
else if (Keyboard.GetState().IsKeyDown(Keys.Right) && Keyboard.GetState().IsKeyDown(Keys.Down))
{
CrearRectangulo(anchoImagen * 2, 0);
}
else if (Keyboard.GetState().IsKeyDown(Keys.Down))
{
CrearRectangulo(anchoImagen, 0);
}
else if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
CrearRectangulo(0, altoImagen * 2);
}
else if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
CrearRectangulo(anchoImagen * 2, altoImagen * 2);
}
else
{
CrearRectangulo(anchoImagen, altoImagen * 2);
}
}
private void CrearRectangulo(int x, int y)
{
rectangulo = new Rectangle(x, y, anchoImagen, altoImagen);
}


Ya que también necesitamos saber si la nave se esta desplazando en diagonal no podemos usar los if utilizados para modificar la posición, y por esto mismo es muy importante el orden de los if (Pues no podemos comprobar si la nave esta Retrocediendo hasta que no hayamos comprobado que no este retrocediendo hacia la derecha ni hacia la izquierda, pues en ambos casos si comprobamos si esta retrocediendo antes obtendremos un falso positivo).



Tras esto ya tenemos actualizado el estado de la nave, y sabemos que parte de la imagen debemos dibujar. Ahora solo resta dibujar el Sprite, con lo que tendremos que modificar el método Draw de la clase nave, quedándonos así:



	public void Draw(SpriteBatch spbtch)
{
spbtch.Draw(imagen, posicion, rectangulo, Color.White);
}


Tras todo esto, y como ya invocábamos a todos los métodos en la parte anterior del tutorial (Incluso Update que antes no hacía nada, si me habéis hecho caso) ya debería funcionar todo correctamente, y nuestra preciosa nave se podrá mover con libertad por la pantalla sin salirse y con una animación bastante chula.



imageYa va cogiendo forma el juego, ¿No? Con esto termina la segunda parte del tutorial, estad atentos para la tercera parte, la haré en unos días. Podéis descargaros el código fuente de la aplicación hasta ahora en el siguiente enlace: http://www.megaupload.com/?d=LN7C0R0Jimage



Eso es todo por hoy, espero que os este gustando el megatutorial. Hasta otra y ¡¡Sed buenos Cangrejos!!





Etiquetas de Technorati: ,,,,,

12 comentarios:

  1. Que rápido.

    Muy bueno, lo voy a estudiar.

    Muchísimas gracias por el trabajo relacionado con XNA.

    ResponderEliminar
  2. Muy bueno.

    Sigue así campeón.

    ResponderEliminar
  3. He cambiado cosas en el codigo, para dejarlo mas corto y limpio, y he editado todo el tutorial para adaptarlo al nuevo código.

    ResponderEliminar
  4. Yo tengo un par de dudas, al principio se declaró un enum con varios estados, private Estado estado; y estado = Estado.Normal;
    Pero después ya no se utilizó para nada eso, yo pensé que eso cambiaría cuando los distintos rectangulos se crearan en el metodo UpdateRectangle();...
    Y la otra duda, tu mandas llamar esta función con los siguientes parámetros: spbtch.Draw(imagen, posicion, rectangulo, Color.White);
    Pero ¿dónde se declaró "rectangulo"? No debería ser "CrearRectangulo"? y aún así, en el metodo pasado usaste "new Rectangle(1,2,3,4)" (o sea, 4 parametros), y en este mandas llamar a "rectangulo", que pues segun yo sería CrearRectangulo y consta de 2 parametros.

    Bueno espero que me puedas aclarar esas dudas.
    Gracias!

    ResponderEliminar
  5. Bueno ya aclaré las dudas descargando el código fuente. Te faltó explicar algunas cosillas y por eso no entendía.

    ResponderEliminar
  6. Gracias por los comentarios carlos. El problema es que hize un cambio, cambiando el estado por un rectangulo, pero al cambiar el tutorial se me colo en un sitio que aun no lo cambie. También se me colo poner el metodo CrearRectangulo que lo que hace es crear un rectangulo y almacenarlo en la variable rectangulo :) esta tarde lo arreglo, perdona las molestias

    ResponderEliminar
  7. problema solucionado, si encontrais mas cosas ponedlo en los comentarios, intentare estar atento

    ResponderEliminar
  8. Gracias, ahora esta todo perfecto. Espero las demás partes, yo sabía Java y otros lenguajes más pero apenas ando retomando el mando para desempolvar los conocimientos. Tengo planeado un proyecto en XNA y tus tutoriales me han servido para entender más todo, asi que espero que sigas actualizando :D :D

    ResponderEliminar
  9. estoy liado estos dias con la carrera, sino ya habría puesto la tercera parte. No obstante, el jueves termino, así que el viernes o así me pondre a trabajar en ello

    ResponderEliminar
  10. Me funciona un tanto lento.. es esto normal?.. no creo que sea mi equipo porque he corrido bien juegos como prince of persia ww.. y esto es 2D.. que podrá ser.. esta bien, pero a veces como que se traba el avion.. sabes que podría ser??..

    ResponderEliminar
  11. tengo algunas dudas ... podrias volver a subir el codigo fuente?

    ResponderEliminar