"Ningún juego dura tanto hasta que te lo terminas como el que te programas tú mismo,
ninguno te absorbe tanto y ninguno te produce tanta satisfacción cuando lo has acabado"
Programacion de videojuegos
Inicio Curso de programación de juegos 11. Rompe ladrillos 2D paso a paso
Sábado 24 de Junio del 2017

Menu principal
Colaborar (con PayPal)

Para continuar con el trabajo de esta Web y poder pagar el hosting, viene bien la ayuda que sea. Gracias a todos.

Importe: 

Ultimas descargas
19.Jan

Clase que permite dibujar texto en OpenGL con mucha facilidad.Usa FreeType2.Para ver que hace y c...


11. Rompe ladrillos 2D paso a paso Imprimir Correo electrónico
Videojuegos - Curso de Programación de juegos
Escrito por Vicengetorix   
Este capitulo es una especie de check point del curso para comprobar que se puede hacer con lo que ya sabemos.
Haremos paso a paso, el tipico juego de romper ladrillos (Arkanoid), solo usando 2D, para comprobar de lo que somos capaces y el resultado nos de animos para seguir. Aviso: el tema sera larguito, pero satisfactorio.



Ademas, he comprobado en foros que hay bastante interes en hacer juegos 2D y, tal vez, quien siga el curso no vea claro como hacerlo con lo que hemos visto. Comprobaremos como para hacer un juego 2D no hace falta exprimir el OpenGL. Simplemente inicializaremos, cargaremos los graficos, usaremos solo proyeccion ortogonal, y plantaremos los graficos en pantalla donde nos convenga. El resto de la programacion no es especifica de OpenGL, es simplemente C (C++ lo usaremos poco o nada de momento).

Aviso que el codigo del juego no es el mas estructurado del mundo ni el mas optimizado, simplemente funciona y esta muy comentado para que quien quiera seguirlo no se pierda y sepa en cada momento que hace cada linea de codigo y cada variable.

Antes de entrar a ver el codigo vamos a ver conceptos que usaremos para el juego y que luego veremos como se implementan.

Primero donde guardamos los datos del muro, las posiciones de los ladrillos. Lo guardaremos en un simple array con tantas posiciones como posibles ladrillos habra en el muro. Un ejemplo. En un muro hubiera 4 filas de posibles ladrillos y en cada fila hay 10 ladrillos, el array sera de 40 posiciones (4*10), si fueran 10 filas serian 100 posiciones ( 10*10) y asi sucesivamente.
Una vez que decidimos cual es el maximo de filas de ladrillos, definimos el array. A la hora de pintar el muro calcularemos las coordenadas en base a la posicion en el array. El tipo del array sera un simple int y el significado sera simplemente que un 0 sera que no hay ladrillo en esa posicion (un hueco en el muro) y otro numero correspondera al tipo (en nuestro programa solo cambia el color) de ladrillo a pintar.
Todos los niveles (muros) estaran definidos en un array de 2 dimensiones en el que la primera sera la pantalla o nivel y la segunda dimension las posiciones de los ladrillos de cada nivel. De este array se copiara cada nivel a otro array de trabajo, de 1 sola dimension, cuando empiece el juego (nivel 0) o cada vez que cambiemos de pantalla.

Otro concepto es el del movimiento de la bola. Lo haremos sumando a la posicion de la bola un vector de movimiento que calcularemos en base al angulo de saque y que luego cambiaremos con los rebotes simplemente cambiando el signo de la componente del vector de movimiento del lado que choca (Ejemplo: si choca con el borde derecho o izquierdo de pantalla cambiamos de signo la componente X del vector).

Otro tema muuuuy importante es el de los choques. En este caso tendremos que controlar el choque de la bola con la paleta y con los ladrillos del muro. El tema de la deteccion de choques es un tema peliagudo en los juegos. Nosotros no nos vamos a complicar.
Lo que haremos sera comprobar en el eje X, si la bola esta dentro del rango de coordenadas del ladrillo (o la paleta); si es asi haremos la misma comprobacion en el eje Y. Si las dos comprobaciones son positivas es que hay choque.
Para comprobar si el choque es contra un lateral del ladrillo o por arriba o abajo (para rebotar en un eje o en otro) lo que haremos sera hacer la misma comprobacion de antes pero restando el vector de movimiento a la pelota (la posicion en que estaba antes). Si en la posicion inmediatamente anterior estaba fuera del rango del ladrillo en el eje X, es que el choque es contra un lado vertical y cambiaremos de signo la componente X del vector de movimiento. Haremos lo mismo con la componente Y en cuyo caso el choque habra sido contra en lado horizontal del ladrillo y cambimos de signo la componente Y del vercor de movimiento de la pelota.

Los graficos que usaremos son sencillos. Yo no soy muy habil en esa materia pero en la red hay muchas cosas interesantes que podemos usar como graficos o programas que con muy poco nos hacen parecer expertos.
Tan solo usaremos estos graficos: la bola, la paleta, los ladrillos (5 colores), el fondo, el logo de UnSitioWeb y tres graficos hechos rapidamente con un programa de graficos vectoriales para la partida perdida, ganada y cambio de nivel. 
Puedes encontrar todos los graficos necesarios en el fichero ZIP descargable al final del capitulo. Contiene tambien el programa compilado y el codigo.

Ya nos resta solo empezar con el codigo.
Respecto a los capitulos anteriores he quitado buena parte, ya que todo lo relativo a 3D e iluminacion no tiene sentido en este juego.

Primero como siempre veremos las definiciones globales. (En esta ocasion completas).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
// Para cargar el icono del programa 
// como recurso.
#define IDI_USW 107
// Tengo que definir esta variable a mas de 0x0400
// para que pueda usar WM_MOUSEWHEEL y TrackMouseEvent,
// si no, no la reconoce (ver Winuser.h)
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif
 
// Defino mi propio numero PI
// para el calculo trigonometrico.
#define PI 3.14159265358
 
// Estas directivas del preprocesador le dicen a el enlazador
// (linker o linkador o ...) que incluya estas librerias.
// Esto se puede hacer en la configuracion del proyecto en
// el Visual C++ pero esto me parece mas claro.
// Biblioteca de OpenGL
#pragma comment( lib, "openGL32.lib" )
// Biblioteca Glu32 (funciones GLU)
#pragma comment( lib, "glu32.lib" )
// Biblioteca Corona para graficos
#pragma comment( lib, "corona.lib" )
 
// Libreria standard de entrada y salida.
#include <stdio.h>
// Fichero de inclusion para programas Windows.
#include <windows.h>
// Fichero de inclusion para funciones de hora.
#include <time.h>
// Fichero de inclusion para argumetos tipo "prinf".
#include <stdarg.h>
// Fichero de inclusion para funciones matematicas.
#include <math.h>
 
// Los ficheros de inclusion de OpenGL.
#include <GL/gl.h>
// Estrictamente solo es necesario el primero.
// El segundo es de funciones utiles que se podrian
// hacer de otra manera. No nos vamos a complicar la
// vida y la usaremos.
#include <GL/glu.h>
 
// Incluiremos las funciones de la libreria Corona.
#include "corona/include/corona.h"
 
// Para guardar el identificador del programa
HINSTANCE IdAplicacion;
// Para guardar el identificador de la ventana
HWND IdVentana;
 
// Device Context para conectar la ventana con OpenGL.
HDC DevContex;
HGLRC OGLrc; // Render Context para la ventana OpenGL.
 
