sábado, 19 de junio de 2010

Programando videojuegos: Tutorial XNA Game Studio 4.0 parte 3

crab-icon ball c#

Bueno, tras un pequeño intervalo de tiempo volvemos con el tutorial sobre XNA, pues ya va siendo hora que nuestra navecita pueda disparar. Lo primero que necesitamos es un sprite del disparo. En mi caso voy a utilizar este, pero bueno, este es un país libre.

weapons

Para introducirlo en el proyecto tenemos que repetir lo que ya aprendimos en la primera parte del tutorial. Copiamos la imagen a la carpeta del proyecto y la arrastramos a Content en el VS.

Una vez que tenemos la imagen dentro de Content necesitaremos crear una nueva clase que almacene toda la información de cada disparo, como por ejemplo la clase Disparo.

¿Que atributos necesita esta clase? Necesita los siguientes:

	private const int anchoImagen = 6;
private const int altoImagen = 22;
private Texture2D imagen;
private Vector2 _posicion;
public event EventHandler FueraDePantalla;


Como podéis ver, ya que Disparo va a representar a un Sprite que se moverá por la pantalla tiene atributos bastante similares a Nave, excepto por el Rectangle (Los disparos no tendrán animación). El único atributo nuevo es el evento FueraDePantalla, que se disparará cuando el disparo se salga de la pantalla, para avisar a la clase que contenga los disparos (Estarán almacenados dentro de una lista en Nave) de que debe borrar el disparo. ¿Por que? Es sencillo, si a lo largo del juego vamos añadiendo cada vez mas disparos, aunque se salgan de la pantalla seguirán existiendo, por lo que estarán consumiendo recursos inútilmente. Este evento se disparará en el método Update que definiremos mas adelante. En cuanto al resto de atributos, son equivalentes a los de Nave, si tenéis dudas podéis mirar atrás.



Necesitamos un constructor acorde con estos atributos, algo parecido a esto:



	public Disparo(Vector2 posicion, int anchoNave, ContentManager Content)
{
_posicion = posicion;
//movemos un poco la posicion del disparo, para que salga desde el centro de la nave y no desde una esquina
_posicion.X += (anchoNave / 2);
//lo que acabamos de centrar es la esquina superior izquierda del disparo. Así situaremos el centro alineado con el centro de la imagen
//los 3 pixeles extra es por que la imagen del disparo no esta perfectamente centrada.
_posicion.X -= (anchoImagen / 2)+3;
//Ya que los disparos pueden surgir en cualquier momento, y no al principio de la ejecución no tiene sentido tener un método
//LoadContent que cargue las imágenes. En vez de eso las cargaremos en el constructor.
imagen = Content.Load("weapons");
}


En el constructor vemos como se inicializa la posición del disparo en función de la posición de la nave (pasada por parámetro) Además utilizamos el ContentManager pasado para inicializar la imagen. Recordad que no hay que poner terminación. Más tarde habrá que modificar la clase nave para que almacene ContentManager, y así poder pasar el parámetro al crear los disparos.



Además de esto necesitaremos un método Update que haga avanzar el disparo y un método Draw que lo dibuje por pantalla:



	public void Update()
{
_posicion.Y-=5;
if (_posicion.Y <= 0)
FueraDePantalla(this, null);
}
public void Draw(SpriteBatch spbtch)
{
spbtch.Draw(imagen, _posicion, new Rectangle(0,0,anchoImagen, altoImagen), Color.White);
}


Como se puede observar en el método Update disminuimos 5 puntos el eje Y de la posición. Variando esto se podrá modificar la velocidad de los disparos (Recordad que la posición se expresa en pixeles). Además de esto si la posición se hace menor que 0 lanzara el evento antes mencionado, que será manejado por Nave para borrar el disparo.



En cuanto a Draw, nada nuevo.



Tras esto ya tenemos lista la clase Disparo. Deberemos añadir una lista de disparos a los atributos de Nave y inicializarla en el constructor:



        private Rectangle rectangulo;
private const int anchoImagen = 42;
private const int altoImagen = 44;
private Texture2D imagen;
private Vector2 posicion;
private int altoVentana;
private int anchoVentana;
private List<Disparo> disparos;
private int frameCounter = 0;
private ContentManager _content;
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);
disparos = new List<Disparo>();
}
public void LoadContent(ContentManager Content)
{
this._content = Content;
imagen = Content.Load<texture2d>("battleship");
}


Como podemos ver hay varias cosas nuevas. En cuanto a la lista antes mencionada, podemos ver que se inicializa en el constructor. También almacenamos el ContentManager que obtenemos en el método LoadContent por que lo necesitaremos para inicializar los disparos.



