miércoles, 30 de junio de 2010

GameLab 2010: Primer día



Hoy ha sido el primer día del GameLab 2010, la feria internacional del videojuego y el ocio interactivo. El evento se celebra en Gijón, dentro del palacio de congresos, en el interior del recinto ferial.
He podido observar tras recoger mi acreditación que este año hay más gente que otros años, y hay mayor cantidad de asistentes informales y menos empresas. Esto se debe a que como mencionaron en el acto de presentación, por primera vez este año se ha intentado orientar a todo tipo de asistentes, y no sólo a desarrolladores.
P300610_09.58
Una vez dentro lo primero que observamos es la zona de Sony, que no deja nada que desear. En ella he podido probar muchas cosas interesantes, gran cantidad de las cuales aún no están en el mercado.
P300610_10.30 P300610_10.30[01]
La primera de ellas y la mas llamativa es el poder probar PlayStation Move, con un juego de eye toy play o algo parecido, así como un juego de varios deportes (No nos suena de absolutamente nada). No estaba mal, era algo parecido a los juegos de minijuegos típicos de la wii. El sistema move es definitivamente mas preciso que la consola de nintendo, pero personalmente yo no he notado tanta diferencia como sony nos prometía, aunque quizás otros juegos lo exploten mejor, por lo que no hablare demasiado antes de tiempo.
P300610_10.47
También llamaba la atención el 3D de PS3, el cual me ha resultado bastante satisfactorio, dando la misma sensación que en el cine, aunque evidentemente no es lo mismo en una tele que en una pantalla de cine. De todos modos funciona genial, y pese al ligero mareo al empezar a usarlo, una vez se acostumbran tus ojos la sensación de profundidad esta muy lograda.
P300610_17.25
La tercera gran sorpresa que tuvimos fue una demo de Castlevania Lord of Shadows. Tras una larga cola he podido jugar la demo completa, y he de decir que la demo ofrecida deja ver un sistema de combate que no tiene nada que envidiar a God of war, por no hablar de la ambientación y de la historia que tiene al ser un Castlevania. En mi opinión, si el resto del juego mantiene la exploración y el plataformeo de los Castlevania clásicos va a ser un juego estupendo.
Además de esto en la sala de Sony podemos encontrar juegos que aún no han salido como Gran Turismo 5 y el SingStar nuevo (con guitarra), y otros que ya están en venta como Heavy Rain, God of War 3 y Play english para la psp.
P300610_10.50
Por desgracia, la parte de Nintendo no estaba ni por asomo tan bien como la de Sony, no hemos podido ver la 3DS, ni nada nuevo, sólo una exposición de Shigueru Miyamoto, donde pudimos probar algunos juegos nuevos como el Mario Galaxy 2 y el Smash Bros Brawl y algunos juegos clásicos, como el donkey kong y el super mario bros, pero por desgracia nada que aun no se haya lanzado aún.

En cuanto a la presencia de Microsoft, aún no hay nada, aunque se que mañana organizarán algunos talleres y otras actividades, y corren rumores de que se podrá ver Kinect (Proyect Natal) antes de terminar la feria. Esperemos que sea cierto.
P300610_12.13
También encontramos otras zonas donde podemos probar juegos en el iPad, así como un sensor que mide las ondas cerebrales para saber tu concentración y tu relajación, de la mano de la empresa Neurosky.

En cuanto a las conferencias del primer día, empezamos con una feria sobre la situación actual de los juegos online (MMO), un poco orientada a empresas, a la cual asistí durante solo un rato, por lo que no me entere mucho.
 