// Para saber si el programa está activo
bool PROGRAMA_ACTIVO = true;
 
// Lo que dice el nombre de las variables
int AnchoVentana = 606;
int AltoVentana = 600;
 
// Definimos un rectangulo (programacion windows)
// lo usaremos para almacenar el area cliente de nuestra ventana
RECT rect;
// Para los id's de texturas que genera OpenGL
// para nuestro programa.
GLuint tex_bola, tex_usw, tex_paleta, tex_nivel;
GLuint tex_fondo, tex_felicidades, tex_bahh;
GLuint tex_ladrillo[5];
 
// Variables de tipo unsigned int para guardar
// el identificativo de las "display list"
GLuint Ladrillo, Bola;
 
// Variable de tipo unsigned int para guardar
// el identificativo base de las "display list"
// que generara para almacenar las letras.
// A partir de este, los siguientes, en orden,
// seran usados en estricto orden.
GLuint ListaFuente=0;
 
// Variable para guardar nuestro calculo
// de FPS reales
double FPS_reales;
// Variable donde ponemos los FPS a los que
// queremos que vaya nuestro programa.
int FPS_que_queremos=200;
 
// Matriz que almacena el estado del teclado.
bool Teclas[256];
// Variable para almacenar el ultimo caracter pulsado.
unsigned char Letra;
// Indicador de si son visibles los FPS en pantalla.
bool pintaFPS=false;
// Indicador de si hemos levantado la tecla.
bool LevantaTecla=true;
 
// Estructura para los datos del raton que vamos a usar.
// Observad como guardamos la coordenada del raton actual
// y la del frame anterior.
struct raton
{
int prev_x;
int prev_y;
int rueda;
int x;
int y;
bool izquierdo;
} Raton;
 
// Estructura necesaria para controlar si el raton sale de
// nuestra ventana con la funcion TrackMouseEvent.
TRACKMOUSEEVENT tme;
 
// Dos arrays que usaremos para simular las funciones
// seno y coseno de forma precalculada.
// En cada una de las casillas del array precalcularemos
// el seno o coseno correspondiente y luego podremos
// usar el contenido de la casilla del array como una
// funcion, siempre que el angulo sea un entero entre
// 0 y 359.
double seno[360], coseno[360];
 
// Definicion del numero de posiciones posibles para
// ladrillos de cada nivel (la capacidad del array
// que los contiene).
// Asi queda el codigo mas claro y es mas facil añadir
// o quitar filas de ladrillos, solo cambiaremos este
// valor y añadiremos los datos al array. El resto del
// codigo no lo tocariamos.
#define NUMERO_LADRILLOS 100
// El array con los datos de los muros de
// cada nivel. El array es de 2 dimensiones:
// La primera es para el numero de niveles,
// la segunda para los ladrillos de cada nivel.
// donde hay un 1 o mas, hay ladrillo, los 0 son huecos
// en el muro.
 
// Hay 6 niveles definidos (del 0 al 5),
// pero se pueden definir mas.
int muro[][NUMERO_LADRILLOS] ={
 
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,0,0,0,
0,0,1,5,0,0,5,1,0,0,
0,0,1,0,0,0,0,1,0,0,
0,0,1,0,0,0,0,1,0,0,
0,0,1,5,0,0,5,1,0,0,
0,0,0,1,1,1,1,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
 
0,0,0,4,4,4,0,0,0,0,
0,0,0,4,0,0,0,0,0,0,
3,0,3,4,4,4,0,0,0,0,
3,0,3,0,0,4,0,0,0,0,
3,0,3,4,4,4,0,0,0,0,
3,0,3,0,0,5,0,0,0,5,
0,3,0,0,0,5,0,0,0,5,
0,0,0,0,0,5,0,5,0,5,
0,0,0,0,0,5,0,5,0,5,
0,0,0,0,0,0,5,0,5,0,
 
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,1,1,1,1,1,1,0,0,
1,1,1,1,0,0,1,1,1,1,
2,3,4,5,0,0,5,4,3,2,
2,3,4,5,0,0,5,4,3,2,
1,1,1,1,0,0,1,1,1,1,
0,0,1,1,1,1,1,1,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
 
4,0,0,0,5,5,0,0,0,4,
0,4,0,0,5,5,0,0,4,0,
0,0,4,1,1,1,1,4,0,0,
0,0,1,4,0,0,4,1,0,0,
5,5,1,0,4,4,0,1,5,5,
5,5,1,0,4,4,0,1,5,5,
0,0,1,4,0,0,4,1,0,0,
0,0,4,1,1,1,1,4,0,0,
0,4,0,0,5,5,0,0,4,0,
4,0,0,0,5,5,0,0,0,4,
 
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,1,1,3,3,1,1,0,0,
1,1,1,1,3,3,1,1,1,1,
2,2,0,0,3,3,0,0,2,2,
2,2,0,0,3,3,0,0,2,2,
1,1,1,1,3,3,1,1,1,1,
0,0,1,1,3,3,1,1,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
 
5,4,3,2,1,1,2,3,4,5,
1,2,3,4,5,5,4,3,2,1,
5,4,3,2,1,1,2,3,4,5,
1,2,3,4,5,5,4,3,2,1,
5,4,3,2,1,1,2,3,4,5,
1,2,3,4,5,5,4,3,2,1,
5,4,3,2,1,1,2,3,4,5,
1,2,3,4,5,5,4,3,2,1,
5,4,3,2,1,1,2,3,4,5,
1,2,3,4,5,5,4,3,2,1
};
 
// Variable para controlar
// el nivel. Se inicializa a 0.
int Nivel = 0;
// Definicion del indice de nivel maximo.
// Si definimos mas habria que aumentarlo.
#define MAX_NIVEL 5
 
// Define el numero de ladrillos por fila
#define Por_fila 10
 
// Este array es igual que el anterior, pero
// con una sola dimension para el nivel actual,
// es el que usaremos en el bucle principal.
// Antes de empezar partida lo cargamos con los datos
// del array anterior, Ya que durante el juego se iran
// cambiando los no 0's (ladrillos) por 0's (huecos).
int ladrillos[NUMERO_LADRILLOS];
 
// Variable para guardar el numero de
// ladrillos durante la partida.
// Cuando el numero de ladrillos sea
// 0 habremos terminado con el muro
// y terminado el nivel.
int NLadrillos;
 
// Variables para las coordenadas de la bola
double bola_x, bola_y;
// Variables para las coordenadas de la paleta.
// La X inicial la cambiaremos luego para centrarla
double pal_x = 200, pal_y = 500;
// Variable para la direccion de la paleta.
// 0 parada, 1 a la derecha y -1 a la izquierda.
// La usaremos para cambiar el angulo de la bola
// segun movamos la paleta al golpear la bola.
int dir_pal = 0;
// Variables para el tamaño de los ladrillos.
// Lo calcularemos luego en funcion del tamaño de la
// ventana y a razon de 10 ladrillos por fila.
int largo_ladrillo, alto_ladrillo;
 
// Variable para el vector de direccion de la
// bola. Luego se calcula.
double vec_x , vec_y ;
// Variable para en angulo de la direccion
// de la bola.
int angulo = 0;
// Variable para un indicador de si estamos
// en juego o esta el juego parado (al fallar
// una bola, al ternimar partida, ...).
bool EnJuego = false;
 
