Documente Academic
Documente Profesional
Documente Cultură
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:
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:
} }
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:
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.
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:
namespace MiMotor { public class MiGame : Game { //variables GraphicsDeviceManager miDispositivoGrafico; ContentManager miContenido;
} } }
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:
Este codigo es muy simple, es importante que main tenga la M (mayuscula). Para terminar, el codigo deberia quedar como sigue:
using MiMotor;
Si hicieron todo bien, al ejecutarlo deberian ver una linda pantalla naranja.
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 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
} }
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:
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:
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.
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;
... public class MiGame : Game { //variables GraphicsDeviceManager miDispositivoGrafico; ContentManager miContenido; Vector3 camara; ...
//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.
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 System;
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:
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:
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:
objeto.di bujar(world,view,projection );
base.Draw(gameTime); }
Si todo salio bien, van a poder ver un lindo modelo dibujado en la pantalla.
//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:
{ 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.
Ya tenemos nuestro metodo update, ahora solo cambios el modelo de nuestro objeto, cambiamos pilonpor autito (si ese es el asset del modelo). Quedaria:
Ahora vamos a reescribir el metodo para dibujar de la clase, solo vamos a agregarle la rotacion:
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:
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:
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);
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.
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:
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:
//movimiento protected float rotacionDelantera = 0.0f; protected float rotacionTrasera = 0.0f; protected float rotacion = 0.0f;
//transformaciones iniciales Matrix transRueda1; Matrix transRueda2; Matrix transRueda3; Matrix transRueda4;
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:
//inicio los bones que voy a mover boneRueda1 = modelo.Bones["bone_rueda1"]; boneRueda2 = modelo.Bones["bone_rueda2"];
//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 dibujar(Matrix world, Matrix vista, Matrix proyeccion) { modelo.Root.Transform = world * Matrix.CreateRotationY( rotacion);
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
Osea que: Si quiero ir hacia adelante, aumento Z. Si quiero girar sobre mi eje, aplico rotacion sobre Y.
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
//matrices private Matrix view; public Matrix View { get { return view; } } private Matrix projection; public Matrix Projection { get { return projection; } }
private float nearClip = 1.0f; private float farClip = 1000.0f; private float fov = MathHelper.PiOver4;
#endregion vars
//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;
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.
Esas son faciles, son solo las variables de velocidad de rotacion y que tan rapido corremos.
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:
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;
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 )