Tras esto, tuvimos al fin la inauguración de la feria, donde nos explicaron que el desarrollo de videojuegos es un sector en aumento en España, que no tiene tantas ventajas como otros sectores, pese a su importancia, pero que poco a poco va ganándose el reconocimiento que merece. También se explico que es el primer intento para abrir la feria al publico global y no sólo para desarrolladores, ya mencionado anteriormente.
P300610_16.10 P300610_13.05
Después de esto, se pasó a una mesa redonda, donde varios ponentes, como Gonzo Suárez y Enric Álvarez, hablaron de la situación internacional del desarrollo de videojuegos en España a nivel Histórico.
Después de comer, tuvo lugar la charla que a mí, personalmente, más me ha llamado la atención: Controladores mentales, de la mano del vicepresidente de Neurosky. Se habló de las diferentes generaciones de controladores, siendo los controladores tradicionales la primera generación y los controladores cinéticos, como Wii, Move y Kinect, la segunda generación. Siguiendo este orden según las generaciones avanzan se va estrechando la distancia entre el juego y el cuerpo del jugador.Por ello, la tercera generación de controladores de los videojuegos serían los sensores biológicos. Podemos ver un ejemplo de esto en el futuro sensor para la wii, Wii Vitality Sensor. Neurosky ofrece un sensor, bastante cómodo y accesible que detecta el estado del cuerpo, en concreto, la concentración, la tranquilidad y el parpadeo. Esto es una herramienta enormemente potente a la hora de hacer videojuegos bastante creativos, pudiendo medir en un juego de terror si el jugador esta asustado, por poner un pequeño ejemplo.
P300610_11.47P300610_11.47[01]
Tras esto, hubo varias charlas más, sobre los modelos de desarrollo adecuados para lanzar un juego de móvil exitoso, una charla sobre la administración como fuente de apoyo al desarrollo de proyectos de ocio interactivo, y otra sobre los juegos online, descargables vs juegos en la web.

No me entretengo más, siento no haber explicado tanto como me gustaría algunas charlas, pero ya es un post bien largo y además tengo algo de prisa. Mañana actualizaré las charlas que se den más a fondo ya que no tendré que volver a explicar las distintas salas.

En cuanto al tutorial de XNA lamento mi tardanza, pero la detección de colisiones me está dando
más problemas de lo que me esperaba, os prometo que en cuanto acabe el GameLab subo la quinta parte. ¡Sed buenos cangrejos!
PD: Gracias a Sara por el cangrejo ;)

martes, 22 de junio de 2010

Programando videojuegos: Tutorial XNA Game Studio 4.0 parte 4

crab-icon ball c#

Hoy en The Code Crab vamos a poner un fondo animado para nuestro futuro superventas. La técnica que vamos a seguir es bien sencilla: Tendremos una imagen que tenga el mismo ancho que la pantalla pero mucho mas alta, y la empezaremos a dibujar por abajo, subiendo poco a poco, hasta llegar a la parte superior que será similar a la parte inferior. Cuando pasa esto volvemos a la parte de abajo y ya tenemos efecto de scroll.

Dado que XNA solo soporta imágenes de resolución X x Y, donde ni X ni Y rebasan los 2000 y pico pixeles (no se cuantos exactamente) es posible que para un scroll largo haga falta dividir el fondo en diferentes imágenes. No vamos a dar un ejemplo de ese caso, pero que sepáis que sería exactamente lo mismo pero un poco mas complicado. Lo más importante es que si tenemos 3 imágenes A, B y C, para hacer una transición fluida de una a otra la ultima parte que se dibuje de una tiene que ser equivalente a la primera de la siguiente, esto es, el final de A y el principio de B coinciden, el final de B y el principio de C coinciden y el final de C y el principio de A coinciden.