// Variable para llevar la cuenta de las vidas
// que quedan. Si es 0, hemos perdido.
int Vidas = 3;

Los comentarios son bastante explicitos.

Las funciones GeneraFuente y Escribe no las pondremos ya que no cambian desde el capitulo en que se vieron. En todo caso solo hay que ajustar el tamaño de la fuente, el color y el nombre de la fuente que usamos.

Aparece una nueva funcion: CargaTextura que lo unico que hace es cargar una imagen con la libreria Corona y crear la textura correspondiente sin mipmaps. No hay porque repetir 7 veces el codigo por 7 texturas que carguemos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Funcion para cargar y crear texturas
void CargaTextura(GLuint *textura, const char *fichero)
{
// Objeto de Corona para cargar imagenes
corona::Image* imagen=NULL;
// Usamos de nuevo el objeto imagen para cargar otra imagen de disco.
imagen = corona::OpenImage(fichero, corona::PF_R8G8B8A8);
// Si no retorna un puntero valido, normalmente por que no encuentra
// la imagen en disco o el fichero esta mal, termino la funcion.
// Si ocurre esto, luego no pintara nada.
if (!imagen) return;
 
// Genero una textura y cargo su identificativo en tex2
glGenTextures(1, textura);
// Le indico a OpenGL que voy a trabajar con la textura tex2
glBindTexture(GL_TEXTURE_2D, *textura);
 
// Cargo la textura normal sin generar mipmaps en OpenGL desde
// la imagen de corona.
// Parametros: 1- Es de 2D; 2- Es para mipmaps manuales y no lo
// usamos; 3- Tipo de imagen (de nuevo RBGA); 4- Ancho de la imagen;
// 5- Alto de la imagen; 6- Borde. Nosotros 0; 7- El formato de nuevo
// 8- Tipo en que almacena la textura; 9- Puntero a los pixels de
// la imagen.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imagen->getWidth(),
imagen->getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
imagen->getPixels());
// Indico como va a visualizarse los pixels en caso de encajar una
// imagen grande en un cuadrado pequeño. GL_LINEAR interpola
// y se ve bien.
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
// Indico como va a visualizarse los pixels en caso de encajar una
// imagen pequeña en un cuadrado grande. GL_LINEAR interpola
// y se ve bien.
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
// Das dos funciones siguientes son por si quiero repetir la imagen
// dentro del cuadrado. Tendria que cambiar las coordenadas de
// textura. En este ejemplo no seran necesarias.
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_REPEAT);
 
// El modo en que se aplica la textura sobre el color del poligono.
// DECAL y REPLACE simplemente de pone el dibujo. MODULATE y BLEND
// se podrian usar con iluminacion una y para transparencia otra.
glTexEnvf(GL_TEXTURE_2D,GL_TEXTURE_ENV_MODE,GL_DECAL);
// Como ya he cargado la imagen en la textura con glTexImage2D
// en la memoria grafica, puedo liberar la memoria.
if(imagen!=NULL) delete imagen;
} // Fin CargaTextura

Luego la funcion GeneraLista cambia y generamos 2 listas, una para la bola y otra para los ladrillos (la paleta sera la misma con otra textura). El metodo se vio en su momento y es exactamente igual asi que no reproducimos el codigo aqui.

En la funcion ProcesaMensajes cambiamos un par de cosas.

Controlamos el evento de activacion de la ventana para que si minimizamos o cambiamos de ventana no siga el programa moviendose.
Para ello en el apartado de definiciones globales habiamos definido el indicador correspondiente.

1
2
3
4
5
// Activación/Desactivación de la ventana
case WM_ACTIVATEAPP:
// Actualizamos el indicador correspondiente
PROGRAMA_ACTIVO = wParam;
break;

Y en el evento de destruccion de la ventana debemos destruir todas las texturas que vamos a crear.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Borro las texturas de memoria. No hay que dejar basura.
glDeleteTextures( 1, &tex_usw );
glDeleteTextures( 1, &tex_ladrillo[0] );
glDeleteTextures( 1, &tex_ladrillo[1] );
glDeleteTextures( 1, &tex_ladrillo[2] );
glDeleteTextures( 1, &tex_ladrillo[3] );
glDeleteTextures( 1, &tex_ladrillo[4] );
glDeleteTextures( 1, &tex_paleta );
glDeleteTextures( 1, &tex_bola );
glDeleteTextures( 1, &tex_fondo );
glDeleteTextures( 1, &tex_felicidades );
glDeleteTextures( 1, &tex_bahh );
// Borro las listas.
glDeleteLists(ListaFuente, 256-32);

La funcion CreaVentana seguira como siempre excepto porque he incorporado un icono a la ventana. Eso no es la materia de este capitulo y puede no hacerse.

La funcion WinMain si que tiene alguna cosa nueva.

Si nos fijamos en las definiciones globales veremos que definiamos el numero PI, incluiamos el fichero "math.h" y definiamos dos arrays llamados seno y coseno. Si leemos los comentarios quedara clara su funcion. Son para precalcular los senos y cosenos de todos los grados de 0 a 359 y asi tener acceso inmediato a funciones trigonometricas durante la ejecucion del programa.
Ahora cargaremos esos arrays con los datos, pasando los grados a radianes, que es lo que aceptan las funciones de "math.h".

1
2
3
4
5
6
7
8
9
10
// Cargo los arrays para simular la funcion seno y coseno, 
// siempre que sea en grados enteros.
// Las funciones sen y cos admiten el angulo en radianes
// asi que lo convierto a grados:
// grados = (radianes * PI)/180.
for(int i=0; i<360; i++)
{
seno[i]=sin((double) (i*PI/180));
coseno[i]=cos((double)(i*PI/180));
}

Luego cargamos el array de ladrillos que usaremos durante el bucle, tal y como explique antes, con el primer nivel (0) para empezar la partida. Mientras cargo el array, cuento los ladrillos (en la variable NLadrillos) para, mas tarde, controlar cuando hemos terminado con todos en el juego.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Voy a rellenar el array de los ladrillos del muro con 
// los datos del array auxiliar que contiene al principio
// los datos. En el array original estara siempre el muro
// y el segundo array sera el que usemos en el juego y en
// el que iremos borrando los ladrillos segun juguemos.
// Antes de cada partida tendremos que volver a rellenar
// el array con los datos originales.
 
// Pongo la variable con el numero de ladrillos a 0
NLadrillos=0;
// Cargo los datos del array "ladrillos" con los del array
// "muro" (el que siempre tiene los datos), el nivel 0,
// el primero. Mientras, cuento los ladrillos.
for(int i=0; i<NUMERO_LADRILLOS; i++)
{
ladrillos[i] = muro[0][i];
if(muro[0][i] != 0) NLadrillos++;
}

La ultima modificacion es para que el juego se pare si la ventana no esta activa. Ya estaba todo preparado en la funcion ProcesaMensajes. Si la ventana no esta activa no ejecuto el bucle principal.

1
2
3
// Pintamos nuestra escena si la ventana
// esta activa.
if(PROGRAMA_ACTIVO) Pinta();

En la funcion IniciaGL solo cambiamos estas cosas.

Evitamos que OpenGL tenga encuenta la profundidad ya que nuestro programa es en 2D y nos sirve con que lo ultimo que pintamos se pinte encima. Comentamos la linea que habilita el test de profundidad.

