Anda di halaman 1dari 47

Tema 1 Preparacin de proyecto y ventana inicial.

Bienevidos al Tema 1.Antes que nada, quiero aclarar que junto con los lectores, yo mismo voy aprendiendo de esta tecnologia, por lo tanto, es muy probable que haya que hacer correcciones ( los tips y correcciones de los lectores seran bienvenidos ). En este Tutorial, les voy a ensear como armar el entorno desde cero. Con esto no me refiero a hacer click en nuevo proyecto, sino a explicar un poco las funciones y armar los archivos desde el comienzo. Para seguir estos tutoriales, se requieren conocimientos de programacion en general, si conocen Java quizas no se pierdan tanto, ya que C# es bastante parecido.Generalmente intento programar siguiendo el paradigma de orientacion a objetos, asi que es tambien va a o ser necesario. No tengo drama en ayudar mediante los comentarios. Lo primero que hay que hacer, es abrir el Visual C# Express Edition, y comenzar un nuevo proyecto. Vamos a seleccionar Windows Game Library. Como ejemplo yo le puse "MiMotor" a la Library y "MiPrimerJuego" a la solucion.Recuerden que Las soluciones pueden tener multiples proyectos, recursos, etc.

A la izquierda debera estar el explorador de soluciones, van a ver que hay un archivo que se llamaClass1.cs. Hagan click derecho y seleccionen Eliminar.

Ahora vamos a agregar un proyecto que va a servir para ir viendo el progreso del motor. MiPrimerJuego") y seleccionan "Add" Hacemos click derecho en el nombre de la solucion (" (agregar) "Nuevo Proyecto". En la casilla nombre pongan "MotorDemo", y borren los archivos por defecto, estos archivos son:Game1.cs y Program.cs.

NOTA: La idea de armar el proyecto en una libreria Y en un ejecutable, comenzo por querer tener las cosas ordenadas. De esta forma, quedaria un ejecutable, una libreria con las funciones y los recursos (que pueden estar o no incluidos en la libreria, todavia no se bien como voy a hacerlo).

Un ejemplo (creo) de este desarrollo, es el quake 2, que se guia mas o menos por este metodo (repito, CREO).

Antes de empezar con la programacion, hagan click derecho en el proyecto "MotorDemo" y seleccionen la opcion "Establecer como proyecto de inicio". Nuevamente hagan click derecho en "MotorDemo" y seleccionen "Agregar Referencia". En el popup que aparecio, seleccionen la pestaa "Proyectos" y hagan doble click en "MiMotor". Ahora a codear un poco. En "MiMotor" hagan click derecho y seleccionen "Add->Clase". El nombre de la clase va a ser "MiGame". Esta clase va a heredar propiedades de la clase Game. Borren todo el contenido del archivo MiGame.cs, vamos a escribirlo desde cero para que aprendan que es cada cosa. El primer codigo que vamos a incluir son las llamadas a las librerias necesarias:

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

NOTA: Los namespaces contienen clases que vamos a necesitar, por ejemplo Graphics contiene clases para acceder a la palca de video. En este tutorial voy a crear algunos Namespaces, por ejemplo, mas adelante, para crear un objeto en el juego, vamos a hacerlo como MiNamespace.Objetos.ObjetoGenerico miObjecto. Es bastante ordenado. Recuerden que si no hacemos namespaces c# lo hara por nosotros, pero es mejor tener las cosas bien ordenadas. Ahora vamos a crear un namespace y poner nuestra clase principal:

namespace MiMotor { public class MiGame : Game {

} }

Como todos sabemos, las clases tienen variables (propiedades), operaciones (metodos) y constructores (tambien destructores). Vamos a declarar algunos para nuestra clase, dentro de la misma, agregamos el siguiente codigo:

//variables GraphicsDeviceManager miDispositivoGrafico; ContentManager miContenido;

//constructor public MiGame() { miDispositivoGrafico = new GraphicsDeviceManager(this); miContenido = new ContentManager(Services); }

GraphicsDeviceManager se encarga de manejar la configuracion de nuestro dispositivo grafico. ContentManager, se encarga de cargar objetos desde archivos binarios para usarlos mas tardes (por ejemplo una textura). Esto lo vamos a usar mas adelante. Ahora vamos a llamar a la funcion que dibuja la ventana.

protected override void Draw(GameTime gameTime) { miDispositivoGrafico.GraphicsDevice.Clear(Color.DarkOrange); base.Draw(gameTime); }

El metodo Draw siempre se esta ejectuando, se encarga de renderizar o dibujar los objetos en la pantalla, no deberia haber codigo logico aqui (operaciones). Bien, antes de seguir, el codigo de MiGame.cs deberia quedar asi:

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