Una vez tenemos claro lo que queremos hacer, necesitamos saber la resolución de nuestra ventana para preparar la imagen. Para ello, como recordareis, tenemos las propiedades PreferredBackBufferHeight y PreferredBackBufferWidth de graphics. Aunque si queréis se pueden alterar, yo no lo voy a hacer, voy a dejar la resolución por defecto. Como no la altero, voy a imprimir estos datos por pantalla, para saber que resolución tiene nuestra ventana:

 protected override void Initialize()
{
Console.WriteLine(graphics.PreferredBackBufferHeight + "x" + graphics.PreferredBackBufferWidth);
_fondo = new Fondo(graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
_nave = new Nave(graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
this.Window.Title="Space Burst";
base.Initialize();
}


Con esto, ejecutamos nuestro proyecto, y no ha cambiado nada, pero si vamos a la ventana de resultados veremos algo así:



image No os olvidéis de borrar la línea ahora que ya sabéis el tamaño de la ventana. Con esto ya sabemos la resolución de la ventana, 480x800, con lo que ahora necesitamos una imagen loquesea x 800 donde los primeros y los últimos 480 px sean iguales. A partir de una imagen real del espacio, algo de photoshopeo cutre (se me da fatal) y algo de paciencia, yo he conseguido esto, aunque si os apetece podéis haceros vuestro propio fondo.



spaceBackgr Con esto, tenemos ya un fondo de 1700x800 con el principio y el final igual (Hay que pinchar en la imagen para verla completa). Lo metemos en la carpeta y lo agregamos a los recursos del proyecto, como venimos haciendo con todas las imágenes. Tras esto ya estamos preparados para crear la clase Fondo (esta es bastante sencillita):



 private const int anchoImagen = 800;
private const int altoImagen = 1700;
private int altoVentana;
private Texture2D imagen;
private Rectangle rectangulo;
public Fondo(int altoVentana, int anchoVentana)
{
rectangulo = new Rectangle(0, altoImagen - altoVentana, anchoVentana, altoVentana);
this.altoVentana = altoVentana;
}


En cuanto a las propiedades, a estas alturas no os sorprenderá mucho, así que iré rápido. Guardamos el ancho y el alto de la imagen, así como el alto de la ventana (para saber cuanto tiene que medir el rectángulo) que será pasado en el constructor. También almacenamos un rectángulo con la sección de la imagen que se dibujara, empezando por la parte inferior de la imagen y un Texture2D con la imagen en sí.



        public void LoadContent(ContentManager Content)
{
imagen = Content.Load("spaceBackgr");
}
public void Update()
{
rectangulo.Y -= 1;
if (rectangulo.Y <= 0)
rectangulo.Y = ((altoImagen - altoVentana) -15);
}
public void Draw(SpriteBatch spbtch)
{
spbtch.Draw(imagen, new Vector2(0, 0), rectangulo, Color.White);
}


En cuanto a los métodos, tendremos los 3 tradicionales de XNA que ya conoceréis. LoadContent y Draw son iguales que en Nave, por lo que no merece mucho la pena pararse (en cuanto a Draw la única diferencia es que lo dibujamos en (0,0), ya que queremos que ocupe toda la pantalla).



Lo importante aquí (relativamente, pues es bastante simple en realidad) es el método Update, en el cual disminuimos en 1 la coordenada Y de rectángulo, para subir hacia arriba el rectángulo y crear un efecto scroll. A mi personalmente me gusta el efecto que queda al disminuir en 1 cada Update, pero si se aumenta el numero aumentara la velocidad del scroll. Eso queda a gusto de cada uno. También controlamos que si Y es 0 o menos, volvemos al principio, volviendo a mostrar la parte de abajo de la imagen, que como ya mencioné anteriormente debería ser igual. Ese –15 extra se debe a que si ponemos las coordenadas iniciales sin mas se apreciara un rebote, dado a que la imagen se esta moviendo constantemente, y que probablemente la parte de arriba y la de abajo no sean exactamente iguales pixel a pixel. A base de prueba y error para el caso de esta imagen he sacado que el numero apropiado son 15 px, pero si usáis otra imagen esto cambiara.



Ahora que ya tenemos lista la clase Fondo, solo nos queda agregar una instancia en la clase principal Game1, y las invocaciones a todos los métodos necesarios:



    public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Nave _nave;
Fondo _fondo;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

protected override void Initialize()
{
//Recordar comentar la siguiente linea
//Console.WriteLine(graphics.PreferredBackBufferHeight + "x" + graphics.PreferredBackBufferWidth);
_fondo = new Fondo(graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
_nave = new Nave(graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
this.Window.Title="Space Burst";
base.Initialize();
}

protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
_fondo.LoadContent(Content);
_nave.LoadContent(Content);
}

protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}

protected override void Update(GameTime gameTime)
{
// Cuidado con esto, no usamos mando.
if (Keyboard.GetState().IsKeyDown(Keys.Escape))
this.Exit();
_fondo.Update();
_nave.Update();

base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

spriteBatch.Begin();
_fondo.Draw(spriteBatch);
_nave.Draw(spriteBatch);
spriteBatch.End();

base.Draw(gameTime);
}
}


Es importante recordar, que como se menciono anteriormente, para que la imagen A se dibuje por encima de la imagen B, hay que dibujar primero B y luego A. Por tanto, el Fondo debe ser lo primero en dibujarse, o sino tapara el resto de objetos. Por lo demás, nada nuevo.



image



Tras ejecutar vemos el efecto obtenido. ¿Que os parece? Yo creo que no esta mal, queda un poco raro la nave pixelada con la imagen real del espacio, pero bueno, siempre podéis cambiar vuestro fondo por otro :P



Podéis descargaros el código fuente del tutorial hasta este momento en el siguiente enlace: http://www.megaupload.com/?d=RB2TYYLJcangrejus guaperus



Con esto concluye esta parte del tut orial. En la siguiente parte nos ponemos serios, introduciendo enemigos y detección de colisiones, no os lo perdáis. ¡Sed buenos cangrejos!



PD: ¡¡Gracias a Sara por el aporte del cangrejo!!



Etiquetas de Technorati: ,,,,,

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: ,,,,,

viernes, 18 de junio de 2010

Noticias


Hoy en the code crab aprovecharemos para anunciar varias cosas. La primera de todas es que (por fin) he terminado la carrera, por lo que ya soy un Ingeniero informático en toda regla ^^. Gracias a esto tendré mucho más tiempo para dedicar al blog, por lo que ya mismo me pongo a trabajar en el tutorial de XNA, que se que estáis ansiosos de más.

En otra línea de sucesos, aprovecho para anunciar que asistiré al GameLab 2010, una feria del videojuego que se celebra en mi provincia (Asturias), por lo que tendré la oportunidad de asistir. Intentare realizar resúmenes de las conferencias a las que asista y ponerlas en el blog, aunque no prometo nada de la calidad, pues soy informático y no periodista. No me hace mucha gracia que se me entremezcle las entradas del GameLab con las de XNA (Que parece que va para largo), pero para compensar, tras terminar todas las partes del tutorial, las recopilare en un PDF y subiré el archivo en una entrada.

Pues no os aburro más con esta entrada sin contenido. La tercera parte del tutorial saldrá en unos días (esta vez sí), ¡Sed unos cangrejos pacientes!

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: ,,,,,

domingo, 6 de junio de 2010

Programando videojuegos: Tutorial XNA Game Studio 4.0 parte 1

crab-icon ball c#

Antes de nada quiero disculparme por la ausencia de las ultimas semanas, he estado demasiado ocupado como para poner ninguna entrada, pero ya estoy de vuelta, y empiezo fuerte.

En las próximas entradas, vamos a trabajar sobre XNA, un entorno de desarrollo proporcionado por Microsoft para el desarrollo de juegos 2D y 3D. Tiene un montón de ventajas, pero en mi opinión la mas importante es la portabilidad del código a la hora de trasladar un juego entre las diferentes plataformas soportadas (PC, XBox 360 y Windows Phone). Evidentemente no es lo mismo trabajar con la pantalla de un móvil que con una tele, pero las diferencias en cuanto a código se reducen a eso y a los controles del juego.

Quiero mencionar que este tutorial esta orientado tanto a gente que nunca ha programado videojuegos como a gente que nunca ha utilizado XNA. Solo se requieren conocimientos básicos de C#, e intentare ir paso por paso y explicarlo todo con detalle.

Para este tutorial, dividido en varias partes, vamos a desarrollar un juego para Windows (Un juego de naves en 2D), pero no es nada difícil trasladar este código a cualquiera las plataformas anteriormente mencionadas. ¿Que necesitamos para empezar a programar en XNA game studio? Pues simplemente, un visual studio y una versión de XNA Game Studio compatible con este. Yo voy a utilizar VS 2010 y XNA GS 4.0 por ser las ultimas versiones de ambos, pero las diferencias en cuanto al código deberían ser mínimas.

¿Donde conseguimos XNA Game Studio? Muy fácil, aquí mismo: http://creators.xna.com/es-ES/downloads

Una vez tenemos todo instalado podemos comenzar a programar. Lo primero que haremos será crear un nuevo proyecto de XNA para Windows Games.

imageTras esto veremos que nos autogenera ya bastantes cosas. La clase principal es la que nos habrá llamado Game1 y tiene los siguientes métodos:

  • Initialize se llama al principio de la ejecución del juego. En principio lo utilizaremos para crear instancias de los objetos que vamos a necesitar y para cambiar cosas como el titulo de la ventana.
  • LoadContet se llama después de Initialize. Este método creara una instancia de SpriteBatch, la cual usaremos para dibujar cosas por pantalla mas adelante. Además es aquí donde debemos cargar las imágenes de nuestro juego.
  • Update se llamara varias veces por segundo a lo largo de la ejecución del juego, y aquí deberemos insertar la lógica del juego.
  • Draw se llamara varias veces por segundo también y se encargara de dibujar cosas por pantalla valiéndose del antes mencionado SpriteBatch. Es importante tener en cuenta de que si una cosa se dibuja mas tarde que otra, la que mas tarde se dibuje quedara por encima de la otra.

Si probamos a ejecutar veremos una pantalla en azul.

image

No es gran cosa, pero es el comienzo de nuestro juego. Lo primero que debemos notar es que ese fondo azul no nos sirve (si alguien quiere hacer un juego de una piscina quizás sirva, pero nosotros vamos a hacer un juego de naves) así que cambiaremos el color de fondo por negro. Para ello habrá que modificar el método Draw:

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

// TODO: Add your drawing code here

base.Draw(gameTime);
}