1
2
3
4
5
// En este programa 2D se pintara encima
// lo que se ha pintado lo ultimo.
//glEnable(GL_DEPTH_TEST); // Habilita test de profundidad.
//A partir de ahora, lo que
// esta mas cerca se pinta encima.


Ponemos proyeccion ortogonal en base al tamaño de la ventana.

1
2
3
4
// Pongo proyeccion ortogonal, la unica que usare en este 
// juego 2D, en base al tamaño del area cliente de la
// ventana.
glOrtho(0,rect.right,rect.bottom,0,-1,1);

Compongo la medida de los ladrillos y la paleta, y pongo la posicion inicial de la paleta.

1
2
3
4
5
6
7
8
9
10
// Pongo eltamaño que tendra el ladrillo en base a la medida 
// del area cliente de la ventana.
// El largo debe ser el necesario para que quepan los
// necesarios ladrillos por fila.
largo_ladrillo = rect.right/Por_fila;
// El alto sera una tercera parte del largo.
alto_ladrillo = largo_ladrillo/3;
 
// Posiciono la paleta en medio de la ventana
pal_x = rect.right/2 - largo_ladrillo/2;

Tambien cargo las texturas con la funcion que creamos para ello y dejo ya habilitado el blending para la transparencia de los PNG's y dejo habilitadas tambien las texturas (casi todo en este programa tendra textura). Observad como los identificativos de las texturas de los ladrillos se guardan en un array que nos sera util a la hora de seleccionar la textura del ladrillo del muro a partir del array de ladrillos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Cargo cada textura de disco y la asigno a la
// variable correspondiente.
CargaTextura(&tex_usw,"usw.png");
CargaTextura(&tex_felicidades,"felicidades.png");
CargaTextura(&tex_bahh,"bahh.png");
CargaTextura(&tex_nivel,"nivel.png");
CargaTextura(&tex_paleta,"paleta.png");
CargaTextura(&tex_bola,"bola.png");
CargaTextura(&tex_fondo,"fondo.jpg");
CargaTextura(&tex_ladrillo[0],"ladrillo_naranja.png");
CargaTextura(&tex_ladrillo[1],"ladrillo_amarillo.png");
CargaTextura(&tex_ladrillo[2],"ladrillo_rojo.png");
CargaTextura(&tex_ladrillo[3],"ladrillo_verde.png");
CargaTextura(&tex_ladrillo[4],"ladrillo_azul.png");
 
// Habilito el blending para OpenGL. En este programa la mayoria
// de las imagenes tienen alguna parte transparente, y la que no
// simplemente no es afectada por esto.
glEnable(GL_BLEND);
// Le indico como tiene que hacer el blending (para un .PNG
// Con fondo transparente esta funcion va bien)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
// Habilito la aplicacion de texturas ya que casi todo
// tiene texturas en este programa.
glEnable(GL_TEXTURE_2D);

Llegado a este punto entramos en el corazon del programa, el bucle principal donde esta la logica del juego.
La funcion Pinta() sera el autentico cerebro de nuestro programa.

Empezamos como siempre limpiando la pantalla y cargando la matriz identidad en la matriz de modelo (por si acaso)

1
2
3
4
5
6
7
8
9
10
// Borro el buffer de atras (BackBuffer), donde voy a pintar.
// Tambien el buffer de profundidad para que no pinte lo que
// esta detras de otra cosa.
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
// Cargo la matriz identidad en la modelview,
// con eso me aseguro de que ningun cambio anterior modifica
// lo que voy ha hacer despues con esta matriz. Empiezo de cero.
// (borramos posibles traslaciones, rotacions, ...)
glLoadIdentity();

Lo primero que pintamos es el fondo de la ventana y luego el logo de UnSitioWeb. lo demas ira encima de estas dos cosas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Voy a pintar el fondo lo primero, el resto ira encima.
// Selecciono la textura donde almacenamos el grafico
// del fondo.
glBindTexture(GL_TEXTURE_2D, tex_fondo);
// Y dibujo un cuadrado con sus correspondientes vertices
// y sus coordenadas de textura, que corresponde con el
// area cliente de la ventana.
glBegin(GL_QUADS);
glTexCoord2d(0.0,0.0);
glVertex3i( 0, 0,0);
glTexCoord2d(0.0,1.0);
glVertex3i( 0, rect.bottom,0);
glTexCoord2d(1.0,1.0);
glVertex3i( rect.right,rect.bottom,0);
glTexCoord2d(1.0,0.0);
glVertex3i( rect.right,0,0);
glEnd();
 
// Pinto el logo de UnSitioWeb.
// Le indico la textura que voy a usar (tex_usw).
glBindTexture(GL_TEXTURE_2D, tex_usw);
// Muevo el cartel 10 pixels para abajo y 10 pixels a la
// derecha del borde de la pantalla para que no este pegado.
glLoadIdentity();
glTranslatef(10,10,0);
// Y dibujo un cuadrado con sus correspondientes vertices
// y sus coordenadas de textura.
glBegin(GL_QUADS);
glTexCoord2d(0.0,0.0);
glVertex3i( 0, 0,0);
glTexCoord2d(0.0,1.0);
glVertex3i( 0, 80,0);
glTexCoord2d(1.0,1.0);
glVertex3i( 300,80,0);
glTexCoord2d(1.0,0.0);
glVertex3i( 300,0,0);
glEnd();

Luego pinto los ladrillos a partir del array de trabajo que contiene el nivel actual.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Ahora toca pintar el muro de ladrillos, solo
// con los ladrillos que quedan (o todos, o
// unos pocos, o ninguno).
 
// Creo 2 contadores, uno para filas y otro
// para columnas.
int i1, i2;
// Buble que se ejecuta el numero de filas del muro:
// "NUMERO_LADRILLOS/Por_fila". (100 ladrillos/10 = 10 filas).
for(i1=0; i1<(NUMERO_LADRILLOS/Por_fila); i1++)
{
// Voy a pintar la fila.
// Cargo matriz identidad.
glLoadIdentity();
// Le digo a OpenGL que voy a empezar a pintar pegado al borde
// izquierdo (x=0).
// Y sera 100, mas el alto de un ladrillo multiplicado por el
// numero de fila (i1, empiezan en 0).
// El muro empezara a pintarse en la coordenada y=100.
// La coordenada z sera 0 en 2D.
glTranslatef(0, ((i1)*alto_ladrillo)+100, 0);
//Bucle que se ejecuta 10 veces (los ladrillos de una fila).
for(i2=0; i2<Por_fila; i2++)
{
// Si la posicion a pintar del array de ladrillos no es 0
// pintare el ladrillo (si es 0 es un hueco y no pintare).
// Compongo el indice del array en base a los indices de
// los bucles for:
// 10 (ladrillos por columna)
// multiplicado por i1 (indice con la fila que estamos
// procesando)
// mas i2 (indice con el numero del ladrillo de
// esta fila que estamos procesando)
if( ladrillos[(Por_fila*i1)+i2] != 0 )
{// En caso de no ser 0 pinto el ladrillo.
 
// Indico a OpenGL que pinte con la textura (grafico)
// del ladrillo.
// Uso el contenido de la casilla del array menos 1 como
// indice del array de identificadores de textura de
// los ladrillos. Hay 5 tipos de ladrillos cada uno de un color.
glBindTexture(GL_TEXTURE_2D,
tex_ladrillo[ladrillos[(Por_fila*i1)+i2]-1]);
// Pinto el ladrillo.
glCallList(Ladrillo);
}
// Le digo a OpenGL que desplace el punto siguiente
// de pantalla donde va a pintar, el largo del ladrillo
// a la derecha (eje x) para la siguiente pasada del bucle.
 
glTranslatef(largo_ladrillo, 0, 0);
} // Fin for
 
} // Fin for y del pintado del muro.

