¿Recordáis el juego Tomb Rider? Si habéis jugado, os acordaréis seguro de que como jugadores movíamos a Lara Croft, viéndola desde una prespectiva «trasera», es decir, que la cámara se movía siempre por detrás de ella. Esto es lo que se llama en desarrollo de videojuegos una cámara en 3a persona, o cámara persecutória (de «chase cam» en inglés, que sin duda queda mejor dicho así :-P). Dando por supuesto que habéis desarrollado una cámara para un juego 3D de XNA antes, añadir esta funcionalidad a un juego es sencillo, de todos modos vamos a repasar los conceptos de las cámaras 3D en XNA.
Si un juego 2D es muy parecido a los clásicos dibujos animados, en que unos dibujos se superponían sobre otros, sobre una película transparente, para generar una composición final, un juego 3D es más bien parecido al rodaje de una película, mediante un objeto cámara. La información de la cámara se almacena en las matrices de vista y proyección. La vista contiene información acerca de la posición de la cámara, la proyección define básicamete lo que la cámara puede ver. Más tarde el contenido de esta proyección es lo que la targeta gráfica transformará en una imagen 2D de la pantalla (sí. qué desengaño verdad? todavía no existen las pantallas 3D «reales», y están lejos de existir ahora mismo…). Ahh las matrices… cómo os iban en el instituto? las vísteis en la universidad? os gustaban, eh pillines? pues bueno, aquí lo bueno es que XNA nos facilita enormemente el trabajo con las matrices!!
Crear la vista
Para crear la matriz vista tenemos un método estático llamado Matrix.CreateLookAt, fácil verdad? Los parámetros que debemos pasarle son:
- Posición de la cámara, en un Vector 3D.
- Objetivo de la cámara (o dónde la cámara está «mirando»), en un Vector 3D.
- Vector que indica dónde está la posición «arriba» (sí, en un mundo 3D el concepto de arriba y abajo hay que definirlos, estilo barrio sésamo :-P), en un Vector 3D.
Esto en el código se traduce de la siguiente forma:
1: private void IniciarVista()
2: {
3: posicionCamara = new Vector3(0.0f, 900.0f, 600.0f);
4: mirar = new Vector3(0.0f, 10.0f, 0.0f);
5: arriba = Vector3.Up;
6:
7: Vista = Matrix.CreateLookAt(posicionCamara, mirar, arriba);
8: }
Donde como he comentado, posicionCamara, mirar y arriba son variables de la clase Camara de tipo Vector3, y Vista es una matriz.
Crear la proyección
Crear la proyección de una cámara no es mucho más difícil… existe otro método estático en Matrix que se llama Matrix.CreatePrespectiveFieldOfView. Este acepta los siguientes parámetros:
- Ángulo de la cámara en radianes, normalmente se pone π/4 (Pi/4).
- Proporción, o “aspect ratio” (otra vez el inglés es más claro que el castellano…).
- Plano cercano: distancia a partir de la cual la cámara puede “ver”.
- Plano lejano: distancia hasta la cual la cámara puede “ver).
Todos estos parámetros son de tipo float. La matriz proyección sirve a XNA para definir el frustum de la cámara, que es el espacio visible por la cámara. Esta imagen vale más que mil palabras…:
¿Porqué ponemos un plano cercano y uno lejano? ¿no sería mejor que se viera todo? Quizá sí… pero nos arriesgaríamos a tener serios problemas de rendimiento…
Esto en código se traduce en lo siguiente:
1: private void IniciarProyeccion()
2: {
3: float nearClip = 10.0f;
4: float farClip = 10000.0f;
5: float aspectRatio;
6:
7: aspectRatio = (float)Configuracion.graficos.Viewport.Width /
8: (float)Configuracion.graficos.Viewport.Height;
9:
10: Proyeccion = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, nearClip, farClip);
11: }
En casi cualquier cámara 3D, el código de inicialización va a ser muy parecido a estos dos métodos que os he descrito, IniciarVista() e IniciarProyeccion(). Con eso ya podríamos renderizar una escena 3D como esta:
Cámara en 3a persona
Vale, tenemos una fantástica cámara 3D… ¿ahora cómo hacemos para que vaya detrás de nuestro personaje del juego? Ya os lo debéis imaginar… simplemente hay que hacer que la posición de la cámara, el view, esté por detrás del jugador. Esto lo haremos con el método siguiente, que se llamará constantemente, a medida que vayamos moviendo al jugador:
1: public Matrix UpDateCam(Matrix worldJugador)
2: {
3: float distanciaCamara = 100.0f;
4:
5: posicionCamara = (worldJugador.Translation - (worldJugador.Backward * distanciaCamara))
6: + new Vector3(0.0f, 200.0f, 0.0f);
7:
8: Vista = Matrix.CreateLookAt(posicionCamara, worldJugador.Translation, arriba);
9:
10: return Vista;
11: }
Básicamente lo que estamos haciendo es calculando la posición de la cámara a partir de la matriz que representa la posición del jugador en el mundo. Usamos el método Translation de la instancia de la clase que representa la posición del jugador, y otra propiedad muy útil de la matriz: Back, que representa el vector que queda detrás de la posición del jugador, a partir de su matriz mundo. A ello, le sumamos un nuevo Vector3, que nos sirve para subir la cámara, para obtener una mejor prespectiva del juego.
¡Y esto es todo amigos! Así es como quedaría la cámara aplicada al robot modelado por Jordi Giménez que vengo usando desde hace algunos tutoriales:
El código completo de la cámara
El código completo de la cámara sería este:
1: using Microsoft.Xna.Framework;
2: using Colisiones3D;
3:
4: namespace PruebaColisiones3D
5: {
6: public class Camara
7: {
8: public Matrix Vista;
9: public Matrix Proyeccion;
10:
11: Vector3 posicionCamara;
12: Vector3 mirar;
13: Vector3 arriba;
14:
15: public Camara()
16: {
17: this.IniciarVista();
18: this.IniciarProyeccion();
19: }
20:
21: private void IniciarVista()
22: {
23: posicionCamara = new Vector3(0.0f, 900.0f, 600.0f);
24: mirar = new Vector3(0.0f, 10.0f, 0.0f);
25: arriba = Vector3.Up;
26:
27: Vista = Matrix.CreateLookAt(posicionCamara, mirar, arriba);
28: }
29:
30: private void IniciarProyeccion()
31: {
32: float nearClip = 10.0f;
33: float farClip = 10000.0f;
34: float aspectRatio;
35:
36: aspectRatio = (float)Configuracion.graficos.Viewport.Width /
37: (float)Configuracion.graficos.Viewport.Height;
38:
39: Proyeccion = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, nearClip, farClip);
40: }
41:
42: public Matrix UpDateCam(Matrix worldJugador)
43: {
44: float distanciaCamara = 100.0f;
45:
46: posicionCamara = (worldJugador.Translation - (worldJugador.Backward * distanciaCamara))
47: + new Vector3(0.0f, 200.0f, 0.0f);
48:
49: Vista = Matrix.CreateLookAt(posicionCamara, worldJugador.Translation, arriba);
50:
51: return Vista;
52: }
53:
54: }
55: }