Flip & Flop – Part 1
Como el camino se hace caminando y no hay nada mejor que enfrentarse a problemas reales para programar las cosas, vamos a empezar un juego, asin sin más … de nombre flip & flop (algún día os contaré mi comlpeja metodologia para poner nombre a los juegos).

Será el tipico juego de cartas que hay que encontrar las parejas, algo muy sencillo para empezar y que se limitará a ver las cosas que ya sabemos hacer … dibujar sprites en pantalla, usar el touch y poco más.
Aunque tambíen aprenderemos cosas nuevas como serán:
- Cargar varias texturas
- Usar una camara en 3D
- Aplicar luces
- Sprites con varias capas
- Sprites con una textura en cada lado
Lo de sprites con varias capas y con texturas en cada lado es algo que he puesto solo por fardar .. queda way, pero amos que solo se trata de dibujar sprites y sprites por pantalla. Enseguida veréis de que hablo.
Sigamos, para empezar debemos partir de cualquiera de los proyectos que tenemos, os recuerdo que SpriteGL iba a ser la base para casi todos los esperimentos, asi que empezaremos por ahi, y asumimos que lo teneis todo clarito.
Inicialización: Camara y Luces
Para este proyecto vamos a poner una cámara en 3D ya que nos interesa que el giro de las cartas sea real y tenga perspectiva y vamos a poner una luz para que las cartas se vayan sombreando mientras van girando.. ambas cosas son muy sencillas de añadir, aunque luego veremos que la camara 3D requiere un poco mas de cuidado.
unos cuantos #defines para EAGLView.m
Vamos a necesitar estas 3 funciones definidas, por un lado tenemos la típica función para pasar grados a radianes, y un par de funciones para generar números aleatorios, aqui teneis:
| C | | copy code | | ? |
| 1 | |
| 2 | #define DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) / 180.0 * M_PI) |
| 3 | #define RANDOM_SEED() srandom(time(NULL)) |
| 4 | #define RANDOM_INT(__MIN__, __MAX__) ((__MIN__) + random() % ((__MAX__+1) - (__MIN__))) |
| 5 |
Seguimos con la camara en 3D
| C | | copy code | | ? |
| 01 | |
| 02 | - (void)setupView |
| 03 | { |
| 04 | GLfloat zNear = 0.1, zFar = 1000.0, fieldOfView = 40.0; |
| 05 | GLfloat size; |
| 06 | GLfloat width = 320; |
| 07 | GLfloat height = 480; |
| 08 | |
| 09 | //Set the OpenGL projection matrix |
| 10 | glMatrixMode(GL_PROJECTION); |
| 11 | size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0); |
| 12 | glFrustumf(-size, size, |
| 13 | size / (width / height), - size / (width / height), |
| 14 | zNear, zFar); |
| 15 | glViewport(0, 0, width, height); |
| 16 | glMatrixMode(GL_MODELVIEW); |
| 17 | |
| 18 | // Clears the view with black |
| 19 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| 20 | } |
| 21 |
y listos para activar una luz
Esto va en nuestro metodo inicializar, de momento no lo pongo entero porque luego haremos mas cosas, pero por ahora solo enchufamos la luz.
| C | | copy code | | ? |
| 01 | |
| 02 | // variables for the light positions |
| 03 | GLfloat lightPosition1[] = { 0.0f, 0.0f, -100.0f, 1.0f }; |
| 04 | GLfloat lightDiffuse1[] = { 1.0f, 1.0f, 1.0f, 1.0f }; |
| 05 | GLfloat lightAmbient1[] = { 0.8f, 0.8f, 0.8f, 1.0f }; |
| 06 | |
| 07 | // add a light to the scene |
| 08 | glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient1 ); |
| 09 | glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse1 ); |
| 10 | glLightfv(GL_LIGHT0, GL_POSITION, lightPosition1 ); |
| 11 | |
| 12 | glEnable(GL_LIGHTING ); |
| 13 | glEnable(GL_LIGHT0 ); |
| 14 |
Class Carta
Nos tiene que quedar una cosa clara en la cabeza, si queréis hacer un juego usar objetos, usar objetos y usar objetos!!!! se inventaron para ello … siempre, por simple que sea lo que tengáis que programar pensar en objetos. Para nuestro juego vamos a crear un objeto Carta, tendrá métodos para moverla de una posición a otra, para hacerla girar y para dibujarse a si misma … si!!! hasta el método draw hay que delegarlo en los objetos. Pronto os daréis cuenta de las ventajas que todo esto supone.
Empezamos por el h
todas las variables son claras y no necesitan explicación, fijaros que para cada carta vamos a tener sus coordenadas, sus vertices de la textura y las normales, esto es algo que se podria optimizar si fueramos a tener 500 cartas en pantalla, pero para tener 20 ó 30 objectos en pantalla no es necesario meternos a ese nivel.
| C | | copy code | | ? |
| 01 | |
| 02 | @interface Carta : NSObject { |
| 03 | |
| 04 | GLuint imageId; // imagen de la carta |
| 05 | GLfloat posx, posy; // posicion |
| 06 | GLfloat angle; // angulo |
| 07 | GLuint imageFront, imageBack; // texturas |
| 08 | |
| 09 | GLfloat spriteTexcoords[8]; // coordenadas para la textura |
| 10 | GLfloat spriteVertices[12]; // coordenadas para los vertices |
| 11 | GLfloat spriteNormals[12]; // normales |
| 12 | } |
| 13 | |
| 14 | -(void)reset; |
| 15 | -(void)setImage:(int)ifront andBack:(int)iback; |
| 16 | -(void)setId:(GLuint)theId; |
| 17 | |
| 18 | -(void)texture4x4:(GLuint)frame; |
| 19 | -(void)texture1x4:(GLuint)frame; |
| 20 | -(void)drawSprite; |
| 21 | |
| 22 | -(void)moveTo:(GLfloat)x y:(GLfloat)y; |
| 23 | -(void)steep; |
| 24 | -(void)render; |
| 25 | |
| 26 | @end |
| 27 |
y seguimos con el m, y su breve descripción
El reset será simplemente para inicializar todas las variables a su origen, ademas vamos a inicializar los vértices del sprite y las normales
| C | | copy code | | ? |
| 01 | |
| 02 | -(void)reset |
| 03 | { |
| 04 | posx=0; |
| 05 | posy=0; |
| 06 | angle = 180; |
| 07 | |
| 08 | // Inicializamos los vertices, que no cambian |
| 09 | spriteVertices[0]=-1.0f; spriteVertices[1]= 1.0f; |
| 10 | spriteVertices[2]= 1.0f; spriteVertices[3]= 1.0f; |
| 11 | spriteVertices[4]= 1.0f; spriteVertices[5]=-1.0f; |
| 12 | spriteVertices[6]=-1.0f; spriteVertices[7]=-1.0f; |
| 13 | |
| 14 | // Y las normales lo mismo, se inicializan y no cambian |
| 15 | GLfloat nx = 0.0f; |
| 16 | GLfloat ny = 0.0f; |
| 17 | GLfloat nz = -1.0f; |
| 18 | spriteNormals[0] = nx; spriteNormals[1] = ny; spriteNormals[2] = nz; |
| 19 | spriteNormals[3] = nx; spriteNormals[4] = ny; spriteNormals[5] = nz; |
| 20 | spriteNormals[6] = nx; spriteNormals[7] = ny; spriteNormals[8] = nz; |
| 21 | spriteNormals[9] = nx; spriteNormals[10] = ny; spriteNormals[11] = nz; |
| 22 | } |
| 23 |
El setImage lo usaremos para pasarle las texturas de las cartas … no vamos a hacer que cada carta se ponga a cargar los recursos, no no no .. los cargaremos nosotros una vez, en nuestro main, y luego cuando creamos cada carta le damos las texturas para que ellas mismas sepan autodibujarse.
| C | | copy code | | ? |
| 1 | |
| 2 | - (void)setImage:(int)ifront andBack:(int)iback |
| 3 | { |
| 4 | imageFront = ifront; |
| 5 | imageBack = iback; |
| 6 | } |
| 7 |
El método setId es para darle a cada carta el id del sprite que va a dibujar (cuidado que id es una palabra reservada del objective)
| C | | copy code | | ? |
| 1 | |
| 2 | -(void) setId:(GLuint)theId |
| 3 | { |
| 4 | imageId = theId; |
| 5 | } |
| 6 |
texture4x4 y texture1x4
Los métodos texture4x4 y texture1x4 son primos de nuestro método ‘setSprite’ que ya definimos en SpriteGL, su misión es la de configurar las coordenadas de la textura para dibujar luego un sprite .. como su propio nombre indica son para configurar un sprite de 4×4 o para 1×4.
| C | | copy code | | ? |
| 01 | |
| 02 | -(void)texture4x4:(GLuint)frame |
| 03 | { |
| 04 | GLfloat x1 = (frame % 4) / 4.0f; |
| 05 | GLfloat y1 = (frame / 4) / 4.0f; |
| 06 | GLfloat x2 = x1 + 0.25f; |
| 07 | GLfloat y2 = y1 + 0.25f; |
| 08 | |
| 09 | // preparamos el array con las coordenadas de la textyra |
| 10 | spriteTexcoords[0] = x1; spriteTexcoords[1] = y2; |
| 11 | spriteTexcoords[2] = x2; spriteTexcoords[3] = y2; |
| 12 | spriteTexcoords[4] = x2; spriteTexcoords[5] = y1; |
| 13 | spriteTexcoords[6] = x1; spriteTexcoords[7] = y1; |
| 14 | } |
| 15 | |
| 16 | -(void)texture1x4:(GLuint)frame |
| 17 | { |
| 18 | GLfloat x1 = (frame % 4) / 4.0f; |
| 19 | GLfloat y1 = 0.0f; |
| 20 | GLfloat x2 = x1 + 0.25f; |
| 21 | GLfloat y2 = 1.0f; |
| 22 | |
| 23 | // preparamos el array con las coordenadas de la textura |
| 24 | spriteTexcoords[0] = x1; spriteTexcoords[1] = y2; |
| 25 | spriteTexcoords[2] = x2; spriteTexcoords[3] = y2; |
| 26 | spriteTexcoords[4] = x2; spriteTexcoords[5] = y1; |
| 27 | spriteTexcoords[6] = x1; spriteTexcoords[7] = y1; |
| 28 | } |
| 29 | |
| 30 | -(void)drawSprite |
| 31 | { |
| 32 | glEnableClientState(GL_VERTEX_ARRAY); |
| 33 | glVertexPointer(2, GL_FLOAT, 0, spriteVertices); |
| 34 | |
| 35 | glEnableClientState(GL_TEXTURE_COORD_ARRAY); |
| 36 | glTexCoordPointer(2, GL_FLOAT, 0, spriteTexcoords); |
| 37 | |
| 38 | glEnableClientState( GL_NORMAL_ARRAY ); |
| 39 | glNormalPointer ( GL_FLOAT, 0, spriteNormals ); |
| 40 | |
| 41 | glDrawArrays(GL_TRIANGLE_FAN, 0, 4); |
| 42 | } |
| 43 |
Si os suena a chino es el momento que veáis las texturas que vamos a usar:
Por un lado tenemos nuestra textura para las cartas, se trata de una imagen 256×64 pixels que vamos a dividir en 4 sprites de 64×64:
El primer sprite será la parte trasera de la carta, el segundo sprite será la parte frontal, el tercer sprite es un brillo para poner luego encima (que supongo no se notará porque es transparente jiji) y el cuarto sprite está en blanco .. no lo usamos, pero está ahi para mantener la proporción de 64×64.
Y esta es nuestra textura con todos los sprites para nuestras cartas
Al final acabaremos combinando estas 2 texturas para dibujar cada carta … de ahi viene aquella historia de dibujar un sprite en varias capas:
Traducido esto nuestro metodo Render nos queda asi:
| C | | copy code | | ? |
| 01 | |
| 02 | -(void) steep |
| 03 | { |
| 04 | angle++; |
| 05 | } |
| 06 | |
| 07 | -(void) render |
| 08 | { |
| 09 | glPushMatrix(); |
| 10 | glTranslatef(posx,posy,0); // llevamos la carta a su posicion XY |
| 11 | glRotatef(angle,0,1,0); // la rotamos el angulo de gior |
| 12 | |
| 13 | glEnable(GL_TEXTURE_2D); |
| 14 | glBindTexture(GL_TEXTURE_2D, imageBack); |
| 15 | |
| 16 | // imagen detras de la carta (rotamos 180 para el cull face) |
| 17 | glRotatef(180,0,1,0); |
| 18 | [self texture1x4:0]; |
| 19 | [self drawSprite]; |
| 20 | glRotatef(-180,0,1,0); // y des-rotamos |
| 21 | |
| 22 | // el otro lado de la carta |
| 23 | [self texture1x4:1]; |
| 24 | [self drawSprite]; |
| 25 | |
| 26 | // sprite de la carta |
| 27 | glBindTexture(GL_TEXTURE_2D, imageFront); |
| 28 | [self texture4x4:imageId]; |
| 29 | [self drawSprite]; |
| 30 | |
| 31 | // brillo de la carta |
| 32 | glBindTexture(GL_TEXTURE_2D, imageBack); |
| 33 | [self texture1x4:2]; |
| 34 | [self drawSprite]; |
| 35 | |
| 36 | glPopMatrix(); |
| 37 | } |
| 38 |
De Vuelta a EAGLView.h
Por ahora solo hemos creado una clase Carta que se corresponde al objeto en si, ahora vamos a ver como usar estas cartas desde nuestro bucle del juego, vamos a EAGLView.h y añadimos estas lineas:
En primer lugar que no se os olvide el include
| C | | copy code | | ? |
| 1 | |
| 2 | #import "Carta.h" |
| 3 |
Y dentro de la interface nos creamos estas variables
| C | | copy code | | ? |
| 1 | |
| 2 | GLuint cartaBack, cartaFront; |
| 3 | Carta *c1, *c2, *c3; |
| 4 |
nos vamos a EAGLView.m
Ahora nos vamos a nuestro método inicializar y pasamos a inicializar las cartas, empezaremos por cargar las 2 texturas, y activar el cull_face. Acordaros que en este método ya habíamos añadido las luces .. ahora seguimos iniciando más cosas:
| C | | copy code | | ? |
| 01 | |
| 02 | // activamos el suavizado de texturas |
| 03 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 04 | glEnable(GL_TEXTURE_2D); |
| 05 | |
| 06 | // activamos el cull_face para no dibujar los poligonos por 2 caras |
| 07 | glEnable(GL_CULL_FACE); |
| 08 | glFrontFace(GL_CCW); |
| 09 | |
| 10 | // y cargamos las 2 texturas |
| 11 | cartaBack = [GL loadTexture2:"CartaRed.png"]; |
| 12 | cartaFront = [GL loadTexture2:"Criaturas.png"]; |
| 13 |
Siguiente paso: crear las 3 cartas, inicializarlas con la textura y poner cada una en un sitio, aqui vamos a añadir una funcion para que se inicie con un sprite aleatorio, para ello usaremos la funcion RANDOM_INT que ya habiamos definido al principio:
| C | | copy code | | ? |
| 01 | |
| 02 | |
| 03 | c1 = [Carta alloc]; |
| 04 | [c1 setImage:cartaFront andBack:cartaBack]; |
| 05 | [c1 reset]; |
| 06 | [c1 setId:RANDOM_INT(0,15)]; |
| 07 | [c1 moveTo:-2 y:0]; |
| 08 | |
| 09 | c2 = [Carta alloc]; |
| 10 | [c2 setImage:cartaFront andBack:cartaBack]; |
| 11 | [c2 reset]; |
| 12 | [c2 setId:RANDOM_INT(0,15)]; |
| 13 | [c2 moveTo:0 y:0]; |
| 14 | |
| 15 | c3 = [Carta alloc]; |
| 16 | [c3 setImage:cartaFront andBack:cartaBack]; |
| 17 | [c3 reset]; |
| 18 | [c3 setId:RANDOM_INT(0,15)]; |
| 19 | [c3 moveTo:2 y:0]; |
| 20 | |
| 21 |
| C | | copy code | | ? |
| 01 | |
| 02 | |
| 03 | - (void)drawView |
| 04 | { |
| 05 | [self clearBuffer];; |
| 06 | glLoadIdentity(); |
| 07 | |
| 08 | glTranslatef(0,0,-10); |
| 09 | |
| 10 | [c1 steep]; |
| 11 | [c1 render]; |
| 12 | |
| 13 | [c2 steep]; |
| 14 | [c2 render]; |
| 15 | |
| 16 | [c3 steep]; |
| 17 | [c3 render]; |
| 18 | |
| 19 | [self swapBuffer]; |
| 20 | } |
| 21 | |
| 22 |
3 Comments
neofar on August 31st, 2009
Pues … tarde o temprano tendré que hacer la segunda parte, de todos modos no soy muy partidario de poner el código para descargar, más que para los archivos que requieran mucho por escribir, aquí tienes todo lo necesario para reproducir el ejemplo
yotes on January 31st, 2010
Hola, recien encuentro los tutoriales y solo los he visto por arriba, pero se ven muy ùtiles. Me encanta encontrar algo en castellano, gracias por tu tiempo!!




@raywenderlich
Cocos 2d
cocos2d-central
libgdx
libgdx-users
alvaro on August 27th, 2009
hay forma de conseguir el codigo terminado para ver como anda o la segunda parte del manual.
gracias.
muy buenos tutoriales
deje mi email por si me lo podes mandar terminado para verlo