Pinto la paleta. Sera un ladrillo con otra textura.

1
2
3
4
5
6
7
8
9
10
11
12
// Pinto ahora la paleta de juego. Usare las 
// medidas de un ladrillo pero su propia textura.
 
// Cargo la matriz identidad.
glLoadIdentity();
// Me preparo para pintar en las coordenadas
// donde debe estar la paleta.
glTranslatef(pal_x, pal_y, 0);
// Indico a OpenGL la textura a usar.
glBindTexture(GL_TEXTURE_2D, tex_paleta);
// y pinto la paleta.
glCallList(Ladrillo);

Ahora un bloque de codigo dentro de un if que contempla la situacion de estar en el momento de bola parada y estar apuntando con una rayita a donde vamos a sacar.
El bloque empieza asi:

1
2
3
4
5
6
7
8
9
// Contemplamos ahora la situacion de que 
// el juego este parado (!EnJuego), queden
// ladrillos (NLadrillos>0) y nos queden
// vidas (Vidas>0).
// Esta situacion es al principio de partida
// (antes de sacar) o en medio de partida, antes
// de sacar, despues de colarsenos una bola.
if(!EnJuego && NLadrillos > 0 && Vidas>0)
{

Dentro de este bloque coloco la bola encima de la paleta, de forma que no se mueva sola si no solidaria a la paleta, y dibujo la rayita que marca la direccion del saque en base a la variable angulo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Hago que la bola se coloque encima de la paleta,
// en medio, y se mueva a la vez que esta.
bola_x = pal_x+(largo_ladrillo/2);
bola_y = pal_y-8;
 
// Pinto ahora la rayita que nos indica la
// direccion en la que vamos a sacar.
 
// Cargo la matriz identidad.
glLoadIdentity();
// Le digo a OpenGL que las rayas las pinte
// de 1 pixel de grosor.
glLineWidth(1);
// Desabilito texturas. La pintare de color blanco.
glDisable(GL_TEXTURE_2D);
// Pinto la linea.
glBegin(GL_LINES);
// Empezando en la posicion de la bola
glVertex2i(bola_x, bola_y);
// Hasta una coordenada que calculare en base a la
// variable angulo usando nuestras pseudofunciones
// trigonometricas y poniendo 30 como longitud de la linea.
glVertex2i( (bola_x) + (seno[angulo]*30),
(bola_y) - (coseno[angulo]*30));
glEnd();
// Tras pintar la raya habilito las
// testuras de nuevo.
glEnable(GL_TEXTURE_2D);

Escribo la leyenda debajo de la ventana para la situacion que tratamos.

1
2
3
4
// Escribo la leyenda abajo de la ventana,
// a 20 del borde.
Escribe(20,rect.bottom-20,
"Flechas arriba y abajo para dirigir la bola. Espacio para empezar" );

Y para terminar el bloque actual (el if) compruebo las teclas de flecha arriba y flecha abajo que seran las que permitan al jugador cambiar la direccion de saque y modifico la variable angulo en base a ellas.
Luego termino el bloque ("}")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Controlo si se pulsan la tecla arriba
// para cambiar el angulo y girar la rayita
// que habiamos pintado antes.
if(Teclas[VK_UP])
{
// Si esta pulsado disminuyo el angulo en 2
angulo-=2;
// Mantengo en angulo en un rango entre 0 y 359.
// De otra forma fallaria al usarlo de indice en
// nuestros arrays de seno y coseno.
if(angulo < 0) angulo+=360;
// Controlo tambien que no apuntemos hacia abajo
// El maximo angulo que permitimos es 280 hacia
// la izquierda.
// El 180 es para comprobar que la rayita esta
// tumbada hacia la izquierda.
if(angulo < 280 && angulo > 180) angulo = 280;
 
}
// Hago lo mismo con la tecla de la flecha abajo
if(Teclas[VK_DOWN])
{
// Si esta pulsado aumento el angulo en 2
angulo+=2;
// Mantengo en angulo en un rango entre 0 y 359.
// De otra forma fallaria al usarlo de indice en
// nuestros arrays de seno y coseno.
if(angulo >359) angulo-=360;
// Controlo tambien que no apuntemos hacia abajo
// El maximo angulo que permitimos es 80 hacia
// la derecha.
// El 240 es para comprobar que la rayita esta
// tumbada hacia la derecha.
if(angulo > 80 && angulo < 240 ) angulo = 80;
}
 
} // Fin if(!EnJuego && NLadrillos > 0 && Vidas>0)

El codigo siguiente se ejecutara siempre, en todas las situaciones con juego parado o no.

Primero pinto la bola (las coordenadas en este caso son las del centro, la bola es de 16x16 pixels).
Escribo en pantallas el numero de vidas que quedan y la pantalla en la que estamos.
Luego compruebo las teclas de mover a la derecha y la izquierda la paleta y actuo en consecuencia en cada caso (tambien actualizo la variable de la direccion de la paleta para luego usarla en el choque de bola y paleta).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Terminado este bloque de codigo, lo siguiente se
// ejecutara siempre, con juego parado o en juego.
 
// Pinto la bola
// Cargo matriz identidad (soy pesado comentando
// esta linea siempre del mismo modo pero es que
// es importente).
glLoadIdentity();
// Voy a pintar en las coordenadas de la bola.
// Le resto 8 a cada coordenada para que las
// coordenadas sean las del centro de la bola.
// La bola es un grafico de 16x16.
glTranslatef(bola_x-8,bola_y-8, 0);
// Le digo a OpenGL que voy a pintar con la
// textura donde tengo la bola.
glBindTexture(GL_TEXTURE_2D, tex_bola);
// Pinto la bola.
glCallList(Bola);
// Escribo en la esquina inferior derecha
// el numero de vidas que quedan.
Escribe(rect.right-80,rect.bottom-20,"Vidas: %i",Vidas );
// y el nivel en que estamos.
Escribe(rect.right-80,rect.bottom-35,"Nivel: %i",Nivel+1 );
 
// Ahora controlo el movimiento de la paleta.
// Inicializo la variable de la direccion de la
// paleta a 0.
dir_pal = 0;
// Si puso la flecha izquierda.
if(Teclas[VK_LEFT])
{
// Disminuyo la coodenada x de la paleta
// en 2.
pal_x-=2;
// Si tras disminuir x la coordenada es
// menor que 0 ( la paleta se sale de la
// ventana por la izquierda). La pongo
// a 0.
if(pal_x < 0) pal_x = 0;
// Si no ha ocurrido lo anterior es que
// la paleta se mueve hacia la izquierda.
else dir_pal = -1;
}
// Si puso la flecha derecha.
if(Teclas[VK_RIGHT])
{
// Incremento la coodenada x de la paleta
// en 2.
pal_x+=2;
// Si tras aumentar la coordenada la paleta
// se sale por al margen derecho de la ventana.
if( (pal_x+largo_ladrillo) > rect.right)
// Dejo la coordenada x de forma que la
// paleta se quede pegada a margen derecho.
pal_x = rect.right-largo_ladrillo;
// Si no ha ocurrido lo anterior es que
// la paleta se mueve hacia la derecha.
else dir_pal = 1;
}