Con esto el fondo de nuestra aplicación ya será negro. En partes posteriores de este tutorial se explicara como cambiar el fondo e incluso animarlo. El próximo paso para que la ventana sea mas adecuada a nuestras necesidades es cambiar el titulo. Esto lo insertamos dentro del método Initialize:



protected override void Initialize()
{
// TODO: Add your initialization logic here
this.Window.Title="Space Burst";
base.Initialize();
}




Tras esto la ventana ya esta preparada para nuestro juego, con un fondo negro y Space Burst de titulo,battleship el próximo exitazo de ventas esta preparado para arrancar :P. Ahora toca preguntarnos que es lo que falta en nuestro juego? Es un juego de naves, así que es bueno tener alguna. Yo voy a utilizar la siguiente imagen, que es un sprite con varias posiciones de una nave



Para añadir la imagen a nuestro proyecto la copiamos en la carpeta del proyecto y la añadimos (arrastrándola mismamente) a Content de nuestro proyecto. Esto será una especie de proyecto a parte para los que usen XNA GS 4.0 o una carpeta en caso de usar versiones anteriores.



Ahora crearemos la clase Nave. Podríamos poner todo dentro de la clase Game1, pero el código resultante no sería nada elegante. Añadimos la nueva clase al proyecto Click derecho>Añadir>Clase y comenzamos con nuestra nave. Antes de nada y para evitar futuros problemillas añadiremos los diferentes imports necesarios:



using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;


¿Que atributos necesitamos que tenga? Pues vamos a ir poco a poco, así que empezaremos con 2: un objeto Texture2D donde se almacenara la imagen y un objeto Vector2 donde se almacenara la posición.




private Texture2D imagen;
private Vector2 posicion;


Además de esto, tendremos que añadir a la clase nave los métodos importantes de la clase Game1: LoadContent, Update y Draw, y así en la clase Game1 solo tendremos que llamar en cada método a los métodos correspondientes de todos los objetos que tengamos en la pantalla.



Debemos crear la instancia de posición dentro del constructor de la clase y la de imagen dentro de LoadContent. Como algo opcional podremos pasar al constructor de nave el ancho y alto de la ventana, para saber donde dibujar inicialmente la nave y que no quede demasiado descentrada:



 public Nave(int altoVentana, int anchoVentana)
{
posicion = new Vector2(altoVentana-100, (anchoVentana)/2);
}
public void LoadContent(ContentManager Content)
{
imagen = Content.Load<Texture2D>;("battleship");
}
public void Update()
{

}


El parámetro ContentManager es necesario para cargar imágenes, y lo pasamos al invocar el método desde la clase principal. Hay que notar que el nombre de la imagen se escribe sin terminación. Esto es importante, por que sino no te encontrara tu imagen. También añadimos ya el método Update de la clase nave, aunque aún no haga nada.