namespace MiMotor { public class MiGame : Game { //variables GraphicsDeviceManager miDispositivoGrafico; ContentManager miContenido;

//constructor public MiGame() { miDispositivoGrafico = new GraphicsDeviceManager(this); miContenido = new ContentManager(Services); }

protected override void Draw(GameTime gameTime) { miDispositivoGrafico.GraphicsDevice.Clear(Color.DarkOrange); base.Draw(gameTime);

} } }

Ahora, agregamos una clase para el proyecto "MotorDemo", que se va a llamar "MiDemo". Como antes, borren todo el contenido. Lo primero que vamos a escribir, es indicarle al programa que vamos a usar clases de nuestronamespace anterior.

using MiMotor;

Este proyecto solo va a instanciar a nuestro motor y lo va a hacer correr, para ello, escribimos el siguiente codigo:

namespace MiDemo { class MiDemo { static MiGame game = new MiGame();

public static void Main() { game.Run(); } } }

Este codigo es muy simple, es importante que main tenga la M (mayuscula). Para terminar, el codigo deberia quedar como sigue:

using MiMotor;

namespace MiDemo { class MiDemo { static MiGame game = new MiGame();

public static void Main() { game.Run(); } } }

Si hicieron todo bien, al ejecutarlo deberian ver una linda pantalla naranja.

Tema 2 - Metodos importantes, teclado y fullscreen


Bienvenidos, en este turorial voy a comentarles sobre algunos mtodos que son muy importantes en el mundo XNA. El primero que vamos a tocar, es el mtodo Update. Dentro de este metodo deberiamos introducir todos los cambios que se realizan en el mundo. Por ejemplo los enemigos que pueden aparecer, los movimientos que hacemos con el teclado, etc. En la declaracion del metodo Update no deberia haber carga de graficos, texturas o sonidos. El metodo Update, se ejecuta despues de que se cargaron los graficos y se inicializaron las variables. Los metodos encargados de esto son: Obviamente el constructor de la clase, en nuetro ejemplo:

//constructor public MiGame() { miDispositivoGrafico = new GraphicsDeviceManager(this); miContenido = new ContentManager(Services); }

El mtodo Initialize()y por ultimo el mtodo LoadGraphicsContent(bool loadAllContent). Quiero que dejemos en claro algo muy importante que puede confundir a mas de uno. Si estan programando un juego, por ejemplo el pacman, y YA ESCRIBIERON la clase "pacman" Y ESTA NECESITA RECURSOS, NO PUEDE SER INSTANCIADA EN EL CONSTRUCTOR PRINCIPAL. Eso quiere decir, que no pueden crear un objeto en el constructor de su clase principal que necesite, por ejemplo, una textura. Ya que las texturas se cargan en el mtodoLoadGraphicsContent()y este se ejecutaDESPUESdel constructor, es como querer entrar a la casa sin abrir la puerta. Estoy seguro que puede confundir a mas de uno, pero esto es asi, y de hecho, esta perfecto que sea asi. Ejemplo:

public class Pacman { public Vector2 posicion;

public Texture2D textura;

public Pacman( ContentManager con) { textura = con.Load<Texture2D> ("ejemploSpritePacman"); } }

public class MiGame : Game { //variables GraphicsDeviceManager miDispositivoGrafico; ContentManager miContenido; Pacman pac;

//constructor public MiGame() { miDispositivoGrafico = new GraphicsDeviceManager(this); miContenido = new ContentManager(Services); pac = new Pacman(miContenido) } // MAL ESTO NO SE HACE

protected override void Draw(GameTime gameTime) { miDispositivoGrafico.GraphicsDevice.Clear(Color.Dark Orange); base.Draw(gameTime);

} }

Como veran, en el constructorMiGame()se esta intentando inicializar unpacman, pero las texturas aun no han sido cargadas, por lo que dara una excepcion. Sabiendo esto, ya pueden imaginarse, que todos los contenidos que llevan graficos deben cargarse enLoadGraphicsContent()( tampoco estara muy correcto hacerlo aca) o enUpdate()( aca estaria perfecto). A modo de ejemplo, imaginen que estamos programando elWoWy el mapa se va cargando a medida que avanzamos, llegamos a "The Brill", la funcion Update() deberia estar procesando en que lugar estamos, cuando (por ejemplo) la variable mapa as igual a The Brill dice, ah, carguemos los NPC correspondientes. No tendria sentido instanciar objetos en otro momento, ya que estarian ocupando preciosa memoria. Yo se que esta montaa de informacion los puede haber confundido, pero es la forma de hacer las cosas, tiene lgica. Tambien se que trat muchos temas que todavia no vemos (como por ejemplo cargar una textura), pero estamos cerca de hacerlo. Para hacer este tutorial un poco mas practico, les voy a ensear como funciona Update() para leer del teclado. Volvamos a la clase MiGame. Vamos a escribir la famosisima funcion Update de la que tanto hemos charlado. Despues del constructor escribimos:

protected override void Update(GameTime gameTime) { base.Update(gameTime); }

Las clases necesarias para utilizar el teclado, estan en el namespace (ver tutorial anterior si no se acuerdan) Input. Si mal no recuerdo en el tutorial 1 importamos el namespace, sino al principio del archivo agreguen:

using Microsoft.Xna.Framework.Input;

Ahora vamos a instanciar un objeto KeyboardEstate ( indica el estado del teclado... este era facil ), y despues con una simple comprobacion vamos a cambiar a modo fullscreen. El metodo completo quedara:

protected override void Update(GameTime gameTime) {

KeyboardState estado = Keyboard.GetState(); if (estado.IsKeyDown(Keys.F10)) { miDispositivoGrafico.ToggleFullScreen(); }

base.Update(gameTime);

El miembro ToggleFullScreen() se encarga de cambiar entre fullscreen y windows mode, esto va a pasar cuando pulsemos F10. Bueno, por ahora esto es mas que suficiente, en el proximo tutorial les voy a mostrar como cargar un sprite. SI, ya se que prometi un engine 3d, pero es muy importante que aprendan lo basico, aparte, todavia ni yo se como hacerlo.

Tema 3 - Importar un modelo


Lo prometido es deuda, aca vamos a ver algunos conceptos interesantes. Antes de empezar con la practica les recuerdo que C# es orientado a objetos por lo tanto vamos a explotar dicha cualidad al maximo. Como dije en otro post, pensaba dedicar este ejemplo a renderizar un sprite, pero por ahora no nos interesa en nuestro motor 3d, por lo tanto este tutorial se va a referir a incluir un modelo al programa. Muchos de los tutoriales que encontre no tratan este tema bien, en lo que respecta a la orientacion a objetos, pero en este espacio todos tratamos de ser buenos programadores, asi que vamos a hacer las cosas como se debe. Una consideracion antes de empezar, no pretendo que nadie sepa como modelar en 3 dimensiones, obviamente es un proceso mas complicado que hacer un sprite, donde valiendonos con solo el paint lo podemos hacer. Los modelos que vamos a usar son hechos por mi (no importa con que software), ya van a estar listos para importar en el formato correcto. Antes de cargar nuestro modelo, quiero estar seguro que todos sepan lo que es, para ello voy a hacer una comparativa contra una textura.

y y y y y y

En una textura o sprite, la unidad minima es el pixel, en un modelo es el vertice. Una linea entre 2 pixeles forma... una linea , una linea entre 2 vertices no forma nada. Un conjunto de al menos 3 vertices unidos, forma una cara. Por lo tanto, el objeto mas basico que se puede observar es un triangulo. Un conjunto de caras forma un poligono. Un conjunto de poligonos forma un "mesh". Un conjunto de meshes forma un modelo.

No es dificil, no ? Bueno, faltan algunas cosas como materiales y esqueletos, pero lo vamos a dejar para otro tutorial. BIEN ! ya sabemos lo que es un modelo, pero como lo metemos al programa ???? facil, con matrices, mas precisamente, una matriz de transformaciones. Dicha matriz es muy importante, contiene 3 cosas importantisimas en el mundo de la programacion en tres dimensiones:

1. Posicion

2. Escala 3. Orientacion

Lo que vamos a hacer es plamar la informacion del modelo en una matriz para representarlo. Para poder verlo, necesitamos una "vista", que tambien se representa con una matriz. No necesitamos un modelo para la vista, es solo un concepto. Bueno, ya tenemos cocinada toda la teoria, vamos a las pias. Primero, bajen en modelo que vamos a usar: pilon.X

Ahora vamos a definir una variable que va a ser nuestra "camara" virtual, en la inicializacion de variables agregamos la linea:

Vector3 camara;

En nuestro ejemplo, quedaria:

... public class MiGame : Game { //variables GraphicsDeviceManager miDispositivoGrafico; ContentManager miContenido; Vector3 camara; ...

En el constructor de nuestro ejemplo, iniciamos la camara como sigue:

camara = new Vector3(0.0f, 5.0f, 5.0f);

En nuestro ejemplo quedaria:

//constructor public MiGame() { miDispositivoGrafico = new GraphicsDeviceManager(this); miContenido = new ContentManager(Services); camara = new Vector3(0.0f, 5.0f, 5.0f); }

Todavia estoy experimentando con esto, pero quiero aclarar que en el mundo de XNA, el eje Z no es arriba ya bajo. En XNA el eje Y es con el que se indica la altura.

Ahora vamos a iniciar las matrices necesarias para dibujar en 3d. Los conceptos que tengo sobre estas matrices todavia son MUY basicos y puede que en algunos este equivocado. Si alguien quiere corregirme, como siempre, es bienvenido.

EL metodo draw nos quedaria:

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

float aspectRatio = 640.0f / 480.0f;

Matrix world = Matrix.Identity;

Matrix view = Matrix.CreateLookAt(camara, Vector3.Zero, Vector3.Up);

Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);

base.Draw(gameTime); }