Nos ocupamos ahora de lo que pasa cuando presiono espacio para sacar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Contemplo ahora el saque.
// Si presiono espacio con el juego parado
// y me quedan vidas
if(Teclas[VK_SPACE] && !EnJuego && Vidas>0)
{
// Si quedan ladrillos
if(NLadrillos > 0 )
{
// Pongo el indicador de juego a true
// (comienza el juego).
EnJuego=true;
// Compongo el vector direccion de la
// bola en base al angulo y con longitud
// de 2
vec_x = seno[angulo]*2;
vec_y = -coseno[angulo]*2;
}
}

Toca ver que hacemos cuando presionamos F1 al terminar un nivel.
Hay dos posibilidades, que siguamos la partida o que hayamos ganado porque en nivel terminado es el ultimo.
Actualizo variables y cargo el array de los ladrillos con los del nivel siguiente (o el primero si empezamos de nuevo por que hemos ganado).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Contemplo la situacion de presionar F1 con 
// la partida parada y sin ladrillos.
// Cuando la partida esta parada porque te has
// terminado el nivel actual.
if( Teclas[VK_F1] && !EnJuego && NLadrillos<=0)
{
// Inicializo lo necesario para empezar nuevo
// nivel o el primero si he terminado y he
// ganado.
 
// Incremento el nivel.
Nivel++;
// Aumento la velocidad del juego
// para dar mas emocion.
FPS_que_queremos+=20;
// Si estaba en el ultimo empiezo
// de nuevo y pongo 0.
if(Nivel > MAX_NIVEL)
{ // Pongo nivel 0.
Nivel = 0;
// Pongo las vidas a 3 de nuevo.
Vidas=3;
// Restablezco la velocidad.
FPS_que_queremos = 200;
}
// Me aseguro de que el numero de ladrillos es 0.
NLadrillos=0;
 
// Cargo el array de los ladrillos igual
// que al principio y mientras los cuento.
// Desde el array con los niveles en
// base a la variable nivel.
for(int i=0; i<NUMERO_LADRILLOS; i++)
{
ladrillos[i] = muro[Nivel][i];
if(muro[Nivel][i] != 0) NLadrillos++;
}
}

La situacion de haber perdido y dar a F1 sin vidas.
Inicializo variables y ladrillos para empezar nueva partida.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Contemplo la situacion de presionar F1 con 
// la partida parada y sin vidas.
// Cuando la partida esta parada porque has
// perdido. Se te ha colado 3 veces.
if( Teclas[VK_F1] && !EnJuego && Vidas<=0)
{
// Inicializo lo necesario para empezar nueva
// partida.
 
Nivel=0;
// Me aseguro de que el numero de ladrillos es 0.
NLadrillos=0;
// Cargo el array de los ladrillos igual
// que al principio y mientras los cuento.
// Cargo el nivel 0 para empezar de nuevo.
for(int i=0; i<NUMERO_LADRILLOS; i++)
{
ladrillos[i] = muro[0][i];
if(muro[0][i] != 0) NLadrillos++;
}
// Pongo las vidas a 3 de nuevo.
Vidas=3;
// Restablezco la velocidad.
FPS_que_queremos = 200;
}

El bloque de codigo que viene a continuacion esta dentro de un if que se ejecuta cuando estamos en juego.
En este bloque esa el control de la pelota, incluida la deteccio de choques, a mi parecer la parte mas espesa de este capitulo.
El bloque empieza asi.

1
2
3
4
5
6
// Este bloque contempla que pasa en juego.
// El control de choques.
 