Aprovecho para explicar el sistema de coordenadas utilizado por XNA. Las medidas son en pixeles y al contrario que el sistema cartesiano el eje (0,0) esta situado en la parte superior izquierda de la ventana. Así por ejemplo si tenemos una ventana de 800x600 el punto (800, 600) sería la esquina inferior derecha, (800, 0) sería la esquina superior derecha y (0, 600) la esquina inferior izquierda.



Tras esto implementamos el método Draw de Nave, que dibujara la imagen en la posición adecuada:



public void Draw(SpriteBatch spbtch)
{
spbtch.Draw(imagen, posicion, new Rectangle(42, 88, 42, 44), Color.White);
}


Que es todo esto, os preguntareis. Lo primero es el parámetro, el objeto SpriteBatch anteriormente comentado, que se encarga de dibujar cosas por pantalla. Lo utilizamos para llamar al metodo Draw. Pide varios parámetros: con imagen le pasamos la imagen que debe dibujar, con posición le pasamos donde debe dibujarla (hasta ahí espero que lo entendáis bien), pero ¿que es ese Rectangle? Pues muy sencillo. Nuestra imagen tiene varias imágenes de la nave en diferentes posiciones, y ese Rectangle indica que parte de la imagen hay que dibujar. En caso de que la imagen fuese una sola no haría falta este parámetro. Los números que necesita para crearse son las coordenadas donde empieza a dibujar, x e y por ese orden, y el ancho y alto de la imagen. Nuestra imagen consta de 9 imágenes de 42x44, así que con esos parámetros debería dibujar la imagen central.



Tras esto solo queda crear una instancia de Nave y llamar a los métodos necesarios en la clase Game1:



    public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Nave _nave;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

protected override void Initialize()
{
// TODO: Add your initialization logic here

_nave = new Nave(graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
this.Window.Title="Space Burst";
base.Initialize();
}

protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
_nave.LoadContent(Content);
}

protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}

protected override void Update(GameTime gameTime)
{
// Cuidado con esto, no usamos mando.
if (Keyboard.GetState().IsKeyDown(Keys.Escape))
this.Exit();
_nave.Update();

base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

spriteBatch.Begin();
_nave.Draw(spriteBatch);
spriteBatch.End();

base.Draw(gameTime);
}
}


Como veréis, en el método LoadContent pasamos a Nave la anchura y la altura de nuestra ventana. En Update hay que tener cuidado, puesto que al menos a mi, en el código autogenerado comprobaba que estuviese pulsado el botón Atrás dentro de un gamepad (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed), y como probablemente nosotros usaremos teclado, tenemos que cambiar eso por la tecla escape, como se puede ver en el código. Además de esto llamamos al método Update de nave que aunque no haga nada, pronto lo hará. También llamamos a _nave.Draw dentro del metodo Draw principal, pasando como parametro el SpriteBatch.



Tras todo esto, si probamos a ejecutar, veremos algo similar a esto:




image

Aún no se mueve, ni hace nada, pero que esperabais, Roma no se construyo en un día. ¿Ha sido largo? No mucho. ¿Ha sido difícil? para nada.




image

Con esto concluyo esta titánica entrada que se me ha alargado bastante mas de lo que esperaba. Muy pronto sacare la segunda parte, para que nuestra navecita ya pueda hacer cosas. ¡Sed unos cangrejos pacientes!





Etiquetas de Technorati: ,,,,,