Ufff... cuantos conceptos nuevos, vamos de apoco. La matriz de Proyeccion muestra los objetos tridimiensionales de forma que los entendamos en nustro simple dispositivo de 2 dimensiones (lease monitor). La matriz World contiene los objetos que vamos a utilizar, su rotacion, escala etc. En este momento no tenemos nada inicializado, asi que la inicializamos como el objeto neutro en las matematicas, en el espacio de matrices, la identidad. La matriz view ya la charlamos, contiene la camara y el destino. Cualquier modificacion de los objetos(3d), deberia quedar plasmada en esta matriz (por ejemplo una rotacion. En este punto deberian poder compilar el proyecto sin problemas. Vamos a agregar el modelo a nuestro proyecto, primero para ordenar las cosas creamos una carpeta para nuestros recursos. Click derecho en MiMotor -> add -> "Nueva carpeta". Como nombre, le ponemos "Recursos".

Ahora hacemos click derecho en "Recursos" -> add -> elemento existente y buscamos el modelo (pilon.X). Recuerden que en tipo de archivo tienen que elegir "Content pipeline".

Ahora vamos a crear un nombre de espacios que va a contener los objetos basicos de nuestro motor. Hacemos click derecho en "MiMotor" -> add -> Nuevo elemento. Seleccionamos Game Component y como nombre ponganle "MisObjetos". Borren todo el contenido antes de seguir.

En este componente vamos a crear una clase simple para crear un objeto muy basico, solo tendra una propiedad, un modelo. Agregamos los nombres de espacios necesarios:

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

using System;

Creamos nuestro nombre de espacios y agregamos una clase:

namespace MisObjetos { public class MiObjetoGenerico { } }

Agregamos sus propiedades y constructor:

//variables protected Model modelo;

public MiObjetoGenerico() {

Ahora vamos a ver otro concepto importante. Cada objeto tendra sus propios metodos update, draw y load. El metodo Load va a ser el encargado de cargar el modelo. Recordemos que no se puede cargar el modelo en el constructor, bueno se podria, pero podriamos estar ante un error de diseo. Despues del constructor, agregamos:

public void load(ContentManag er content) { modelo = content.Load<Model>("Recursos//pilon"); }

Como parametro recibe un manejador de contenido (que ya estara inicializado en la clase principal).

Nota: Es MUY importante el nombre (asset) del modelo, para verlo, en la ventana de propiedades (f4 si no la ven), aparece.

Lo unico que nos queda es el metodo Draw (dibujar), que es bastante simple, despues del metodo Load agregamos el siguietne codigo:

public void dibujar(Matrix world, Matrix vista, Matrix proyeccion) { foreach (ModelMesh mesh in modelo.Meshes)

{ foreach (BasicEffect efecto in mesh.Effects) { efecto.EnableDefaultLighting(); efecto.World = world; efecto.View = vista; efecto.Projection = proyeccio n; } mesh.Draw(); } }

Recordemos que los mesh componen un modelo, effect es la forma en la que se dibujan los meshes (creo). Nuestro objeto basico esta listo ! Ya podemos compilar y no deberiamos tener problema. Ahora tenemos que agergar el objeto a nuestro motor. Vamos a la declaracion de variables y agregamos el codigo necesario, quedaria:

//variables GraphicsDeviceManager miDispositivoGrafico; ContentManager miContenido; Vector3 camara; MisObjetos.MiObjetoGenerico objeto;

En el constructor:

//constructor

public MiGame() { miDispositivoGrafico = new GraphicsDeviceManager(this); miContenido = new ContentManager(Services); camara = new Vector3(0.0f, 5.0f, 5.0f); objeto = new MisObjetos.MiObjetoGenerico(); }

Recordemos que no podemos cargar modelos y texturas hasta que los recursos esten listos, por lo tanto, en el metodo LoadGraphicsContent, el cual agregaremos despues del metodo Update, quedaria como sigue:

protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { //cargar... objeto.load( miContenido ); } base.LoadGraphicsContent(loadAllContent); }

Lo unico que queda es dibujar nuestro objeto. Nuestro metodo Draw quedaria:

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

float aspectRatio = 640.0f / 480.0f;

Matrix world = Matrix.Identity;

Matrix view = Matrix.CreateLookAt(camara, Vector3.Zero, Vector3.Up );

Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);

objeto.di bujar(world,view,projection );

base.Draw(gameTime); }

Si todo salio bien, van a poder ver un lindo modelo dibujado en la pantalla.

Tema 4 - Importar un modelo mas lindo y moverlo


Continuando con el ejemplo anterior, ahora voy a ensearles como importar un modelo mas lindo, en realidad en esto no hay muchos cambios de codigo... de hecho, no hay cambios. Lo unico nuevo es que van a tener que importar la textura del modelo. Archivos necesarios: Contenido.rar Antes de empezar les explico la teoria: Se acuerdan de el famoso metodo Update ? Bueno vamos a escribir uno en nuestra clase, se va a encargar de leer las entradas de teclado. Eso es toda la teoria por ahora. Vamos al codigo, abrimos "MisObjetos.cs" y agregamos las siguientes propiedades para hacer rotar el modelo:

//variables protected Model modelo; protected float rotacionX = 0.0f; protected float rotacionZ = 0.0f; protected float rotacionY = 0.0f;

Ahora vamos a escribir el metodo Update para nuestro objeto, no tiene absolutamente nada nuevo, mediante el objeto KeyState leemos el imput y cambiamos unas variables. Despues del constructor, agregamos:

public void update() { KeyboardState estado = Keyboard.GetState(); if (estado.IsKeyDown(Keys.Left))

{ rotacionZ = rotacionZ + 0.1f; } if (estado.IsKeyDown(Keys.Right)) { rotacionZ = rotacionZ - 0.1f; } if (estado.IsKeyD own(Keys.Up)) { rotacionY = rotacionY + 0.1f; } if (estado.IsKeyDown(Keys.Down)) { rotacionY = rotacionY - 0.1f; } }

Ahora cargamos el nuevo modelo, click derecho en "Recursos->add->Elemento existente" y agregamosautito.FBX y autotext.png.

RECUERDEN: poner en piepline el tipo de archivo cuando los agreguen

Ya tenemos nuestro metodo update, ahora solo cambios el modelo de nuestro objeto, cambiamos pilonpor autito (si ese es el asset del modelo). Quedaria:

public void load(ContentManager content) { modelo = content.Load<Model>("Recursos//autito"); }

Ahora vamos a reescribir el metodo para dibujar de la clase, solo vamos a agregarle la rotacion:

efecto.World = world * Matrix.CreateRotationX(rotacionX) * Matrix.CreateRotationZ(rotacionZ) * Matrix.CreateRotationY(rotacionY);

Listo, con eso tenemos la clase terminada, ahora vamos a MiGame. Lo primero que vamos a hacer es tocar los datos de la camara, este modelo es mas gr ande que el anterior, asi que alejamos un poco la camara, en el constructor dejen el codigo asi:

camara = new Vector3(0.0f, 50.0f, 500.0f);

Y en el metodo update, agregamos nuestra funcion:

protected override void Update(GameTime gameTime) {

KeyboardState estado = Keyboard.GetState(); if (estado.IsKeyDown(Keys.F10)) { miDispositivoGrafico.ToggleFullScreen(); } objeto.update(); base.Update(gameTime);

Tutorial 5 - modelos un poco mas complejos


Ya habia dicho en mi post anterior que los tutoriales que se venian eran pesados en teoria. Decidi retrasar esos tutoriales para mostrarles un poco mas el funcionamiento de la matriz World. Para ello, hice un modelo un poquito mas complejo que el anterior. Es el mismo autito solo que ahora tiene 4 meshes mas que son las ruedas. Todavia no vamos a acceder a cada rueda, solo vamos a mostrarlas de forma correcta. Archivos necesarios: 1Contenido.rar No hace falta a estas alturas decirles como se agregan esos archivos al proyecto. Solo tengan en cuenta agregarlos al directorio Recursos y corroborar el asset. Si agregamos estos archivos (la textura la deberian tener del tutorial anterior) y referenciamos el modelo a este nuevo ocurriria un error bastante feo:

QUE PASO ??? ACASO CHOCO NUESTRO AUTITO ??? No, el problema es el siguiente. Cuando decimos como dibujar cada mesh, no estamos aplicando la transformacion a la matriz correspondiente. Recuerden que para lo que nosotros es un modelo, para XNA es solo una matriz con datos.

Por defecto, nuestra matriz de transformaciones era la identidad (elementro neutro). Cuando cargabamos el modelo, no estabamos aplicando los valores necesa rios a esta matriz (posicion, escala y orientacion). Para poder ver el modelo correctamente, tenemos que plasmar el mismo a la matriz. Primero cambiamos la referencia, en el metodo Load, cambiamos el codigo como sigue:

modelo = content.Load<Model>("Recurs os//autitoRuedas");

NOTA: si no entienden esto (yo se que soy muy bruto para explicar) les ruego que pregunten o busquen bibliografia, si no entienden como se maneja esto van a estar programando a ciegas. Lo que vamos a hacer, es una matriz que va a contener los cambios de todo el entorno (imaginemos que tenemos un mundo con palmeras y oceanos) y le vamos a "agregar" los cambios de nuestro auto. Para eso, en el metodo dibujar, tenemos que declara una matriz que va a contener la informacion de cada mesh (chasis y las 4 ruedas) y va a aplicar esos datos a la hora de dibujar cada parte del auto. El metodo dibujar de nuestro objeto quedaria:

public void dibujar(Matrix world, Matrix vista, Matrix proyeccion) { modelo.Root.Transform = world; Matrix[] contenedor = new Matrix[modelo.Bones.Count]; modelo.CopyAbsoluteBoneTransformsTo(contenedor);

foreach (ModelMesh mesh in modelo.Meshes) { foreach (BasicEffect efecto in mesh.Effects) { efecto.EnableDefaultLighting();

efecto.World = contenedor[mesh.ParentBone.Index] * Matrix.CreateRotationX(rotacionX) * Matrix.CreateRotationZ(rotacionZ) * Matrix.CreateRotationY(rotacionY); efecto.View = vista; efecto.Projection = proyeccion; } mesh.Draw(); } }

Lo que hacemos es setear la matiz world (en este caso no hay nada transformacion principal, o "raiz" de nuestro modelo.

) como la

La matriz contenedor tiene la informacion de cada bone (en este caso ninguno) de nuestro modelo. La linea modelo.CopyAbsoluteBoneTransformsTo(contenedor) pasa toda la informacion del modelo a nuestra matriz. En caso de tener que hacer animaciones o cambios, esta matriz contendria los datos.

Tema 6 - animacion esqueletal


En este tutorial vamos a ver un concepto nuevo y aveces dificil para algunos: la animacion esqueletal. Quiero aclarar que no voy a cubrir un tutorial de como hacer un modelo y como aplicarle un esqueleto, hay mucho material dando vuelta al respecto. Por mi parte estoy usando Milkshape 3d. Como dije antes un modelo es un conjunto de meshes que forman un objeto, en los ejemplos que venimos siguiendo, nuestro modelo es un auto, contiene 5 meshes, estos son las 4 ruedas y el chasis. Imaginen que queremos hacer una animacion, por ejemplo hacer rotar una rueda. Lo logico seria tomar todos los vertices de la rueda y aplicarles una transformacion (en este caso, rotacion). Pero calcular todo esto implica calculos matematicos, y cuando se tiene un modelo complejo, con unos cientos de miles de vertices, la practica se hace imposible de aplicar. Para solucionar este problema se usa un concepto llamado "animacion esqueletal" donde se aplica un esqueleto al modelo. Un esqueleto no es mas que una cadena de uniones y huesos emparentados.

Algunas cosas importantes: y y y y Los esqueltos tienen forma hereditaria, un padre tiene uno o mas hijos. Un hueso conoce solo su hermano mas cercano y no tiene informacion de los demas. Los esqueletos tienen un nodo raiz El movimiento de un hijo afecta los anteriores

Ahora, cada union, puede tener asignado un grupo de vertices que obedeceran a esta, osea, que si aplico una transformacion a una union con vertices asociados, estos se deformaran igual que la union:

En la imagen, se ve que una union tiene los vertices de la rueda, osea que si deformo la union (ejemplo rotarla) se deforman los vertices. Facil. Primero, aca estan los archivos que vamos a necesitar: 3Contenido.rar Ahora, al codigo: Lo primero que vamos a hacer, es deshacernos del contenido viejo.

Ahora agregamos el contenido nuevo (autito.x y autotext.png). Esto lo hacemos igual que como lo veniamos haciendo antes. Ahora vamos a modificar los valores de la camara (las dimensiones del autito cambiaron), enMiGamemodificamos la camara para que quede asi:

camara = new Vector3(0.0f, 30.0f, 60.0f);

Ahora vamos aMisObjetos. Vamos a re escribir todas las variables, ya que muchas no las vamos a usar mas, entonces, todo el codigo hasta el constructor tiene que quedar asi:

//variables protected Model modelo;

//movimiento protected float rotacionDelantera = 0.0f; protected float rotacionTrasera = 0.0f; protected float rotacion = 0.0f;

//bones ModelBone boneRueda1; ModelBone boneRueda2; ModelBone boneRueda3; ModelBone boneRueda4;

//transformaciones iniciales Matrix transRueda1; Matrix transRueda2; Matrix transRueda3; Matrix transRueda4;

//contenedor Matrix[] boneTransf;

Mucho ? Les explico. Las variables derotacin son para rotar el eje delantero, trasero y el chasis completo del auto. No hay mucha ciencia en esas variables. Las variablesModelBonesirven para hacer referencia a distintos bones del modelo, estos ya traen la informacion de los vertices que tienen asociados. Lasmatrices transRuedaXalmacenan la transformacion inicial de cada bone. Por ultimo, vamos a hacer una matriz que va a contener todas las nuevas transformaciones del esuqeleto. Vamos ahora, al codigo del metodo "Load", que deberia quedar como sigue:

public void load(ContentManager content) { modelo = content.Load<Model>("Recursos/autito");

//inicio los bones que voy a mover boneRueda1 = modelo.Bones["bone_rueda1"]; boneRueda2 = modelo.Bones["bone_rueda2"];

boneRueda3 = modelo.Bones["bone_rueda3"]; boneRueda4 = modelo.Bones["bone_rueda4"];

//guardo las transformaciones iniciales transRueda1 = boneRueda1.Transform; transRueda2 = boneRueda2.Transform; transRueda3 = boneRueda3.Tran sform; transRueda4 = boneRueda4.Transform;

//inicializo la matrz que va a guardar todos los cambios boneTransf = new Matrix[modelo.Bones.Count];

El codigo no es dificil, solo instanciamos los bones que queremos mover o transformar y guardamos la transformacion inicial. Ahora vamos a re escribir el metodo update deacurdo a las nuevas variables:

public void update() { KeyboardState estado = Keyboard.GetState();

if (estado.IsKeyDown(Keys.Up)) { rotacionDelantera = rotacionDelantera + 0.1f; } if (estado.IsKeyDown(Keys.Down )) { rotacionDelantera = rotacionDelantera - 0.1f;

if (estado.IsKeyDown(Keys.Left)) { rotacion += 0.1f; } if (estado.IsKeyDown (Keys.Right)) { rotacion -= 0.1f; } }

Por ultimo, el metodo dibujar quedaria como sigue:

public void dibujar(Matrix world, Matrix vista, Matrix proyeccion) { modelo.Root.Transform = world * Matrix.CreateRotationY( rotacion);

Matrix rotacionBonesDelanteros = Matrix.CreateRotationX(rotacionDelantera); Matrix rotacionBonesTraseros = Matrix.CreateRotationX(rotacionDelantera * 0.33f);

boneRueda1.Transform = transRueda1 * rotacionBonesDelanteros; boneRueda2.Transform = transRueda2 * rotacionBonesDelanteros;

boneRueda3.Transform = transRueda3 * rotacionBonesTraseros;

boneRueda4.Transform = transRueda4 * rotacionBonesTraseros;

modelo.CopyAbsoluteBoneTransformsTo(boneTransf);

foreach (ModelMesh mesh in modelo.Meshes) { foreach (BasicEffect efecto in mesh.Effects) { efecto.EnableDefaultLighting(); efecto.World = boneTransf[ mesh.ParentBone.Index]; efecto.View = vista; efecto.Projection = proyeccion; } mesh.Draw(); } }

Tampoco hay mucha ciencia, es solo analizar. Lo que hice fue crear una rotacion en el eje X para cada eje (el eje tracero es solo 1/3 de rapido que el delantero, para que vean la independencia de los bones), luego le aplique la transformacion a cada bone y copie las transformaciones finales a la matriz contenedora.

IMPORTANTE: si el proyecto no les compila, es porque tiene cacheada info vieja, eliminen el contenido de: MiMotor\obj\x86\Debug\Recursos

Tema 7 - como crear una camara y controlarla


No voy a seguir la linea de desarrollo de los otros tutoriales por 2 motivos: Primero, seguir tocando el codigo como lo venia haciendo va a ser mas confuso que educativo. Segundo, creo que si llegaron hasta este tutorial son capaces de entender el modelo orientado a objetos y agregar la camara a sus proyectos sin mayores problemas. Conceptos de la camara: Vi muchos papers respecto a las camaras, y la mayoria o eran muy dificiles o no cumplian los objetivos que queria. El ejemplo que presenta microsoft en msdn2 depende de un modelo o un "avatar" y no tiene movimiento arriba y abajo. Otros ejemplos que si tienen movimiento arriba y abajo tienen el siguiente problema: Si miro hacia arriba y luego hacia adelante, la camara viaja en esa direccion, cuando lo que yo quiero es que se camine hacia adelante mirando hacia arriba y no "nadar" en el espacio por asi decirlo. De todas formas voy a cubrir ese tipo de camara en este tutorial. Conceptos del mundo 3D repaso Recordemos que en XNA las cordenadas son como siguen:

Osea que: Si quiero ir hacia adelante, aumento Z. Si quiero girar sobre mi eje, aplico rotacion sobre Y.

Si quiero "volar" hacia arriba, aumento Y. ejemplo:

Variables: La camara sera la encargada de manejar las matrices view,world y projection. La camara va a tener una posicion, un objetivo y una definicion de que eje apunta arriba (eje Y) La camara va a manejar diferentes velocidades.

Codigo: Primero que nada, este codigo es de un proyecto personal y yo siempre programo en ingles como norma, pero voy a explicar todo en espaol:

#region using using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; #endregion

namespace FantasyCamera { public class camera { #region vars

//matrices private Matrix view; public Matrix View { get { return view; } } private Matrix projection; public Matrix Projection { get { return projection; } }

private Matrix world; public Matrix World { get { return world; } }

//end matrices private Vector3 position;

private float ratio; public float Ratio { set { ratio = value; } }

private float nearClip = 1.0f; private float farClip = 1000.0f; private float fov = MathHelper.PiOver4;

private float yaw; private float pitch;

private float rotationSpeed = 1f / 160f; private float forwardSpeed = 50f / 60f;

#endregion vars

public camera(int w, int h) { ratio = w/h; }

public void update() {

KeyboardState state = Keyboard.GetState();

Vector3 movement = Vector3.Zero;

//movement if (state.IsKeyDown(Keys.Up)) { movement.Z -= forwardSpeed; } if (state.IsKeyDown(Keys.Down) ) { movement.Z += forwardSpeed; } if (state.IsKeyDown(Keys.W)) { movement.Y += forwardSpeed; } if (state.IsKeyDown(Keys.S)) { movement.Y -= forwardSpeed; } //lock sides if (state.IsKeyDown(Keys.Left)) { yaw += rotationSpeed; } if (state.IsKeyDown(Keys.Right)) { yaw -= rotationSpeed;

//look up and down if (state.IsKeyDown(Keys.Q)) { pitch -= rotationSpeed; } if (state.IsKeyDown(Keys.E)) { pitch += rotationSpeed; }

Matrix rota tionMatrix = Matrix.CreateRotationY(yaw); Vector3.Transform(ref movement, ref rotationMatrix, out movement); position += movement;

// matrices view = Matrix.CreateLookAt(position, position + rotationMatrix.Forward + new Vector3(0,pitch,0), Vector3.Up); projection = Matrix.CreatePerspectiveFieldOfView(fov, ratio, nearClip, farClip); world = Matrix.Identity;

} } }

Explicacion:

//matrices private Matrix view; public Matrix View { get { return view; } }

Aca tenemos un ejemplo de como tratar una variable con visibilidad privada, observen que cuando uso "public" cambia la mayuscula. La variable ratio dice como dibujar los objetos dependiendo la resolucion. farClip y nearClip indican el alcance de la camara, no se dibujara lo que este a menos de 1 unidad y mas lejos de 1000 unidades. "fov" o Field of View o Campo de vision indica como vemos, piOver4 o pi/4 son 45 grados.

las variables yaw y pitch (no se si estos nombres estan bien) indican el movimiento hacia los lados y hacia arriba y abajo de la camara.

private float rotationSpeed = 1f / 160f; private float forwardSpeed = 50f / 60f;

Esas son faciles, son solo las variables de velocidad de rotacion y que tan rapido corremos.

public camera(int w, int h) { ratio = w/h; }

El constructor es facil, solo necesitan pasarle el alto y ancho de la pantalla, si usan monitores regulares, pueden pasarle por ejemplo 800x600. Lo que resta del codigo es facil, pero me voy a detener en la siguiente linea:

view = Matrix.CreateLookAt(position, position + rotationMatrix.Forward + new Vector3(0,pitch,0), Vector3.Up);

CreateLookAt recibe 3 valores, posicion de la camara, objetivo y el direccion arribe. Si ven, en objetivo esta "posicion + rotationMatrix.Forward + " un nuevo vector. Ese "nuevo vector" indica que se suma una rotacion sin desplazamiento hacia arriba y hacia abajo. Esto es para que la camara no salga flotando hacia arriba o abajo. Imaginen en el quake 1 si cuando miramos para arriba el personaje empezara a volar, malisimo. Ahora, para los que quieran estar en un ambiente submarino, remplacen el codigo como sigue:

Matrix rotationMatrix = Matrix.CreateRotationY(yaw) * Matrix.CreateRotationX(pitch); Vector3.Transform(ref movement, ref rotationMatrix, out movement); position += movement;

// matrices

view = Matrix.CreateLookAt(position, position + rotationMatrix.Forward, Vector3.Up); projection = Matrix.CreatePerspectiveFieldOfView(fov, ratio, nearClip, farClip); world = Matrix.Identity;

Cualquier duda pregunten.

NOTA Recuerden que cuando necesiten las matrices world view y projection, se las tienen que "pedir" a camera. Ejemplo si quieren dibujar un objeto: objeto.draw( instanciaCamara.World, instanciaCamara.View, instanciaCamara.Projection )