// Si estamos en juego.
if(EnJuego)
{

Primero evito que la bola se salga de los bordes de la ventana y que rebote al llegar a uno.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Controlo que la bola rebote contra los bordes de
// la ventena.
 
// Si va a salir por la derecha cambio la componente X
// de signo y me aseguro que la bola esta dentro.
if( (bola_x+vec_x) > (rect.right-8) )
{ vec_x = -vec_x; bola_x = rect.right-8; }
// Si va a salir por la izquierda cambio la componente X
// de signo y me aseguro que la bola esta dentro.
if( (bola_x+vec_x) < (0+8) )
{ vec_x = -vec_x; bola_x = 0+8;}
// Si va a salir por abajo cambio la componente Y
// de signo y me aseguro que la bola esta dentro.
if( (bola_y+vec_y) > (rect.bottom-8) )
{ vec_y = -vec_y; bola_y = rect.bottom-8; }
// Si va a salir por arriba cambio la componente Y
// de signo y me aseguro que la bola esta dentro.
if( (bola_y+vec_y) < (0+8) )
{ vec_y = -vec_y; bola_y = 0+8; }

Compruebo si la bola baja mas que la posicion de la paleta. Si fuera asi se nos ha colado la bola y perdemos una vida y paramos el juego.

1
2
3
4
5
6
7
8
9
10
11
// Si la bola baja mas que la paleta pierdes 
// una vida y se para el juego.
if(bola_y > pal_y+alto_ladrillo+8 )
{
// Paro el juego.
EnJuego = false;
// Pongo a 0 el angulo para pintar la rayita.
angulo = 0;
// Resto una vida.
Vidas--;
}

El siguiente codigo se ocupa de controlar el posible choque de la bola con la paleta. Usaremos el metodo explicado al principio del capitulo. Se complica un poco al poderse mover la paleta en el eje X. En base al movimiento de la paleta en el momento del choque modificaremos el angulo en que rebota la pelota para dar mas interes al juego.
Tambien tendremos que tener cuidado ya que si la pelota y la paleta chocan en movimiento con componontes contrarias en el eje X, la comprobacion que hacemos de ver si la posicion de la bola menos el vector de movimiento (el frame anterior) chocaban tambien, ya no sirve. Para este caso concreto usaremos un indicador para ver si se han dado los casos normales. Si no fuera asi modificariamos la posicion de la bola en el eje X tanto como la velocidad de la paleta. Si no observamos este caso, podria la bola atravesar la paleta como un fantasma o si implementamos el resto de forma diferente, hacer raros al chocar en esta situacion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Compruebo si la bola choca con la paleta.
 
// Indicador para saber luego si hemos
// chocado con la paleta en movimiento
// por el lado vertical.
// (es un caso especial)
bool tratado=false;
// Si las coordenadas X de la bola estan en el rango de
// coordenadas X de la paleta puede que choquen...
if( bola_x >= (pal_x-8) && bola_x <= (pal_x+largo_ladrillo+8) )
{
// ... asi que compruebo si pasa lo mismo con las coordenadas
// Y. Si es asi hay choque.
if(bola_y >= (pal_y-8) && bola_y <= (pal_y+alto_ladrillo+8) )
{
 
// Si el frame anterior no coincidian las coordenadas X de la bola
// y la paleta, es que el choque ha sido por un borde vertical...
if( (bola_x-vec_x) <= (pal_x-8) ||
(bola_x-vec_x) >= (pal_x+largo_ladrillo+8) )
// ... y el rebote debe ser en el eje X.
{ vec_x = -vec_x; tratado = true; }
// Si el frame anterior no coincidian las coordenadas Y de la bola
// y la paleta, es que el choque ha sido por un borde horizontal...
if( (bola_y-vec_y) <= (pal_y-8) ||
(bola_y-vec_y) >= (pal_y+alto_ladrillo+8) )
// ... y el rebote debe ser en el eje Y.
{
// Si ha pasado por aqui lo pongo.
tratado=true;
// Primero nos ocupamos de modificar
// el angulo de rebote segun el movimiento de la paleta.
// Si va a la izquierda.
if(dir_pal < 0 )
{
// Disminuyo el angulo y lo restrinjo como hacia
// al mover la rayita de saque.
angulo-=20; if(angulo < 0) angulo+=360;
if(angulo < 280 && angulo > 180) angulo = 280;
}
// Si va a la derecha.
if(dir_pal > 0 )
{
// Aumento el angulo y lo restrinjo como hacia
// al mover la rayita de saque.
angulo+=20; if(angulo >359) angulo-=360;
if(angulo > 80 && angulo < 180 ) angulo = 80;
}
// Si se movia la paleta componemos de nuevo el
// vector de movimiento de la bola en base al nuevo
// angulo.
if(dir_pal != 0)
{
// Si el angulo va en contra del movimiento de la bola
// es que ha cambiado con el rebote...
if( (vec_x > 0 && angulo > 180) ||
(vec_x < 0 && angulo < 180) )
// ... asi que cambiamos el angulo hacia la
// otra direccion...
angulo = 359-angulo;
// ... y componemos el vector de movimiento en base
// al nuevo angulo (con longitud 2)
vec_x = seno[angulo]*2;
vec_y = -coseno[angulo]*2;
}
// Si no se movia la paleta solo rebotamos.
else vec_y = -vec_y;
}
// Si este indicador es false todavia es
// porque la paleta se mueve contra la pelota
// por el lado vertical. Al sumarse el movimiento
// de la bola y la paleta no pasa por ninguna
// de las dos opciones de antes.
// Esto evita que la pelota haga raros a veces
// al chocar contra la paleta en movimiento.
if(!tratado)
{
// Si la paleta va hacia la izquierda
// resto 2 a la pelota para que no se
// encuentre dentro de la paleta.
if(dir_pal<0) bola_x-=2;
// Si la paleta va a la derecha
// sumo dos.
if(dir_pal>0) bola_x+=2;
vec_x = -vec_x;
}
}
}

Despues comprobamos si la bola choca con los ladrillos y "destruimos el ladrillo" con el que si choque.
El metodo de calculo del choque es el mismo pero sin la complicacion del movimiento de la paleta.
Simplemente comprobaremos si la bola esta en zona de ladrillos y si es asi recorreremos el array que los contiene igual que cuando lo pintabamos. Si encontramos algun ladrillo que choca, lo sustituiremos por un hueco y terminaremos de buscar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// Comprobamos ahora el choque contra los ladrillos,
// para lo que recorreremos el array que los contiene
// comprobando si contiene 1 o 0, y si es 1 comprobaremos
// si choca como hicimos con la paleta.
 
// Defino dos variables para las coordenadas
// que iremos calculando para cada ladrillo
int lx,ly;
// Defino un indicador para comprobar luego si
// hay choque.
bool choque = false;
// Primero compruebo si la bola esta en las coordenadas del
// eje Y, en las que hay posibilidad de choque, en las que
// esta el muro.
if(bola_y > 92 &&
bola_y < 100+(alto_ladrillo*(NUMERO_LADRILLOS/Por_fila))+8 )
{
// Recorremos el array de ladrillos tal y como hicimos
// al pintarlo, con dos bucles "for" anidados, uno para
// filas y otro para los ladrillos de cada fila (10).
 
// Bucle para recorrer las filas
for(i1=0; i1<(NUMERO_LADRILLOS/Por_fila); i1++)
{
// Si ha chocado en la anterior pasada
// me salgo del bucle y no compruebo mas.
if(choque) break;
// Bucle para recorer los ladrillos de la fila.
for(i2=0; i2<Por_fila; i2++)
{
// Compruebo que el la posicion actual del
// array de ladrillos es 0 o no (si tiene
// ladrillo o hueco).
if( ladrillos[(Por_fila*i1)+i2] != 0 )
{
// Si tiene ladrillo cargo en
// lx y ly las coordenadas del
// ladrillo que estamos tratando.
lx=i2*largo_ladrillo;
ly=100+(i1*alto_ladrillo);
// Ahora si, copruebo si choca el ladrillo y la bola.
// Primero miro si coinciden las coordenadas X de
// bola y ladrillo.
if( bola_x >= (lx-8) && bola_x <= (lx+largo_ladrillo+8) )
{
// Compruebo si coinciden las coordenadas Y.
if(bola_y >= (ly-8) && bola_y <= (ly+alto_ladrillo+8) )
{
// Si llegamos a este punto es que si que choca.
// Compruebo entonces si lo hace por la parte
// vertical, horozontal o las dos.
// Si anteriormente no coincidia en el eje X, es
// que el choque, entonces es en el lado vertical.
if( (bola_x-vec_x) <= (lx-8) ||
(bola_x-vec_x) >= (lx+largo_ladrillo+8) )
// Cambio de sentido la componente X del
// vector de direccion.
vec_x = -vec_x;
// Si anteriormente no coincidia en el eje Y, es
// que el choque, entonces es en el lado horizontal.
if( (bola_y-vec_y) <= (ly-8) ||
(bola_y-vec_y) >= (ly+alto_ladrillo+8) )
// Cambio de sentido la componente Y del
// vector de direccion.
vec_y = -vec_y;
 
// Ahora toca algo importante, si
// choca eliminamos el ladrillo y
// lo cambiamos por un hueco.
ladrillos[(Por_fila*i1)+i2] = 0;
// y restamos 1 al numero de ladrillos.
NLadrillos--;
// Ponemos choque a true para
// que no siga comprobando otras
// filas.
choque = true;
// Nos salimos del bucle
// "for" para no comprobar mas.
break;
}
}
}
}// Fin for
}// Fin for
}

Ya para termimar el if que iniciamos mas arriba solo queda la comprobacion de si hemos acabado con todos los ladrillos y despues mover la bola.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 // Comprobamos el numero de ladrillos
// que queda. Si es 0 paramos el
// juego (hemos ganado o por lo
// menos el nivel)
if(NLadrillos <=0)
{ // Paramos el juego.
EnJuego=false;
// Ponemos recta la rayita.
angulo=0;
}
// Actualizamos las coordenadas de
// la bola sumando a sus coordenadas
// actuales el vector de direccion.
bola_x+=vec_x;
bola_y+=vec_y;
 
} // Fin if(EnJuego)

Resta solo contemplar cuando se pintan los carteles de ganar la partida, perder y cambio de nivel.

Ganar la partida.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Esta parte se ocupa de pintar en pantalla
// lo necesario cuando has ganado.
 
// Si los ladrillos son 0 y el nivel es el
// ultimo has ganado (natural ¿no?)
if(NLadrillos <=0 && Nivel == MAX_NIVEL)
{
// Escribo el mensaje de victoria y recuerdo
// la tecla para empezar de nuevo.
Escribe(20,rect.bottom-20,
"Has derribado todos los muros. Pulsa F1 para empezar de nuevo" );
 
// Pongo en pantalla un grafico con la
// felicitacion en grande.
// Selecciono la textura adecuada.
glBindTexture(GL_TEXTURE_2D, tex_felicidades);
// Cargo la matriz identidad
glLoadIdentity();
// Selecciono el punto donde poner
// el grafico.
glTranslatef( (rect.right-400)/2, (rect.bottom-250)/2, 0);
// Y dibujo un cuadrado con sus correspondientes vertices
// y sus coordenadas de textura.
glBegin(GL_QUADS);
glTexCoord2d(0.0,0.0);
glVertex3i( 0, 0,0);
glTexCoord2d(0.0,1.0);
glVertex3i( 0, 250,0);
glTexCoord2d(1.0,1.0);
glVertex3i( 400,250,0);
glTexCoord2d(1.0,0.0);
glVertex3i( 400,0,0);
glEnd();
}

