12. Rompe ladrillos ahora en 3D |
![]() |
![]() |
Videojuegos - Curso de Programación de juegos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Escrito por Vicengetorix | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() Usaremos, pues, el codigo del capitulo 11 (Rompe ladrillos 2D paso a paso) y lo modificaremos para que haga lo mismo en 3D. Tras eso incluiremos algun pequeño cambio para que, cambiando el punto de vista, aprovechemos un poco mas la proyeccion 3D. El cambio mas obvio es que antes pintabamos en pantalla un grafico cargado de disco para poner los ladrillos, la pelota y la paleta, y ahora necesitaremos los vertices de modelos representando los ladrillos, la paleta y la pelota. A tal efecto recuperaremos el codigo de la funcion GeneraLista() de los capitulos 9 (Listas, Texto y FPS) y 10 (Teclado, ratón y cámara), que al generar una lista que pinta un cubo nos sera util para dibujar los ladrillos y la paleta (escalando en el eje Y y el eje Z, ya lo veremos). Para dibujar la pelota usaremos la funcion auxSolidSphere(...) que ya hemos usado en los capitulos 9 y 10. Lo que si usaremos para los ladrillos de colores y la paleta seran texturas cargadas de disco. El resto de graficos (fondo y mensajes entre saques) seran en 2D como en la version anterior del programa. El borde de rebote (el borde del campo de juego) ahora no coincidira con el de la ventana, asi que habra que dibujarlo en el espacio 3D, para lo que simplemente usaremos una reticula de lineas blancas. El cambio mas importante en el codigo, pero menos visible mientras jugamos, es el cambio de coordenadas. Si, si, cambio de coordenadas: El campo de juego en 2D era de unos 600x600, que era lo que ocupaba el area cliente de la ventana. En 3D eso no seria practico, ya que con ese rango de coordenadas obligariamos a OpenGL a ampliar el rango de la comprobacion de profundidad y podria dar errores (habria que probarlo). En todo caso nosotros lo que haremos sera mantener las coordenadas antiguas (600x600) internamente, y a la hora de pintar en pantalla en 3D las convertiremos a las mismas coordenadas pero en un rango mas pequeño pero con numeros en coma flotante en vez de enteros. Para hacer esto nos bastara con una simple regla de tres (sencillo ¿no?): Tomaremos como referencia en ancho del campo de juego. Antes era el ancho del area cliente de la ventana contenido en la variable "rect.right", y ahora decidiremos que es 20. Asi pues para convertir cualquier coordenada del programa anterior al nuevo sistema en 3D bastara con multiplicarla por 20 y dividirla entre rect.right. La formula quedaria asi: " Nueva_coordenada = (Vieja_coordenada*20.0f)/rect.right " Para asegurarnos de que el resultado es en coma flotante y no se pierde precision se puede usar cast: " Nueva_coordenada = (double)(Vieja_coordenada*20.0f)/(double)rect.right " En el nuevo codigo incluiremos un timer (los vimos en el capitulo 9, en el apartado de FPS) para que cada cierto tiempo de juego cambie el punto de vista y asi animaremos mas el juego. Veremos alguna funcion nueva de OpenGL que no hemos visto (suavizado y culling) pero que se podrian no usar y el juego seria el mismo. Al final el resultado sera sencillo pero resulton, demostrando asi lo facil que es hacer un juego tu solo en casa, y no digamos si se juntan 4 amigos y le echan horas y ganas. Hechas las presentaciones pasamos al codigo, como es habitual, paso a paso. Recordad que partimos del juego en 2D del capitulo anterior (11). Primero, como siempre vemos los cambios en las definiciones globales. El primer cambio es incluir la biblioteca glAux para poder dibujar la bola sin complicarnos, para lo cual incluimos estas lineas.
y la cabecera desues de incluir opengl y glu.
Incuimos tambien los datos que usaremos para la iluminacion y el material, tal y como vimos en el capitulo 8 Iluminacion.
Eliminamos el indicador de textura de la bola y el identificador de lista para la bola, ya que la pintaremos con glAux y sin textura. Los indicadores de textura se usaran ahora para las texturas que aplicaremos a los ladrillos en 3D (como en el capitulo 7 Texturas).
Pondremos en coma flotante las variables bola_x y bola_y, y pal_x y pal_y, para que los calculos que haremos despues sean correctos y el compilador no se lie.
Dos nuevas variables para contener el alto y largo del ladrillo (y la paleta) pero en su version 3D para ser pintado (en las nuevas coordenadas).
Tres variables mas para la gestion de las vistas (desde donde se ve la escena) y la forma en que cambia la vista (ira cambiando segun se juega). La primera contendra el indice correspondiente a la vista actual (tendremos 7). La segunda es un indicador que un "timer" pondra a true para que la funcion Pinta() cambie la vista ( y luego ponga a false ). La tercera es un indicador que cambiaremos con la tecla "R" para poder dejar la vista fija o para que vaya cambiando al ritmo que diga el "timer" de antes.
Con estos cambios terminamos con la definiciones globales. (por lo demas iguales que en el programa en 2D del capitulo 11). Las funciones Genera Fuente(), Escribe(...) y CargaTextura(...) no se modifican. En todo caso, en esta ultima se puede cambiar el modo de aplicacion de la textura de DECAL a MODULATE. En mi PC se ve igual. La funcion GeneraLista() es la misma que en el capitulo 10 (Teclado, ratón y cámara), salvo porque el identificador de la lista es ahora la variable Ladrillo. Con la funcion glScalef achataremos el cubo en 1 o 2 de los ejes para obtener la forma deseada. La funcion Pinta(), si me lo permitis, la dejaremos para lo ultimo. Despues de ver el resto del programa tendra mas sentido todo. La funcion IniciaGL() tendra algun cambio. El primero es habilitar el test de profundidad, que para el programa en 2D lo habia deshabilitado.
Ahora van una serie de lineas con cosas que no hemos visto hasta ahora. Se podrian quitar y el programa cambiaria poco. Lo que hacen es decir a OpenGL que pinte las cosas en pantalla suavizadas, sin escalones (esto se llama "antialiasing" que traducido al cristiano es eliminar los escalones de las lineas al pintarlas en la pantalla). El resultado de estas lineas no esta muy claro porque las implementaciones que se hacen de este tema segun la tarjeta grafica o el driver son por asi decirlo, "variopintas". Se hace a nivel de punto, linea y poligono. En mi PC el de poligono no hace nada pero a lo mejor en el vuestro funciona. Las lineas si que se pintan mejor. Suerte.
Las siguientes dos lineas indican a OpenGL que parte de un objeto debe pintar. Aqui si nos debemos detener. El concepto de OpenGL que vamos a ver ahora es el Culling (del ingles desechar). En un objeto 3D cerrado como por ejemplo un cubo, las caras traseras no se ven porque estan tapadas por las delanteras. Esto OpenGL lo resueleve con el test de profundidad y el buffer de profundidad como vimos. Por otra parte, vimos tambien el orden en que debemos indicar a OpenGL los vertices de un poligono; en orden inverso a las agujas del reloj. ¿Recordamos estas dos cosas? son importantes. Pues bien si un poligono es pintado, desde el punto de vista, en pantalla en orden inverso a las agujas del reloj, OpenGL lo considera una cara delantera, lo vemos por delante. Si lo pinta en el orden de las agujas del reloj, considerara que lo estamos viendo por detras y es una cara trasera. Dicho esto, con el culling en OpenGL podemos indicarle que pinte todas las caras (las traseras no se veran por el test de profundidad pero OpenGL hara el intento), las frontales, las traseras o ninguna. Esto se hace con la funcion glCullFace(...) con los parametros GL_FRONT, GL_BACK o GL_FRONT_AND_BACK. Con uno no pintara las caras delanteras, con el otro las traseras y con el tercero ni traseras ni delanteras (no pintara nada). Luego habilitas el culling con la funcion glEnable(GL_CULL_FACE), al estilo OpenGL. En el programa vamos a deshabilitar el pintado de caras traseras para ahorrar tiempo de calculo. Si tuvieramos un simple cuadrado en el espacio 3D y quisieramos verlo dar vueltas, tendriamos que deshabilitar el culling antes de pintarlo.
La poyeccion en este caso, ya no sera ortogonal, asi que cambio la proyeccion y, ya puestos, le indico la situacion de la camara (el punto de vista y a donde mira) al empezar el programa.
Ahora calculo el largo del ladrillo, para las coordenadas en 2D antiguas y para las nuevas que me van a servir para pintar en 3D.
Y hago lo mismo con el alto del ladrillo.
Y la posicion de la paleta en medio del campo de juego.
En la parte en que se cargan las texturas cambiaremos las texturas a cargar segun el programa. No nos ocuparemos de eso aqui, en el codigo queda claro. El ultimo añadido a la funcion IniciaGL() es la parte en que habilito la iluminacion y la luz 0 de openGL. Lo mismo que hicimos en el capitulo 8, Iluminacion. En este programa solo usaremos 1 luz, pero se pueden usar 8.
En la funcion ProcesaMensajes(...) haremos cambios tambien. Definiremos un "timer" como en el capitulo 9 (Listas, Texto y FPS) en la parte de FPS, pero esta vez para cambiar la vista del campo de juego cada 15 segundos. Es una forma de añadir dificultad al jueguito. Solo cambiaremos el indicador CambiaVista a true. Luego, en la funcion Pinta() nos encargaremos de ver como esta y si esta a true cambiar la vista y ponerlo otra vez a false.
En esta funcion tengo que tener en cuenta las texturas que he creado ya que aqui las destruyo. Normalmente habria una funcion para cuando termina el programa que haria estas cosas pero me parece mas didactico simplificar el codigo. La funcion CreaVentana() no cambia. En la funcion principal, WinMain(...) añadiremos la creacion del "timer" unicamente.
Vamos por fin con el autentico corazon de nuestro programa, la funcion Pinta(). El primer cambio es al pintar el fondo y el logo. antes solo debia pintarlos, ahora debo poner la proyeccion ortogonal antes por que ahora lo normal es que este en perspectiva. Asi que guardo primero en la pila de matrices la proyeccion para restaurarla cuando termine de pintar en ortogonal. Tambien deshabilito el test de profundidad y la iluminacion. Luego cambio la proyeccion y despues pinto el fondo y el logo.
Despues pinto como hacia antes y luego dejo todo como estaba:
Tras esto toca pintar los bordes del campo, que como comentamos al principio ya no son los bordes de la ventana. Lo haremos con simples lineas sin textura (las dehabilitaremos para pintarlas). Algunas de ellas las pintaremos en un bucle for para ahorrar lineas de codigo. Despues de pintarlas habilitamos de nuevo las texturas.
Luego viene la parte en la que pintamos el muro. El metodo sera el mismo que en 2D, salvo que pintamos el ladrillo en 3D, y con las nuevas coordenadas para pintar en pantalla. Ya vimos como vamos a cambiar las coordenadas para pintar, al principio del capitulo.
Pues eso mismo hacemos con la colocacion inicial de las filas del muro. Conviene aclarar que en 2D las coordenadas 0,0 de cada dibujo (ladrillos y bola), eran la esquina superior izquierda del dibujo. Si ponias un dibujo en las coordenadas de pantalla 20,30; aparecia en dibujo con la esquina superior izquierda en las coordenadas 20,30. Ahora, en 3D, las coordenadas 0,0 del modelo estan en el centro del modelo. Por esta razon la primera coordenada de la siguiente funcion no es 0 (el borde izquierdo), si no la mitad de un ladrillo.
Como veis, la conversion de coordenadas no es mas que aplicar la formula.
La tercera coordenada seguira siendo 0; vamos a dibujar en 3D pero todo en un plano (Z=0). Luego, a la hora de pintar el ladrillo, tambien habra cambios. Pinto con la "display list" del ladrillo que en realidad es un cubo, por eso antes reduzco las coordenadas del eje Y con la funcion glScale(..), que si mal no recuerdo, no hemos usado hasta ahora. Esta funcion simplemente escala, multiplica las coordenadas por el factor que indicamos en la funcion. Si indicas 1,1,1 no hara nada. 0.5,0.5,0.5 reducira el modelo pintado despues a la mitad. 2,2,2 multiplicaras por 2 las subsiguientes coordenadas de vertices y aumentaras el objeto. En nuestro caso solo queremos achatar el cubo para hacer nuestro ladrillo, asi que haremos un tercio la altura del ladrillo, el eje Y (1, 0.3333, 1). Segun los manuales de OpenGL esta funcion penaliza el rendimiento. Lo correcto seria que el modelo tenga su tamaño definitivo pero esto me sirve de pretexto para explicar esta funcion y no me apetecia cambiar las coordenadas del cubo.
Tras esto hay codigo con una funcion nueva de OpenGL. Lo que hacemos, despues de pintar el ladrillo con la textura, es pintar un borde alrededor del mismo para que cuando se junten 2 ladrillos, se vea la separacion. Ademas esto suavizara los bordes de los ladrillos. Para esta operacion usaremos la funcion glPolygonMode(...) que lo que hace es decir a OpenGL la forma que debe pintar los poligonos, rellenos, las lineas del borde o los puntos de los vertices. Los parametros son 2. El primero indica a que caras se aplicara el efecto. Puede ser: GL_FRONT_AND_BACK, GL_FRONT o GL_BACK, segun sea a las caras frontales, las caras traseras o las dos. El segundo parametro indica que efecto se aplica: GL_POINT, GL_LINE o GL_FILL, segun se pinten solo los puntos de los vertices, las lineas de las aristas o el poligono con relleno (lo normal). En el seguiente codigo ponemos esto en practica, tras pintar el ladrillo normal.
Tenemos que restalecer la matriz de modelo para que no achate mas de lo que debe.
El ultimo cambio en la zona de pintar el muro es a la hora de desplazarse a la derecha para pintar el siguiente ladrillo de la fila. Como hablamos de pintar el desplazamiento sera el largo del ladrillo en las nuevas coordenadas de pantalla.
Despues del muro, coloco y pinto la paleta, tambien acomodandolo a las nuevas coordenadas de la escena 3D y con el borde alrededor de las caras como hicimos con los ladrillos.
Mas tarde, cuando pintamos la rayita blanca que indica la direccion de saque de la bola, tambien debemos transformar las coordenadas.
Tras esto, los siguientes cambios son a la hora de pintar la bola. Aparte de convertir las coordenadas de la bola a las nuevas coordenadas 3D, teniendo encuenta que el la coordenada 0,0 es en el centro del modelo, deshabilito texturas y defino el material con el que pintar. En realidad este material sera con el que se pinte todo lo que no tenga textura, ya que no cambio mas de material en todo el programa (la bola y la reticula del borde del campo). La bola sera pintada con la funcion auxSolidSphere(...) que ya vimos en anteriores capitulos, con un radio de 8 (coorenadas antiguas en 2D) pero convertido a nuevas coordenadas 3D.
Ahora, en la situacion de presionar las flechas izquierda o derecha y que se mueva la paleta, incluimos el movimiento del punto de vista, en el caso de ser la vista 6 del camo (la ultima). Esta vista sera del tipo "primera persona", la camara estara situada al lado de la paleta y se movera con ella de forma solidaria. Mirara siempre hacia arriba. Nos dara la impresion mientras jugamos de estar sentados encima de la paleta. Por eso debemos mover la camara (o punto de vista) a la vez que la paleta en caso se estar usando esta vista (Vista==6). Primero creamos unas variables para guardar el calculo de coordenadas a 3D. Sera asi mas facil de entender el codigo.
Y luego, dentro de cada if de presionar tecla, ademas de el codigo de mover la paleta, incluimos el codigo para cambiar el punto de vista (si, si, es un poco chapuza repetir dos veces el mismo codigo pero hay veces que uno se cansa y lo deja como sea).
Despues de esto solo quedan cambios al final de la funcion pinta, tendremos que poner proyeccion ortogonal a los carteles de fin de partida y de nuevo muro, y el tema de ir cambiando de vista cada cierto tiempo o con la tecla "V". Toda la parte del programa para el choque de la bola con los bordes, la paleta y los ladrillos, y el rebote en cada caso, se queda como estaba, con las mismas coordenadas del programa en 2D. Antes de pintar los carteles (si fuera el caso) cambio la proyeccion, deshabilito la iluminacion y el test de profundidad.
Y tras pintarlos dejo todo como estaba.
Gestiono la tecla "R" del mismo modo que hacia con la tecla "F" (para ver o no los FPS). Servira para cambiar el indicador "RotacionVistas" que sera el que indique si se cambia la vista del juego cada 15 segundos o la vista estara fija.
Uso tambien el indicador para escribir en pantalla un mensaje cuando la vista esta fija.
Por fin ya solo nos queda el codigo que cambia de vista. Sin este codigo el programa funcionaria pero solo con la vista inicial, sin cambiarla. La vista cambiara en dos situaciones. Si presiono la tecla "V" (se gestionara de forma analoga a las teclas "F" y "R"), y si el "timer" que definimos en la funcion WinMain(...) y gestonamos en la funcion ProcesaMensajes(...) pone el indicador "CambiaVista" a true y el indicador "RotacionVistas" es true tambien.
Dentro de este if , actualizamos la variable "Vista" con la siguiente vista (en el rango de 0 a 6)...
... preparamos la matriz de proyeccion para el cambio de vista ...
... pongo la camara adecuada en base a la variable "Vista" ...
... y termino poniendo la matriz de modelo, pongo "CambiaVista" a false (hasta que el timer la vuelva a poner a true) y me aseguro que la tecla "V" solo hace su funcion una vez por pulsacion. Termino el if que empezamos antes.
Terminamos la funcion Pinta() "suicheando" back buffer y front buffer como siempre. Terminado el programa, el aspecto sera asi: ![]() El codigo completo es este:  usw12.cpp El juego se puede descargar compilado y listo para jugarlo en un ZIP que incluye el codigo fuente: Muro3D.zip Bien, si habeis seguido el curso paso a paso y habeis llegado hasta aqui, quiere decir que habeis aprendido a programar lo basico de OpenGL para hacer juegos simples en 2D y 3D, y habeis (espero) entendido, linea a linea, el codigo de un juego en 2D y otro en 3D. Se puede afirmar que sabeis ya algo de programacion de juegos. Espero que esto os de animo para seguir aprendiendo e investigando. En todo caso el curso de programacion de juegos de UnSitioWeb continua con mas temas interesantes y avanzados. ¡Sólo los usuarios registrados pueden escribir comentarios!
3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved." |