En cuanto al int frameCounter, lo utilizaremos para que no se pueda disparar demasiado rápido. Me explico. Si no ponemos ninguna clase de restricción de este estilo al crear los disparos, si mantenemos pulsado el botón se creara un disparo cada vez que se pulse el botón Update. Esto serán una cantidad enorme de disparos. Si tenemos un contador, que cada vez que se llame a Update se le suma 1, y que cada vez que creamos un disparo se ponga a 0 podemos decir que si el contador esta mas bajo que 7 no se cree ningún disparo, haciendo así que los disparos se creen mucho mas lentos. Yo he puesto 7, pero el numero se puede alterar al gusto, claro. Esta técnica es indispensable aplicarla a cualquier botón que se utilice que no sean los de movimiento.



Una vez explicado esto, veremos como queda nuestro método Update tras manejar los disparos:



        public void Update()
{
UpdateShots();
UpdatePosition();
UpdateRectangle();
}
private void UpdateShots()
{
frameCounter++;
if (Keyboard.GetState().IsKeyDown(Keys.Z) && disparos.Count < 6 && frameCounter > 7)
{
Disparo s = new Disparo(posicion, anchoImagen, _content);
disparos.Add(s);
s.FueraDePantalla += new EventHandler(FueraDePantallaHandler);
frameCounter = 0;
}
disparos.ForEach(x => x.Update());
}


Como se puede ver, hemos añadido un método mas a Update donde se manejan los disparos. Lo primero que se hace es aumentar frameCounter, tras lo cual si la tecla está pulsada, hay menos de 6 disparos y framecounter está a 7 o más crearemos un nuevo disparo. Lo de la limitación de 6 disparos en la pantalla es totalmente opcional, depende del poder que le queráis dar a vuestra nave :).



Una vez se cumplen todas estas condiciones se crea un nuevo disparo, se añade a la lista y frameCounter se pone a 0. Además de esto también empezamos la escucha del evento, con lo cual, una vez se dispare el evento FueraDePantalla se invocará automáticamente al método FueraDePantallaHandler, que definiremos ahora, en el cual se eliminara el disparo que ha enviado el evento por que este se ha salido de la pantalla.



En cuanto a la última línea del método es una expresión lambda que básicamente recorre disparos y llama a Update de cada disparo. Lo más intuitivo sería hacer un foreach para que realice esto, pero dado que durante la ejecución del bucle puede cambiar el tamaño de disparos, si usamos foreach saltara una excepción, mientras que usando esta expresión Lambda no pasara. Otra solución sería recorrer la lista manualmente valiéndose de un bucle convencional como for.



En cuanto al manejador de eventos que borrara los disparos sobrantes, será algo tan sencillo como esto:



	private void FueraDePantallaHandler(Object sender, EventArgs args)
{
disparos.Remove((Disparo)sender);
}


Por ultimo, solo nos queda llamar al método Draw de cada disparo dentro del método Draw de Nave:



        public void Draw(SpriteBatch spbtch)
{
spbtch.Draw(imagen, posicion, rectangulo, Color.White);
DrawShots(spbtch);
}
private void DrawShots(SpriteBatch spbtch)
{
foreach (Disparo s in disparos)
{
s.Draw(spbtch);
}
}


Tras esto, podemos probar a ejecutar el programa, y veremos que nuestra nave ya esta dotada de un armamento temible.



imageY eso es todo por hoy. Podéis bajaros el código fuente de lo que tenemos hasta ahora en el siguiente enlace:image http://www.megaupload.com/?d=SQ0VIX0T



En la próxima parte del tutorial veremos como añadir a nuestro juego un fondo animado con scroll. No os lo perdáis y ¡¡Sed unos cangrejos pacientes!!









Etiquetas de Technorati: ,,,,,

7 comentarios:

  1. Ya he acabado esta 3ª parte del tutorial, muy bueno, espero con impciencia la 4ª, XD.

    ResponderEliminar
  2. a ver para cuando me puedo poner con ello :) no tardare, no te preocupes

    ResponderEliminar
  3. Buenisimo!!! sin duda el mejor tutorial de xna en español que he visto.

    ResponderEliminar
  4. La verdad, felicitaciones loco, muy bueno el tutorial; va directo al grano.
    Felicitaciones ;)

    ResponderEliminar
  5. esta muy bien el tutorial me gusto mucho y me esta ayudando mucho a entender esto del xna.. sigue asi

    ResponderEliminar
  6. He visto no sé cuantos tutoriales de XNA en español y ninguno de ellos explica tan bien como tú.
    De verdad Wardamo, si no fuera por tí me habría costado mucho más empezar en XNA.
    Ahora mi nave se mueve, cambia de imagen y dispara que da miedo xD
    Gracias!

    ResponderEliminar