sábado, octubre 28, 2006

Logica de una plantilla "Windows Game XNA"

Buenas a todos, una vez de vuelta del viaje de esta semana, vamos a seguir aprendiendo acerca de las bases de un juego creado con XNA.

En este articulo, voy a intentar explicar la lógica del programa que "XNA Game Studio Express" diseña por nosotros cuando le ordenamos que cree un proyecto del tipo "Windows Game XNA". Esta plantilla, como ya vimos en artículos anteriores, esta diseñada para crear el famoso bucle de juego, que ya explicamos con anterioridad. En este punto, vamos a bajar a nivel del código fuente y vamos a ver como se ejecuta el programa, que líneas de código van corriendo, y como salta la ejecución del programa de un sitio a otro y porque.

El código que vamos a explicar es el de un juego vacío, es decir, el resultado de la ejecución de una plantilla "Windows Game XNA" sin ninguna modificación, es el de una pantalla de juego, con unas determinadas dimensiones, pero totalmente vacía. A pesar de esto, por debajo de esa pantalla, la ejecución del programa esta siguiendo el famoso modelo de bucle, y aunque parezca que el juego no hace nada, realmente demostraremos que cada segundo que pasa el programa esta actualizando la lógica del juego, aunque este vacía, y también esta dibujando la pantalla, igualmente vacía. Este ejercicio, aunque parezca que no tiene mucho misterio, nos será de gran ayuda mas adelante cuando queramos empezar a incluir código en nuestro juego, ya que sabremos donde situar ese código y porque.

Para seguir este articulo, creamos un nuevo proyecto, le decimos que queremos utilizar la plantilla "Windows Game XNA" y pulsamos F11. Al pulsar F11, le estamos ordenando al IDE (recordar que esto del IDE era el entorno de desarrollo), que ejecute el programa, pero en vez de ejecutarlo a toda velocidad, y sin que nosotros podamos controlarlo, le estamos diciendo que queremos que se detenga en cada una de las líneas que vaya a ejecutar para que podamos ver que esta haciendo en cada punto. De momento no nos va a ser necesario conocer todo el código que esta escrito, ni el por que se encuentra separado en varios componentes, solo queremos ver la lógica del programa, así que mas adelante hablaremos de estos otros temas. Comencemos:

static void Main(string[] args)
{
using (Game1 game = new Game1())


El programa empieza en este punto. En todo programa debe existir al menos un método Main(), siempre dentro de una clase. Recordar que estamos trabajando con un lenguaje que se puede considerar como orientado a objetos puro, donde todo debe estar dentro de objetos. En este caso, el método principal, por el que empieza todo programa, el método Main(), se encuentra dentro de una clase llamada Program, la cual se ha creado utilizando esta línea:

static class Program

Esta clase, como se puede ver en el código, lo único que contiene es el arranque del programa a través del método Main(). Dentro del método Main() nos encontramos con esta línea:

using (Game1 game = new Game1())

En esta línea realmente se están ejecutando dos instrucciones, y se podría leer de la siguiente forma:

Game1 game = new Game1();
using(game)

En la primera línea, lo que se ejecuta, es la creación de una instancia nueva de un objeto, cogiendo como plantilla para la creación de ese objeto la clase de objetos Game1.
La segunda línea, marca el objeto recién creado game para que pueda liberar la memoria fácilmente una vez no sea necesario utilizar. Ya hablaremos en artículos posteriores acerca de la sentencia using un poco mas a fondo.

Recordar que estas dos líneas estaban en el programa original como una sola, por lo que al invocar la creación de un nuevo objeto el programa va a saltar a lo que se denomina el constructor de ese objeto, así que pulsamos F11 de nuevo y vemos que salta a otra parte del código, veamos que tenemos por ahí...

partial class Game1 : Microsoft.Xna.Framework.Game
{
public Game1()
{
InitializeComponent();
}


Como la primera línea de código le ordenaba la creación de un objeto clase Game1, llamado game, el programa se ha ido a la definición de la clase Game1 para instanciar ese nuevo objeto. En este caso nos encontramos con que la línea donde empieza la creación de la clase es:

partial class Game1 : Microsoft.Xna.Framework.Game

esta línea indica que todo el código que aparezca a continuación entre las llaves {} será código de la clase, y que cada objeto creado a partir de esta clase debe llevar. En primer lugar, cada vez que se crea un objeto a partir de una clase, se llama a lo que se denomina el constructor de la clase. Este constructor no es ni más ni menos que un conjunto de sentencias que queremos que se ejecuten cuando se cree el nuevo objeto. En nuestro caso el constructor esta definido de esta f0rma:

public Game1()
{
InitializeComponent();
}

¿como se sabe que el constructor de la clase es esa parte del código?,.... pues muy sencillo, la respuesta es porque el constructor es un método que tiene exactamente el mismo nombre que el nombre de la clase, en este caso el nombre es Game1. Este constructor en concreto, solo tiene una instrucción, que simplemente hace una llamada para que se ejecute a un método llamado InitializeComponent(), que veremos mas tarde, ya que antes de continuar me gustaría anotar un par de detalles de la línea de definición de la clase:

partial class Game1 : Microsoft.Xna.Framework.Game

en esta línea, básicamente, le estamos diciendo que vamos a crear una nueva clase (class), que el código de esta clase no se encuentra completamente en este fichero, sino que tiene otras partes en otro fichero (partial), y que además esta clase se va a llamar Game1(Game1) y para finalizar, que esta clase Game1 es hija de una clase padre llamada Microsoft.Xna.Framework.Game ( : Microsoft.Xna.Framework.Game).

Si seguimos pulsando F11, y ejecutamos la línea donde se llama al método InitializeComponent(), vamos a ver que nos vuelve a saltar a otro sitio distinto, con el siguiente código:

partial class Game1
{
private void InitializeComponent()
{
this.graphics = new Microsoft.Xna.Framework.Components.GraphicsComponent();
this.GameComponents.Add(this.graphics);
}


Antes de nada, fijaros en que he ido quitando los comentarios, para que el código quede mas simplificado. Bien, en esta parte del código, vemos que empieza con una instrucción:

partial class Game1

tal y como hemos visto antes, cuando se definía la clase Game1, se utilizaba la palabra partial, que como he comentado, significaba que el código de la clase estaba repartido en varios ficheros. Pues esta línea indica que todo lo que quede entre las llaves {} de después de esta sentencia, forma parte también de la clase Game1. En este caso, al continuar con la ejecución, como hemos hecho una llamada al método InitializeComponent(), lo que va a hacer ese ejecutar las líneas de código de ese método:

this.graphics = new Microsoft.Xna.Framework.Components.GraphicsComponent();
this.GameComponents.Add(this.graphics);


Estas líneas empiezan a ser ya un poco mas complejas. En primer lugar, es importante conocer el significado de this.graphics . La palabra clave this hace referencia a la propia clase, es decir, en nuestro caso, ese this viene a significar lo mismo que Game1, que recordar que es el nombre del a clase donde se esta ejecutando este código. El punto(.) que separa el this de graphics, significa que lo que se encuentra a la derecha, en este caso la palabra graphics es algo que se encuentra dentro de lo que aparece a la izquierda, en este caso el this, que hace referencia como hemos visto antes a la propia clase (Game1). Pues bien, básicamente esta sentencia significa que, dentro de la clase Game1 (this), existe un contenedor llamado graphics(ahora veremos de donde sale), y que dentro de ese contenedor lo que queremos meter es un nuevo objeto (new) creado a partir de la clase Microsoft.Xna.Framework.Components.GraphicsComponent . Vamos por partes ahora, en primer lugar, ese contenedor graphics no se ha creado de la nada, sino que ha sido definido con anterioridad, aunque nosotros no lo hayamos visto a través del F11, y es que ocurre que algunas líneas del código, como la creación de variables, y algunas otras instrucciones, no se muestran al depurar el código usando el método de paso a paso (F11). En este caso, ese contenedor, que estamos viendo que va a contener un objeto, ha sido creado dentro de la propia clase Game1, y se encuentra unas pocas líneas mas abajo (pero fijaros que fuera del método InitializeComponent() ):

private Microsoft.Xna.Framework.Components.GraphicsComponent graphics;

fijaros, en esta línea, le estamos diciendo que dentro de la clase Game1, nos cree un contenedor llamado graphics y que va a ser del tipo Microsoft.Xna.Framework.Components.GraphicsComponent . Cuando hablamos de un tipo, estamos refiriéndonos a la forma que tiene que tener la información para caber en ese contenedor, en este caso, en graphics, solo entrara información con la forma de Microsoft.Xna.Framework.Components.GraphicsComponent , que en este caso es una clase de objetos.

En resumen, hemos creado un contenedor con una determinada forma, y luego hemos metido una instancia de un objeto, que justamente tiene esa forma, dentro de ese contenedor.

Este componente graphics, que es un objeto de la clase Microsoft.Xna.Framework.Components.GraphicsComponent va a contener una definición de nuestro display grafico. Esta definición nos va a ayudar mas tarde a muchas cosas, como por ejemplo a ver que resolución tendrá nuestro juego, a controlar si el display grafico esta listo cuando vayamos a dibujar, y un largo etcétera...

Me gustaría apuntar otro detalle importante. Recordar que en los primeros artículos, hablábamos de lo que era el XNA framework, y lo definíamos como una librería de código que íbamos a utilizar en nuestros juegos. Pues aprovecho para usar este ejemplo que acabamos de ver para que dejar los conceptos del framework totalmente claros. Imaginaros esta librería como pequeños trozos de código, organizados en una estructura como las carpetas de vuestro disco duro. En el caso que acabamos de ver (Microsoft.Xna.Framework.Components.GraphicsComponent), le estamos diciendo que dentro de la carpeta Microsoft, hay otra llamada Xna, que a su vez contiene Framework, esta a su vez Components, y dentro de esta carpeta Components, tenemos un fichero llamado GraphicsComponent. Este fichero realmente es una clase, que contiene una serie de código, y lo que estamos haciendo al meterlo dentro del contenedor graphics, que recordar se encuentra a su vez dentro de Game1, es disponer de todo ese código que se encuentra en GraphicsComponent, para que pueda ser utilizado en nuestro programa. Si no utilizásemos el framework de XNA, para usar el código de GraphicsComponent, tendríamos que escribirlo nosotros mismo, con la dificultad y perdida de tiempo que esto supone.

Bueno, siguiendo con el código, vemos que la siguiente línea que se ejecuta es:

this.GameComponents.Add(this.graphics);

Esta línea lo único que hace es organizar un poco nuestra clase. En este caso, dentro de nuestra clase Game1, que recordar en esta línea esta representada por la palabra clave this, tenemos definido un atributo llamado GameComponents, y que dentro de este atributo queremos añadir(add) el contenido de this.graphics, el cual ya conocemos lo que tiene del paso anterior. Básicamente, y para simplificar, lo que estamos haciendo es añadir this.graphics, dentro de una colección de componentes del juego. Este paso, aunque en un principio no nos diga nada, y no le veamos utilidad posteriormente, veremos que es totalmente necesario, ya que en el código que no vemos ni tocamos, es decir el código del propio framework de XNA, se hacen una serie de llamadas, como por ejemplo, a actualizar todos los componentes del juego, y si no hemos metido nuestro componente grafico dentro de esa colección, este componente grafico se nos va a quedar sin actualizar, ya que recordar, no estaría dentro de la colección de componentes del juego.

Perfecto.... espero que hasta este punto este todo más o menos claro. Si seguimos pulsando F11, vemos que llega a la sentencia:

game.Run();

Esta sentencia se encuentra dentro de la sentencia using que hemos visto justo al principio del articulo, ya que recordar que la ejecución del código se había ido a otro sitio ya que se tenia que instanciar una clase llamada Game1 la cual iba a tomar forma como objeto en game. Recordar esta línea

using (Game1 game = new Game1()),

ahora que estamos situados, comprobamos que lo que se realiza en game.Run() , es la llamada a un método (Run()), del objeto game (recordar que game es una instancia de Game1, y que esta clase a su vez es hija de Microsoft.Xna.Framework.Game). Pues bien, este método Run() esta definido dentro de la clase padre Microsoft.Xna.Framework.Game, y que es heredado por Game1, y que simplemente indica al programa que tiene que comenzar. En este momento es necesario que tengamos en mente el bucle de un juego, tal y como os mostré en los artículos anteriores (diagrama incluido). En ese bucle del juego, se llamaba alternativamente (no en todos los caso es alternativamente pero eso ahora no merece la pena discutirlo) a distintas partes del código del juego, una para actualizar la lógica del juego, y otra para mostrar en pantalla los distintos elementos gráficos. En nuestro caso, el método Run(), que recordar que no definimos nosotros, sino que viene en el framework de XNA, simplemente lo que hace (entre otras cosas que nosotros no vemos) es llamar al método draw(). Este método, como veremos a continuación, contiene la parte del código dedicada a mostrar por pantalla el resultado de nuestro juego. Este método Draw() se encuentra en nuestra clase Game1, y ahora veremos como funciona.

protected override void Draw()
{
if (!graphics.EnsureDevice())
return;
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
graphics.GraphicsDevice.BeginScene();
DrawComponents();
graphics.GraphicsDevice.EndScene();
graphics.GraphicsDevice.Present();
}


De nuevo he quitado los comentarios para que sea mas sencillo. Esta parte del código es el método Draw() creado por defecto, y que recordar que no hace nada en especial, simplemente contiene el código necesario para que, podríamos decir, se ejecute sin errores, y algunas líneas que nos ayudara a crear un juego usando esta plantilla. Estudiémoslo por partes:

protected override void Draw()
{
....
....
}


En primer lugar vamos a ver que tiene de especial la definición del método Draw(). Este método solo es accesible desde la propia clase o clases hijas (protected), aunque esto de momento no nos importe mucho. Además este método sobrescribe (override) la definición del método Draw() original definido en la clase Microsoft.Xna.Framework.Game.

Recordatorio importante: La clase Game1 que estamos definiendo es hija de la clase Microsoft.Xna.Framework.Game. Esta clase Microsoft.Xna.Framework.Game en su origen contiene una definición virtual del método Draw(), esto de "virtual" significa que al crear una clase hija, si nosotros queremos, podemos borrar la definición original y crear una nueva definición, que es justo lo que estamos haciendo al añadirle en la creación del método la palabra override.

A continuación se utiliza la palabra clave void para definir que este método se va a ejecutar pero no nos tiene que devolver nada como resultado, aunque de esto ya hablaremos en artículos posteriores y no nos interesa mucho ahora. Y finalmente, utiliza la palabra Draw() para definir el nombre del método.

Vamos con el código de dentro de Draw():

if (!graphics.EnsureDevice())
return;

estas dos líneas, se podrían leer de esta forma:

if (!graphics.EnsureDevice()) return;

fijaros en que solo hay un punto y coma, así que se puede leer como he puesto en una sola instrucción.

El significado de esta instrucción es: Si no tenemos un display grafico en estado usable termina con la ejecución del método Draw().

Si vamos por partes vemos que empieza con una sentencia If, esta sentencia condicional modifica la lógica de ejecución del código en función de una condición. En este caso la condición es !graphics.EnsureDevice() . El símbolo ! del principio indica negación (not), es decir va a alterar el resultado de graphics.EnsureDevice() . La instrucción graphics.EnsureDevice() indica que dentro de graphics existe un método llamado EnsureDevice(), recordar que graphics contenía una instancia de una clase que existía en el framework llamada Microsoft.Xna.Framework.Components.GraphicsComponent , y que si hacéis memoria, nos iba a ayudar a controlar algunos aspectos del display grafico, sin que nosotros tuviéramos que escribir ningún tipo de código para este propósito. Pues bien, al llamar a EnsureDevice(), este método nos devolverá un True (o indicador de acierto) si nuestro display grafico se encuentra activo y usable. Si juntamos ahora toda la sentencia vemos que estamos preguntando Si (if) no es cierto (!) que el dispositivo grafico (graphics) esta activo y usable (EnsureDevice())...... Si vemos ahora la otra parte de la sentencia, vemos que si esa frase se cumple lo que hará el programa es ejecutar la instrucción return, que únicamente indica al programa que debe de abortar la ejecución del método, en este caso el método Draw(). Como se puede adivinar, estas sentencias (que la hemos estudiado como una sola) simplemente controla que si no tenemos un display grafico activo y usable que deje de ejecutar las ordenes encargadas de dibujar en pantalla.

Seguimos pulsando F11 y nos encontramos con:

graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

esta instrucción hace una llamada a un método llamado Clear(), dentro de nuestro componente grafico graphics.GraphicsDevice (fijaros como nos esta siendo de utilidad el haber creado graphics), y a este método le estamos pasando un atributo, Color.CornflowerBlue.

De nuevo recordar que nosotros no hemos escrito el código que se encuentra dentro de graphics, sino que es un objeto instanciado a partir de la clase Microsoft.Xna.Framework.Components.GraphicsComponent, y por esta instrucción podemos adivinar que dentro de esta objeto tenemos un método llamado Clear(), que de nuevo nosotros solo estamos llamando para usarlo pero no lo hemos escrito, sino que ya esta dentro del framework de XNA. Este método Clear, simplemente lo que hace es limpiar el buffer que será mas tarde pasado a nuestro dispositivo grafico (Tarjeta grafica ---> Pantalla). A la hora de limpiar ese buffer lo puede hacer de muchas formas, como ya veremos en artículos posteriores, pero en este caso lo va a limpiar pintando todo el buffer de un color único. Este color se encuentra entre los paréntesis de la llamada al método, y este valor es lo que se denomina como un atributo, es decir, llamamos a las líneas de código del método Clear(), pero además lo llamamos pasándole un valor, que en este caso es un color, y que el código de dentro de la definición del método utilizara para lo que necesite. A la hora de pasarle el atributo, que en este caso es un color, se lo tenemos que pasar en un formato que lo entienda. En el caso que nos ocupa, este valor tiene que ir en un formato especial, que es el formato Color.

¿Como que formato color? mmmh no entiendo esto de que un color sea un formato....

bien, llegado a este punto, me gustaría hablar de un elemento que se utiliza en la programación orientada a objetos, y que nos va a ayudar a entender esto de que Color es un formato de datos. En la programación orientada a objetos vamos a poder crear nuestros propios tipos de datos, es decir, vamos a poder crear contenedores que tengan una forma concreta, y que a nosotros nos interesa por el motivo que sea, para poder luego meter información en ese formato. Un ejemplo muy sencillo para explicar esto es el que nos hemos encontrado en esta parte de código. Para el ordenador, un color no es el blanco, el azul, o el rosa, simplemente es la combinación de tres colores primarios, rojo, verde y azul (RGB, Red, Green, Blue). Pues bien, a la hora de pasarle el atributo a Clear(), le tenemos que dar un color, en el formato adecuado, que seria algo como esto (24,54,102), donde cada numero representa cada uno de los componentes fundamentales del color. Para simplificar las cosas, dentro del framework de XNA se ha creado lo que se llama una estructura, que es, como he contado antes, la definición de un contenedor con una determinada forma. En el caso de la estructura Color, en su definición, se ha establecido que para meter información dentro de esta estructura debemos de meter la información en este formato (byte, byte, byte), donde cada Byte tiene que ser un numero entre 0 y 255 (potencia de 2). En el código del programa vemos que llama a Color.CornflowerBlue. , pues simplemente le esta indicando que busque dentro del framework de XNA por la definición de la estructura Color, que ya hemos visto antes como esta definida, y dentro de esta definición busque por el valor de CornflowerBlue, que podría ser, por ejemplo (12,32,207). Este color CornflowerBlue viene definido dentro del framework de XNA por comodidad, al igual que muchos otros. Si quisiéramos, en vez de utilizar colores definidos por defecto podríamos crearnos para el parámetro que pasamos a Clear() nuestro propio color. Seria tan sencillo como hacer

graphics.GraphicsDevice.Clear(new Color(255,255,255));

donde le estamos diciendo que cree un nuevo (new) color (Color), con los componentes RGB 255, 255 y 255 ((255,255,255)).

Seguimos con F11 y nos encontramos:

graphics.GraphicsDevice.BeginScene();

Esta sentencia simplemente utiliza nuestro componente grafico graphics (de nuevo!!!!) y llama a un método llamado BeginScene(), el cual prepara el buffer para empezar a ser llenado. Antes he nombrado a este buffer pero no he explicado su función, así que aprovecho ahora para hacerlo. Dentro del método de dibujado Draw(), se suele realizar varios pasos: se borra el contenido de la ejecución anterior del método Draw(), de la forma que hemos visto antes con el Clear, después se guarda todo lo que queremos mostrar en un espacio de memoria, el famoso buffer, y cuando esta todo guardado simplemente se ejecuta una instrucción para que se vuelque el contenido de ese buffer en la memoria de la tarjeta grafica, la cual a su vez lo envía a nuestro monitor, permitiéndonos ver el resultado en pantalla.

El método BeginScene(), como he dicho antes, prepara el buffer para ser llenado. Este método se ejecuta antes de empezar a meter cosas en el buffer, y como veremos ahora, cuando ese buffer esta completo con todos los elementos que queremos mostrar, se va a cerrar con otro método, el método EndScene()

Continuamos con el F11......

DrawComponents();

Este método esta definido en la clase del framework XNA Microsoft.Xna.Framework.Game , y que ha su vez es la clase padre de nuestra clase Game1. Este método ejecuta una serie de instrucciones para enviar al buffer todos los elementos que le hayamos dicho que queremos dibujar, y esto a su vez para todos los componentes del juego que tengamos en nuestro juego. Esto de los componentes de juego lo veremos en artículos posteriores, pero básicamente, podemos dividir nuestro juego en varios elementos , para que sea mas fácil de controlar, por ejemplo, podríamos crear un componente de juego para que nos mostrarse el marcador de un juego de tenis y dibujar por un lado la cancha y por otro lado el marcador.

Una vez que hemos llenado el buffer, procedemos a cerrarlo con la siguiente instrucción:

graphics.GraphicsDevice.EndScene();

Si continuamos nos encontramos con:

graphics.GraphicsDevice.Present();

donde llamamos al método Present(), el cual envía a la tarjeta de video el contenido del buffer, haciendo que esta a su vez lo mande a nuestro monitor y veamos en pantalla representado un frame de nuestro juego. Daros cuenta que aunque no lo he mencionado, se vuelve a utilizar el objeto graphics para realizar todas estas operaciones.

Y con esto se termina el método Draw(), que era el encargado de mostrar en pantalla el contenido de nuestro juego. En un juego que no este vacío, como es nuestro caso, entre las líneas:

graphics.GraphicsDevice.BeginScene();

y

DrawComponents();

se debería incluir el código para ir metiendo en el buffer todos los elementos que queramos que aparezcan en escena, como por ejemplo, si fuera un juego de tenis, los jugadores, las raquetas, la pelota, la red, la cancha , etc....

Llegado a este punto, y si continuamos pulsando F11 vamos a ver que es posible que el método Draw() se ejecute varias veces seguidas, sin ninguna razón aparente. Aunque esto ya lo explicare mas adelante, resumiendo mucho podríamos decir que en función de los frames por segundo que queramos que tenga nuestro juego, nosotros controlaremos cuantas veces seguidas se repite el método Draw() por cada 1000 milisegundos. Lo dicho, no quiero entrar en mucho detalle con este asunto ya que lo explicare en otros artículos, así que vamos a seguir pulsando F11 hasta que veamos que deja de repetirse el código de Draw() y salta a otro sitio, concretamente al método Update().

El código contenido dentro del método Update(), como su nombre indica, estará encargado de actualizar la lógica del juego, poniendo el caso hipotético que he usado antes de un juego de tenis, en esta sección update deberíamos controlar cosas como variar la posición de la pelota si esta se encuentra en movimiento, chequear si el jugador ha pulsado alguna tecla para mover a los jugadores, o actualizar el marcador en el caso de que se haya producido un tanto nuevo.

Como es de esperar, el código del método Update(), en nuestro caso, no hace nada, simplemente como he comentado antes, las justas y necesarias para que no haya problemas en la ejecución del juego, y alguna otra que nos han sido creadas para cuando empecemos a crear un juego nuevo usando esta plantilla.

Echémosle un vistazo al código...

protected override void Update()
{
float elapsed = (float)ElapsedTime.TotalSeconds;
UpdateComponents();
}



Para empezar, y como hicimos con Draw(), veamos la definición del método Update():

protected override void Update()

Exactamente igual que Draw(), este método esta protegido (no nos importa mucho esto ahora), sobrescribe la versión original de Update() de la clase padre (override), no devuelve ningún valor (void) y obviamente, se llama Update() (Update()).

La primera línea que nos encontramos es:

float elapsed = (float)ElapsedTime.TotalSeconds;

En esta línea, estamos se crea un contenedor (variable) llamado elapsed el cual tiene un formato en concreto, que es float. Float nos permite guardar números con hasta 7 decimales, desde Aprox. 1.5 x 10-45 a 3.4 x 1038 . Ahora veremos porque se utiliza este tipo de datos para la creación de este contenedor. En la segunda parte del código, después del igual, el cual representa que el contenido de la parte de la derecha se va a meter en la parte de la izquierda (en nuestro contenedor float) vemos que se hace una llamada una propiedad llamada ElapsedTime. Esta propiedad, definida en la clase padre de nuestra clase Game1 (recordáis cual era la clase padre verdad), contiene el tiempo que ha pasado desde la ultima vez que se llamo al método Update(), y además, al añadirle el TotalSeconds, le esta indicando que a la hora de devolver el valor, lo exprese en forma de segundos completos mas la parte decimal. Al incluir además de prefijo a esta llamada la parte (float) , le estamos diciendo que el valor del tiempo pasado desde el ultimo update sea representado en formato float, que como hemos visto antes tendrá una parte entera y hasta 7 decimales. En resumen, al ejecutar esta instrucción vamos a guardar en el contenedor elapsed el tiempo que ha pasado desde la ultima ejecución del método Update, en un formato de este estilo X.XXXXXX , y representado en segundos. Aunque no quiero de nuevo entrar en profundidad, comentaros que en la plantilla por defecto este valor va a ser siempre 0,0166666 segundos, que es uno partido sesenta (1/60) segundos. Este valor, y la forma en el que lo utilicemos, nos ayudara a controlar en nuestros juegos el flujo de actualización del mismo, por ejemplo, y para que quede claro, podemos hacer que nuestra bola del juego de tenis varíe en un pixel su posición en pantalla cuando haya pasado 0.5 segundos, es decir, cuando elapsed haya pasado aproximadamente 3 veces (es decir 3 updates). Ya veremos también en artículos posteriores como controlar este parámetro, y como hacer que el método Update se ejecute mas lento, mas rápido, o simplemente se ejecute tan rápido como la CPU de nuestro ordenador pueda.

Seguimos con el código, que ya estamos terminando y nos encontramos con:
UpdateComponents();

La llamada a este método, el cual este definido como podéis imaginar, dentro de la clase padre (ahora si que ya no digo el nombre...) de nuestra clase Game1, se encarga de enviar las instrucciones necesarias a todos nuestros componentes de juego, para que se actualicen. Daros cuenta que este método es similar al método que vimos en Draw() llamado DrawComponents(), el cual tenia el mismo propósito pero para el método de dibujado.

En el caso de un juego que no este vacío como este, nuestro código debería incluirse entre

float elapsed = (float)ElapsedTime.TotalSeconds;

y

UpdateComponents();

ya que es muy probable que queramos usar la variable elapsed en nuestro código propio de Update() y finalmente una vez modificada la lógica del juego tengamos que llamar al método UpdateComponents() para actualizar todos los componentes de juego.

Y con esto termina el código de la parte de Update(). Si seguimos pulsando F11 continuamente, veremos que al igual que ocurría con Draw() se ejecuta varias veces seguidas para finalmente volver a pasar al método Draw(), el cual se ejecutara tal y como vimos anteriormente y volverá a llamar a Update() y así continuamente hasta que terminemos la ejecución del juego, creando el ya conocido bucle de juego.

Para terminar, deciros que espero que este articulo, aunque extenso(me ha llevado varias horas), os sirva para entender completamente que nos encontramos al crear un juego nuevo usando la plantilla de "Windows game XNA" y que a partir de estas bases podáis ser capaces de entender donde tenemos que empezar a incluir nuestro código para la creación de nuestro juego.

Tal y como ocurrió la semana pasada, esta semana por motivos de trabajo también estaré de viaje, concretamente en Bilbao, así que veo difícil el poder actualizar el blog hasta la semana que viene a estas horas, así que espero que tardéis en digerir este articulo para que no os quedéis pendientes de actualizaciones, por lo menos en eso, una semana...

Un saludo a todos

6 comentarios:

Anónimo dijo...

Muy bien explicado y gran trabajo por tu parte, sigue asi por favor.

mihe dijo...

Gracias por el comentario, lastima que esta plantilla sea la de la beta 1, pero bueno, comprenderla esta bien ya que la de la beta 2 es igual pero un poco mas organizada.

Gracias por el comentario y por la visita!!!

nando dijo...

La verdad es q no soy muy dado a hacer comentarios ni nada de eso en los blogs, pero la verdad es que en este caso me siento casi obligado ha hacerlo y agraderce el trabajo que estas haciendo aqui.
Dios!! vaya paliza de escribir te has pegado tio, y ademas muy bien explicado.
Espero que sigas actualizando mucho tiempo y poder seguir aprendiendo contigo.
Estoy deseando ponerme ha hacer cositas en XNA, y porsupuesto te pasare los jueguecillos que vaya haciendo XDDD
Muchas gracias tio

mihe dijo...

Gracias por tu comentario Nando. La verdad es que espero actualizar el blog durante bastante tiempo y si el trabajo me lo permite con mucha frecuencia. Actualmente el panorama es un poco extraño, ya que aunque han prometido que no va a cambiar mucho, estoy temieno que hagan muchos cambios entre la beta 2 y la beta 1. De todos modos estoy preparando tutoriales que no dependan mucho de componentes complejos y susceptibles a cambios para que no tenga que cambiarlos luego.

Un saludo y espero que vengas mucho por aqui !!!!

Togusa dijo...

Muy buen tutorial. Con unos conocimientos muy basicos de programacion orientada a objetos es facil seguirlo sin perderse y comprender un poco mas a fondo como funciona la plantilla.

¡Animo y sigue así!

mihe dijo...

Gracias por tu comentario Togusa. ahora te recomiendo que te mires los tutoriales que he preparado !!! acabo de terminar el segundo con mas cositas nuevas !!!!!!

Un saludo!