Cambio de nivel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// El caso de no haber ladrillos pero 
// no sea el ultimo nivel.
if(NLadrillos <=0 && Nivel < MAX_NIVEL)
{
// Escribo el mensaje de fin de nivel recuerdo
// la tecla para seguir.
Escribe(20,rect.bottom-20,
"Has derribado este muro. Pulsa F1 para seguir" );
 
// Pongo en pantalla un grafico con el
// cartel de nivel terminado.
// Selecciono la textura adecuada.
glBindTexture(GL_TEXTURE_2D, tex_nivel);
// Cargo la matriz identidad
glLoadIdentity();
// Selecciono el punto donde poner
// el grafico.
glTranslatef( (rect.right-300)/2, (rect.bottom-100)/2, 0);
// Y dibujo un cuadrado con sus correspondientes vertices
// y sus coordenadas de textura.
glBegin(GL_QUADS);
glTexCoord2d(0.0,0.0);
glVertex3i( 0, 0,0);
glTexCoord2d(0.0,1.0);
glVertex3i( 0, 100,0);
glTexCoord2d(1.0,1.0);
glVertex3i( 300,100,0);
glTexCoord2d(1.0,0.0);
glVertex3i( 300,0,0);
glEnd();
}

Perder la partida.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Esta parte se ocupa de pintar en pantalla
// lo necesario cuando has perdido.
 
// Si las vidas son 0 pierdes (toda la vida
// fue asi)
if(Vidas <= 0)
{
// Escribo el mensaje y la tecla para empezar.
Escribe(20,rect.bottom-20,"Has perdido. F1 nueva partida" );
// Pongo en pantalla un grafico con la
// burla por perder en grande.
// Selecciono la textura adecuada.
glBindTexture(GL_TEXTURE_2D, tex_bahh);
// Cargo la matriz identidad
glLoadIdentity();
// Selecciono el punto donde poner
// el grafico.
glTranslatef( (rect.right-300)/2, (rect.bottom-200)/2, 0);
// Y dibujo un cuadrado con sus correspondientes vertices
// y sus coordenadas de textura.
glBegin(GL_QUADS);
glTexCoord2d(0.0,0.0);
glVertex3i( 0, 0,0);
glTexCoord2d(0.0,1.0);
glVertex3i( 0, 200,0);
glTexCoord2d(1.0,1.0);
glVertex3i( 300,200,0);
glTexCoord2d(1.0,0.0);
glVertex3i( 300,0,0);
glEnd();
}

¡POR FIN! Hemos terminado con con el codigo nuevo para nuestro juego 2D rompe ladrillos.

Lo que queda de la funcion Pinta(), ya se vio en anteriores capitulos. Es el codigo para visualizar los FPS en pantalla o no y para aumentarlos o disminuirlos. Esta parte del codigo se puede quitar si no se quiere cambiar los FPS pero mientras programas puede ser util que el juego vaya mas rapido o mas lento.
Luego cambia el "back buffer" y el "front buffer".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Controlo ahora lo que hago con teclado
// Si presiono la tecla "a" o "A"
if(Teclas['A'])
{
// Aumento los FPS
FPS_que_queremos+=5;
// Controlo que no pase de 500
// (por que me ha dado la gana)
if(FPS_que_queremos>500) FPS_que_queremos=500;
}
// Si presiono la tecla "z" o "Z"
if(Teclas['Z'])
{
// Reduzco los FPS
FPS_que_queremos-=5;
// Controlo que no baje de 25
if(FPS_que_queremos<25) FPS_que_queremos=25;
}
 
// Este trozo de codigo es para visualizar o no en
// pantalla los FPS presionando la tecla "f" o "F".
// Usamos un indicador para ver si se ha levantado
// la tecla. De otra forma cambiaria el estado de
// pintaFPS muchas veces en un solo pulsado de tecla
// y el resultado seria imprevisible.
if(LevantaTecla && Teclas['F'])
{ pintaFPS=!pintaFPS; LevantaTecla=false; }
else if( !Teclas['F']) LevantaTecla=true;
 
// Escribo los FPS reales con 4 digitos decimales si
// pintaFPS es true.
if(pintaFPS) Escribe(rect.right-100,40,"FPS: %.2f",FPS_reales );
 
// Cambio los bufferes de modo que presento lo que
// he dibujado en pantalla:
SwapBuffers(DevContex);

Con esto terminaria la funcion Pinta() y nuestro programa. El juego deberia verse asi.



Espero que despues de esto os dediqueis, como siempre os aconsejo, a modificar el codigo y ver que pasa. El metodo cientifico, el de prueba y error es con el que mas se aprende.

El codigo completo es este:  usw11.cpp 

Tambien podeis bajar un ZIP con el programa compilado y listo para jugar, y que incluye el codigo: Muro.

Ahora con vuestro permiso me retirare a descansar que el esfuerzo de comentar el codigo (mas que el de hacer el programa) y explicarlo en este capitulo ha sido agotador.



¡Sólo los usuarios registrados pueden escribir comentarios!
+/- Comentarios
Buscar
Jehu   |Registered |05-05-2014 10:09:20
Por que si modifico el codigo que descargue no se afecta cuando corro el
programa?
Vicengetorix   |SAdministrator |08-05-2014 21:13:24
Pues... no se, depende de lo que modifiques.
Supongo que la modificación se
compilará correctamente.
Steph  - Gracias   |190.241.246.xxx |24-04-2011 22:18:18
Excelente curso, soy estudiante de Ing en Sistemas y siempre he querido
programar un juego, excelente manera para aumentar los conocimientos en algún
lenguaje de programación
Juan Carlos  - Grande ;)   |83.37.168.xxx |15-03-2011 01:12:32
Estupendos tutoriales, aunque estoy interesado en aprender y a cabo de empezar
me parece super complicado.
No se si algún día llegaré a asimilar tanto
concepto, (tal vez sea torpe aunque me encanta esto de programar videojuegos).
Pero de todas formas gracias por existir y saber enseñar.
Un abrazo... y sigue
así.

P.D.: se te pueden consultar dudas?
Vicengetorix   |85.53.222.xxx |15-03-2011 23:00:29
Si claro.
Pense en el foro para ese tipo de cosas aunque la gente parece que
prefiere los comentarios.
fer     |189.154.26.xxx |24-09-2010 21:22:44
gracias por enseñar como se hacen las cosas
Vicengetorix   |85.53.213.xxx |25-09-2010 00:06:31
De nada. Mi intención es ser útil.
Hacer cosas en casa y que no le sirvan a
nadie no me llena.
Kevin  - Sigue mas     |200.63.197.xxx |04-10-2011 21:23:33
Gracias esapero que sigas ensaeñando XD Muchas gracias Te amo

3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."

 


Banner
Spanish Chinese (Simplified) English French German Japanese Portuguese Russian