Crear un juego con Cocos2D para IPhone/IPad en Xcode

1
8927

Puedes descargar el código fuente aquí.

Crear un juego con Cocos2D para IPhone/IPad en Xcode

0. índice de contenidos.

1. Introducción.

Hola a todos, he empezado unas prácticas en autentia la semana pasada y cómo primera tarea me han encargado hacer una aplicación para iPhone similar a Space invaders. Para los que no conozcan el juego, básicamente consiste en controlar una nave que se encuentra en la parte baja de la pantalla y disparará a unos alienijenas que van avanzando poco a poco. El objetivo del juego es cargarse a los aliens antes de que lleguen a la posición de la nave. Nosotros vamos a usar la cabeza de la mosca de autentia, que disparará a unas naves que avanzarán por un fondo estrellado.

El objetivo de este tutorial es aprender a hacer un juego para iPhone usando un framework existente (cocos2d). Como el diseño del juego lo he hecho yo mismo, puede que haya algún fallo en el mismo. Por ejemplo, al principio pense en separar los actores (Nave,Mosca y Disparo) en tres clases distintas, porque iban a tener atributos característicos propios, pero al acabar la aplicación he visto que podría haber usado una única clase. Además la primera vez que hice la aplicación (aprendiendo a usar cocos2d mientras la hacía), la mayoria de las variables eran globales al fichero (lo hice así porque en uno de los tutoriales de cocos2d ponía que se podía hacer para aplicaciones simples), lo que al final trajó más problemas que soluciones. Para hacer este tutorial, he vuelto a hacer el proyecto desde cero, y esta vez he optado por identificar a los actores con un id y recuperarlos a través de ese id cuando sea necesario. Me hubiese gustado hacer un menu principal desde el que pudiesemos acceder a la aplicación, pero me dio más problemas de los que pensaba (creo que ahora si sabría hacerlo), por lo que el juego se lanza al empezar la aplicación y se reinicia cuando se gana o se pierde.

Para poder hacer la aplicación me leí varios tutoriales y manuales (podeís encontrarlos en lecturas recomendadas), ya que no había programado antes en Objetive-C, no me ha parecido un lenguaje díficil una vez que te acostumbras, aunque tuve varios problemas al principio por no acordarme de poner los imports (la mala costumbre del autocompletar). Y bueno, ya me he alargado bastante en la presentación, asi que vamos a empezar con el tutorial en sí.


2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBookPro8,2 (2 GHz Intel Core i7, 4GB DDR3 SDRAM).
  • AMD Radeon HD 6490M 256MB.
  • Sistema Operativo: Mac OS X Snow Leopard 10.6.7.
  • IDE: Xcode Versión 3.2.6.
  • iPhone OS: Versión 4.3.


3. Lecturas recomendadas.

Antes de empezar con este tutorial, sería recomendable leerse los tutoriales relacionados con el tema que podeís encontrar en AdictosAltrabajo.com.

Hay varios, yo para trabajar en este tutorial he seguido varios. Para montar el sdk junto con Xcode he seguido este
tutorial
, que además sirve para hacer una pequeña aplicación y familiarizarse con Xcode. Después de ese tutorial, seguí este
otro
, en el que podemos desarrollar una aplicación un poco más compleja y seguir familiarizandonos un poco más con Xcode. Y por último como introducción a Cocos2D tenemos este.

Otra lectura recomendable mientras se trabaja con Cocos2d (incluso antes de empezar) es la documentación que ofrecen en la página de Cocos2D para IPhone.
Dentro de la sección programming Guide
podemos encontrar tambien una pequeña guía para novatos (Beginners’ Guide) con tres lecciones en la que nos enseñan la funcionalidad básica que ofrece cocos2D.

Se puede intentar seguir este tutorial sin leer antes los anteriores, pero sería recomendable por lo menos echarles un ojo por encima.

Bueno, creo que ya estamos listos para ponernos manos a la obra


4. Creando el proyecto en Xcode.

Para crear el proyecto le damos a File > New Project. En la ventana que nos sale tenemos que seleccionar que vamos a usar la template de una cocos2d Application.

Le damos a Choose… y nos pedirá el nombre del proyecto (en mi caso le voy a llamar AutentiaInvaders) y el lugar en el disco en el que lo queremos guardar (lo dejo a vuestra elección).

La verdad es que al crear el proyecto ya te da practicamente todo hecho. Vamos a ejecutarlo tal y como esta. Para ejecutarlo vamos a cambiar el ejecutable a iPhone Simulator 4.2 (en el 4.3 no he conseguido que funcione de momento, tendría que mirar si esta soportado en la versión que uso de cocos2d).

Ejecutamos, podemos pinchar en el martillito y el play (clean and build) o directamente pinchar en el editor y darle a (cmd + r). Como es la primera vez tarda un poquito porque tiene que instalar la aplicación en el simulador.

Una vez visto que funciona vamos a analizar un poquito las clases que ha creado.

Vamos a ver el método applicationDidFinishLaunching de la clase AutentiaInvadersAppDelegate.m. De momento no he estudiado mucho esta clase (que es la que se encarga de gestionar el ciclo de vida de la aplicación), pero podemos ver que la última linea de este metodo, que es el que se llama cuando la aplicación acaba de lanzarse, llama al método runWithScene del objeto sharedDirector pasandole la lo que devuelve el método scene de la clase HelloWorld.

// AutentiaInvadersAppDelegate.m  
// metodo applicationDidFinishLaunching  
// última linea  
[[CCDirector sharedDirector] runWithScene: [HelloWorld scene]]; 

Esto último que he dicho, puede que haya sonado a chino, pero bueno, básicamente es que al acabar de lanzarse la aplicación se llama al método scene de HelloWorld. Vamos a ver que hace este metodo:

// HelloWorldScene.m  
  
+(id) scene  
{  
    // 'scene' is an autorelease object.   
    // aqui nos dice que no tenemos que preocuparnos de liberar la memoria del objeto scene, porque es automático  
    CCScene *scene = [CCScene node]; // Crea una escena  
      
    // 'layer' is an autorelease object.  
    // este objeto también libera la memoria solo  
    HelloWorld *layer = [HelloWorld node]; // Crea una capa con el objeto HelloWorld  
      
    // add layer as a child to scene  
    [scene addChild: layer]; // le añade la capa a la escena  
      
    // return the scene  
    return scene; // devuelve la escena  
} 

Bueno, pues parece que lo que tenemos cuando se acaba de lanzar la aplicación en nuestro iPhone es una escena que tiene como hija una capa de la clase HelloWorld. Nuestra aplicación, como va a ser muy simple, utilizará esta unica escena, y en ella se desarrollará toda la lógica del juego y la presentación.

Vamos a empezar por crear las tres clases principales de la aplicación, Mosca, Nave y Disparo. Las tres clases serán hijas de la clase de cocos2d CGSprite. Le damos a File > New File … en la ventana que se abre, vamos a las clases de cocos2d, elegimos una CCNode class y le decimos que será una subclase de CCSprite y le damos a next.

Elegimos el nombre y le damos a finish.

En la documentación de cocos2d dicen que hay que implementar al menos el método initWithTexture cuando se crea una subclase de CGSprite, así que una vez que tenemos creadas las tres clases, vamos a implementarlo. Además de este método, implementaremos el metodo spriteWithFile, que es el método que vamos a usar para crear nuestros actores (mosca, naves y disparos) asociandoles una imagen. Así nos quedarian los archivos de la interfaz y la implementación:

Como recomendación os diria que vayais probando que el proyecto sigue funcionando despues de realizar cambios. También combiene guardar snapshots cada cierto tiempo (Roberto os dice como en su tutorial ( http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=oop_con_objetiveC_Iphone )). Así será más fácil detectar posibles fallos y restaurar el proyecto a una versión anterior que funcionase.

Una vez hemos visto que el proyecto sigue funcionando, vamos a poner a nuestros actores dentro de la escena. Para ello nos vamos a la clase HelloWorldScene, y vamos a cambiar el método init.

//  
//  Mosca.h  
//  AutentiaInvaders  
//  
//  Created by César López de Felipe Abad on 10/04/11.  
//  Copyright 2011 Autentia Real Business. All rights reserved.  
//  
  
#import   
#import "cocos2d.h"  
  
@interface Mosca : CCSprite {  
  
}  
  
// método que hay que debe sobreescribir una subclase de CGSprite  
- (id) initWithTexture:(CCTexture2D *)texture;  
  
// método que usaremos para construir nuestros actores  
- (id) spriteWithFile:(NSString *)filename;  
  
@end  
//  
//  Mosca.m  
//  AutentiaInvaders  
//  
//  Created by César López de Felipe Abad on 10/04/11.  
//  Copyright 2011 Autentia Real Business. All rights reserved.  
//  
  
#import "Mosca.h"  
  
  
@implementation Mosca  
  
- (id) initWithTexture:(CCTexture2D *)texture{  
    if (self = [super initWithTexture:texture]){  
          
    }  
    return self;  
}  
  
- (id) spriteWithFile:(NSString *)filename{  
    if (self = [super spriteWithFile]){  
    }  
    return self;  
}  
  
@end 

He añadido unas imagenes al proyecto para poder asociarlas a los actores (acordaros de añadirlas tanto en el directorio como en Xcode), además de añadir una constante (TAM_IMAGEN) que guarda el tamaño de la imagen. Para añadir la constante, simplemente teneis que añadir este linea antes de el @implementation.

const int TAM_IMAGEN = 21;  
  
// HelloWorld implementation  
@implementation HelloWorld 

Nos quedaría algo asi:

Con esto ya tendriamos a nuestra mosca enfrentandose a una nave. Pero lo que queremos es que se enfrente a varias naves. Podríamos hacer un NSMutableArray de naves, pero a mí me dio problemas a la hora de moverlas por la pantalla, así que lo solucione poniendoles una etiqueta a la hora de añadirlas a la capa. Vamos a crear un método que nos añada las naves a la capa, una variable para saber el número de naves que hay y un par de constantes para saber cuantas filas y columnas de naves tenemos.

//  
//  HelloWorldLayer.m  
//  AutentiaInvaders  
//  
//  Created by César López de Felipe Abad on 08/04/11.  
//  Copyright Autentia Real Business 2011. All rights reserved.  
//  
  
// Import the interfaces  
#import "HelloWorldScene.h"  
#import "Nave.h"  
#import "Mosca.h"  
  
const int TAM_IMAGEN = 21;  
const int NAVES_FILAS = 5;  
const int NAVES_COLUMNAS = 12;  
int totalNaves;  
  
// HelloWorld implementation  
@implementation HelloWorld  
  
+(id) scene  
{  
    // 'scene' is an autorelease object.  
    CCScene *scene = [CCScene node];  
      
    // 'layer' is an autorelease object.  
    HelloWorld *layer = [HelloWorld node];  
      
    // add layer as a child to scene  
    [scene addChild: layer];  
      
    // return the scene  
    return scene;  
}  
  
// on "init" you need to initialize your instance  
-(id) init  
{     
    if( (self=[super init] )) {       
        // recuperar el tamaño de la pantalla del dispositivo  
        CGSize size = [[CCDirector sharedDirector] winSize];  
          
        Mosca *mosca = [Mosca spriteWithFile:@"mosca.png"]; // Creamos la mosca con su imagen  
        mosca.position = ccp (size.width /2, 0 + TAM_IMAGEN/2); // Colocamos la mosca centrada en la parte de abajo  
        [self addChild:mosca]; // Añadimos la mosca a la capa  
          
        [self addNaves]; // añadir las naves  
      
    }  
    return self;  
}  
  
// añade las naves a la capa  
- (void) addNaves{  
    // Recuperamos el tamaño de la pantalla  
    CGSize size = [[CCDirector sharedDirector] winSize];      
      
    int numero=0; // esta será la tag de las naves  
    for (int fila=1 ; fila <= NAVES_FILAS ; fila++) {  
        for (int columna = 1 ; columna <= NAVES_COLUMNAS ; columna++){  
            Nave * nave = [Nave spriteWithFile:@"nave.png"]; // creamos la nave  
            nave.tag = numero; // le ponemos su tag  
            nave.position = ccp(size.width - TAM_IMAGEN*columna, size.height - TAM_IMAGEN*fila); // la colocamos en la capa  
            numero++;  
            [self addChild:nave]; // la añadimos a la capa  
        }  
    }     
    totalNaves = NAVES_FILAS*NAVES_COLUMNAS;  
}  
  
  
// on "dealloc" you need to release all your retained objects  
- (void) dealloc  
{  
    // in case you have something to dealloc, do it in this method  
    // in this particular example nothing needs to be released.  
    // cocos2d will automatically release all the children (Label)  
      
    // don't forget to call "super dealloc"  
    [super dealloc];  
}  
@end

Nos debería quedar algo así cuando lo ejecutemos:

Ahora que tenemos nuestros actores en escena, vamos a poner un par de botones para poder mover nuestra mosca. Lo vamos a hacer usando un objeto menu de cocos2d, que estará compuesto por los dos botones que serán objetos de la clase CCMenuItemImage. Creamos el metodo addBotones, una constante para guardar el tamaño del boton y los métodos a los que se llamará cuando se pulsen (me hubiese gustado hacer un unico método, pero después de pelearme un buen rato no lo conseguí, así que lo solucione haciendo dos métodos). También vamos a añadirle una tag a la mosca (que será una constante) a la hora de crearla para poder encontrarla desde el método que la va a mover.

// añade los botones a la capa  
- (void) addBotones{   
    CGSize size = [[CCDirector sharedDirector] winSize]; // recuperamos el tamaño de la ventana  
      
    // creamos los dos botones  
    CCMenuItemImage *izquierda = [CCMenuItemImage itemFromNormalImage:@"botonizquierda.png" // la imagen del boton  
                                        selectedImage:@"botonizquierda.png" // la imagen para cuando se pulse el boton // en este caso la misma  
                                        target:self // objeto que responderá cuando se pulse el boton  
                                        selector:@selector(moverMoscaIzquierda:)]; // método al que se llamará al pulsar el boton  
    CCMenuItemImage *derecha = [CCMenuItemImage itemFromNormalImage:@"botonderecha.png" // la imagen del boton  
                                    selectedImage:@"botonderecha.png" // la imagen para cuando se pulse el boton // en este caso la misma  
                                    target:self // objeto que responderá cuando se pulse el boton  
                                    selector:@selector(moverMoscaDerecha:)]; // método al que se llamará al pulsar el boton   
      
    CCMenu * menu = [CCMenu menuWithItems:izquierda,derecha,nil]; // añadimos los botones al menu  
    [menu alignItemsHorizontallyWithPadding: size.width - TAM_BOTON*2]; // los alineamos horizontalmente con separación para que queden en los bordes  
      
    [self addChild:menu]; // añadimos el menu a la capa  
}  
  
// método para mover a la mosca a la derecha  
- (void) moverMoscaDerecha:(CCMenuItemImage *) boton{     
    NSLog(@"Boton derecha pulsado"); // escribimos en el log  
    CGSize size = [[CCDirector sharedDirector] winSize]; // recuperamos el tamaño de la ventana  
    Mosca *mosca = [self getChildByTag:ID_MOSCA]; // buscamos la mosca y la asignamos a una variable  
    if (mosca.position.x < 0 + size.width - TAM_IMAGEN) // movemos la mosca si no ha llegado al borde  
    {     
        mosca.position = ccp (mosca.position.x + TAM_IMAGEN,mosca.position.y);  
    }else{  
        NSLog(@"Se ha llegado al borde derecho");   
    }  
}  
  
// método para mover a la mosca a la izquierda  
- (void) moverMoscaIzquierda:(CCMenuItemImage *) boton{  
    NSLog(@"Boton izquierda pulsado"); // escribimos en el log  
    Mosca *mosca = [self getChildByTag:ID_MOSCA]; // buscamos la mosca y la asignamos a una variable  
    if (mosca.position.x > 0 + TAM_IMAGEN) // movemos la mosca si no ha llegado al borde  
    {     
        mosca.position = ccp (mosca.position.x - TAM_IMAGEN,mosca.position.y);  
    }else{  
        NSLog(@"Se ha llegado al borde izquierdo");   
    }  
}

Nos quedará algo así:

Podemos ver en el log que al pulsar los botones se esta llamando al método correspondiente. Para abrir el log podeís darle a Run > Console o podeís darle a (cmd + shift + r) desde el editor.

Como ahora añadimos a la mosca con un id, podemos hacer un método addMosca y otro addComponentes, para que el método init quede un poco más limpio:

// on "init" you need to initialize your instance  
-(id) init  
{     
    if( (self=[super init] )) {       
        [self addComponentes];    
    }  
    return self;  
}  
  
// añade los componentes  
- (void) addComponentes{  
    [self addMosca]; // añadir la mosca  
    [self addNaves]; // añadir las naves  
    [self addBotones]; // añadir los botones  
}  
  
// añade la mosca  
- (void) addMosca{  
    // recuperar el tamaño de la pantalla del dispositivo  
    CGSize size = [[CCDirector sharedDirector] winSize];  
    Mosca *mosca = [Mosca spriteWithFile:@"mosca.png"]; // Creamos la mosca con su imagen  
    mosca.position = ccp (size.width /2, 0 + TAM_IMAGEN/2); // Colocamos la mosca centrada en la parte de abajo  
    mosca.tag = ID_MOSCA; // ponerle un tag a la mosca para poder buscarla  
    [self addChild:mosca]; // Añadimos la mosca a la capa     
} 

Vamos a mover a las naves, para hacer el juego algo más emocionante. Para ello, vamos a crear un evento periódico que se encargue de mover a las naves, en este método tambien gestionaremos las colisiones en
el futuro. Para saber en que dirección se estan moviendo las naves vamos a añadir el atributo isMovingRight a la clase Nave. Este atributo será YES si se mueve a la derecha y NO si se mueve a la izquierda.

//  
//  Nave.h  
//  AutentiaInvaders  
//  
//  Created by César López de Felipe Abad on 10/04/11.  
//  Copyright 2011 Autentia Real Business. All rights reserved.  
//  
  
#import   
#import "cocos2d.h"  
  
@interface Nave : CCSprite {  
    Boolean isMovingRight;  
}  
  
@property (assign) Boolean isMovingRight;  
  
- (id) initWithTexture:(CCTexture2D *)texture;  
- (id) spriteWithFile:(NSString *)filename;  
  
@end  
  
//  
//  Nave.m  
//  AutentiaInvaders  
//  
//  Created by César López de Felipe Abad on 10/04/11.  
//  Copyright 2011 Autentia Real Business. All rights reserved.  
//  
  
#import "Nave.h"  
  
  
@implementation Nave  
  
@synthesize isMovingRight;  
  
... 

Descomentamos la linea que habia en el metodo addNaves, para que al crearlas tengan el atributo isMovingRight a NO. Creamos el método periódico logicaJuego, y le añadimos al método init una llamada al método logicaJuego. Me hubiese gustado hacer el movimiento de las naves en un método aparte, pero no conseguí que funcionase.

// on "init" you need to initialize your instance  
-(id) init  
{     
    if( (self=[super init] )) {       
        [self addComponentes]; // añadimos los componentes  
        [self schedule:@selector(logicaJuego:)]; //un evento periódico que realizará la lógica del juego cada x tiempo  
    }  
    return self;  
}  
  
// método para la lógica del juego  
- (void) logicaJuego:(ccTime)dt{  
    // recuperar el tamaño de la pantalla  
    CGSize size = [[CCDirector sharedDirector] winSize];  
      
    // recuperamos las naves por su etiqueta y las movemos  
    for (int tag=0;tag size.width - TAM_IMAGEN){   
                nave.position = ccp(nave.position.x,nave.position.y-TAM_IMAGEN);  
                nave.isMovingRight = NO;  
            }  
        }else{  
            nave.position = ccp (nave.position.x -dt*50,nave.position.y);  
            if (nave.position.x < 0 + TAM_IMAGEN){  
                nave.position = ccp(nave.position.x,nave.position.y-TAM_IMAGEN);  
                nave.isMovingRight = TRUE;  
            }         
        }  
    }  
} 

Ahora que ya tenemos las naves moviendose, sólo nos falta poder dispararlas. Para ello vamos a hacer que cada vez que se toque la pantalla en cualquier sitio que no sean los botones de dirección, se dispare. Para hacer esto, hay que decirle a nuestra clase que admita toques. En cocos2d hay dos tipos de toque, los normales (standard touches) y los targeted touches (no se muy bien como traducirlo). Nosotros vamos a usar los segundos, la diferencia la explican en esta página de la documentación de cocos2d (http://www.cocos2d-iphone.org/wiki/doku.php/tips:touchdelegates), explicándolo brevemente, los targeted touch, sólo recuperan un toque cada vez mientras que los standard recuperan todos los toques que haya.

Volviendo a nuestro juego, tenemos que sobreescribir el metodo registerWithTouchDispatcher (hay que importar CCTouchDispatcher.h) de la siguiente forma:

-(void) registerWithTouchDispatcher  
{  
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];  
}  

Esta forma de activar los toques es valida para todas las subclases de CCLayer, si quisiesemos activar los toques en otra clase, requiere un poco más de trabajo (explican como hacerlo en el link anterior.

Ahora que ya podemos gestionar los toques, vamos a hacer algo con ellos, le decimos al método init que los toques estan activados y sobreescribimos estos dos métodos:

-(id) init  
{     
    if( (self=[super init] )) {       
        [self addComponentes]; // añadimos los componentes  
        [self schedule:@selector(logicaJuego:)]; //un evento periódico que realizará la lógica del juego cada x tiempo  
    }  
    self.isTouchEnabled = YES;  
    return self;  
}  
  
// Al usar los "targeted touch events" tenemos que implementar al menos este metodo  
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {  
    return YES; // Con esto reclamamos el touch  
}  
  
// Cuando el toque acaba será cuando disparemos  
- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {  
    NSLog(@"Toque finalizado");  
}

Ahora vamos a cambiar el método ccTouchEnded para que llame al método disparar (que vamos a crear ahora). El método disparar creará un disparo que se moverá verticalmente. Para mover el disparo, en vez de usar el método periódico que tenemos, vamos a crearlo y asignarle una acción (moverse hacia arriba).

// Cuando el toque acaba será cuando disparemos  
- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {  
    NSLog(@"Toque finalizado");  
    [self disparar];  
}  
  
- (void) disparar{    
    CGSize size = [[CCDirector sharedDirector] winSize]; // recuperamos el tamaño de la ventana  
    Disparo *disparo = [Disparo spriteWithFile:@"disparo.png"]; // Creamos el disparo  
    Mosca *mosca = [self getChildByTag:ID_MOSCA]; // Recuperamos la mosca, para saber donde crear el disparo  
    disparo.position = ccp (mosca.position.x,mosca.position.y+TAM_IMAGEN); // le ponemos las coordenadas  
    disparo.tag = ID_DISPARO; // le ponemos el id para saber buscarlo  
    [self addChild:disparo]; // añadimos el disparo a la capa  
    [disparo runAction:[CCMoveTo actionWithDuration:1 position: ccp(disparo.position.x,disparo.position.y + size.height)]];  
    // le decimos al disparo que se mueva durante 1 segundo hasta el final de la pantalla  
}  

Ahora que ya podemos disparar, vamos a gestionar las colisiones entre disparos y naves. Para ello, vamos a usar la funcion CGRectIntersectsRect, que nos devuelve YES si dos rectangulos interseccionan y NO si no lo hacen. Pero antes, tenemos que hacer que nuestras clases disparo y nave nos devuelvan un CGRect. Vamos a crear un protocolo con el método getRect.

//  
//  IRect.h  
//  AutentiaInvaders  
//  
//  Created by César López de Felipe Abad on 10/04/11.  
//  Copyright 2011 Autentia Real Business. All rights reserved.  
//  
  
#import   
  
  
@protocol IRect  
  
- (CGRect) getRect;  
  
@end 

Ahora hacemos que nuestras clases usen ese protocolo e implementamos el método:

//  Disparo.m  
- (CGRect) getRect{  
    CGFloat px = self.position.x;  
    CGFloat py = self.position.y;  
    CGFloat ancho = 12;  
    CGFloat alto = 21;  
    return CGRectMake(px, py, ancho, alto);  
}  
  
// Nave.m  
- (CGRect) getRect{  
    CGFloat px = self.position.x;  
    CGFloat py = self.position.y;  
    CGFloat ancho = 21;  
    CGFloat alto = 21;  
    return CGRectMake(px, py, ancho, alto);  
}

Creemos un método gestionarColosiones que llamaremos desde nuestro método logicaJuego:

// metodo para la gestión de colisiones entre naves y disparo  
- (void) gestionarColisiones{     
    Boolean colisionado = NO;  
    int tags = NAVES_FILAS * NAVES_COLUMNAS;  
    int numeroTag = 0;  
    Disparo *disparo = [self getChildByTag:ID_DISPARO]; // Recuperamos el disparo  
    while (!colisionado && numeroTag

Si probáis el juego ahora que se pueden destruir naves, vereís que cuando fallamos un disparo, si seguimos disparando no se destruyen las naves. Esto es porque el disparo se queda en la parte superior de la pantalla (aunque no se ve, podeís bajarle la altura un poco a la hora de crearlo y vereís como se queda ahí). Para arreglar esto, vamos a destruir los disparos que superen esa altura en un método que llamaremos desde logicaJuego.

// destruir los disparos que no acertaron  
- (void) destruirDisparos{  
    CGSize size = [[CCDirector sharedDirector] winSize];  
    Disparo *disparo = [self getChildByTag:ID_DISPARO];  
    if(disparo.position.y > size.height) {  
        [self removeChild:disparo cleanup:YES]; // borramos el disparo  
    }  
} 

Por último vamos a añadirle un fondo para que quede más bonito y vamos a crear un método que nos ponga un mensaje y reinicie la partida cuando se acabe. El fondo lo voy a añadir como si fuese otro CGSprite (seguramente se puede hacer de alguna forma mejor, pero se me acaba el tiempo para hacer el tutorial).

// añade el fondo  
- (void) addFondo{  
    [self addChild:[CCSprite spriteWithFile:(@"fondo.png")] z:-100000]; // lo añadimos diciendole  
                                                                        // un eje z bajo para que  
                                                                        // lo ponga debajo del resto de elementos  
  
}  
  
// método para la lógica del juego  
- (void) logicaJuego:(ccTime)dt{  
    // recuperar el tamaño de la pantalla  
    CGSize size = [[CCDirector sharedDirector] winSize];  
      
    // recuperamos las naves por su etiqueta y las movemos  
    for (int tag=0;tag size.width - TAM_IMAGEN){  
                nave.position = ccp(nave.position.x,nave.position.y-TAM_IMAGEN);  
                nave.isMovingRight = NO;  
            }  
        }else{  
            nave.position = ccp (nave.position.x -dt*50,nave.position.y);  
            if (nave.position.x < 0 + TAM_IMAGEN){  
                nave.position = ccp(nave.position.x,nave.position.y-TAM_IMAGEN);  
                nave.isMovingRight = TRUE;  
            }         
        }  
        if (nave.position.y < 0) // si las naves llegan hasta abajo, hemos perdido  
        {  
            [self reiniciarPartida:@"¡Has perdido"];      
        }  
    }  
    [self gestionarColisiones];  
    [self destruirDisparos];  
    if (totalNaves == 0) // si destruimos todas las naves, hemos ganado  
    {  
        [self reiniciarPartida:@"¡Has ganado"];   
    }     
      
}  
  
  
- (void) reiniciarPartida:(NSString *)mensaje{    
    // Crear mensaje  
    CCLabelTTF *label = [CCLabelTTF labelWithString:mensaje fontName:@"Marker Felt" fontSize:64]; // mensaje que dice ¡Has ganado!  
    CGSize size = [[CCDirector sharedDirector] winSize]; // Tamaño de la pantalla  
    label.position =  ccp( size.width /2 , size.height/2 ); // Centrar el mensaje  
    [self addChild:label]; // añadirl el mensaje a la capa  
    [[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:3.0f scene:[HelloWorld scene]]];  
    // Llamar al director para que sustituya esta escena por otra nueva, con una transicion de 3.0 segundos  
} 

En el caso de perder, la transición no funciona porque las naves siguen moviendose, para arreglar esto, vamos a destruir todos los hijos de la capa antes de añadir el mensaje de fin de partida.

- (void) reiniciarPartida:(NSString *)mensaje{    
    // Crear mensaje  
    CCLabelTTF *label = [CCLabelTTF labelWithString:mensaje fontName:@"Marker Felt" fontSize:64]; // mensaje que dice ¡Has ganado!  
    CGSize size = [[CCDirector sharedDirector] winSize]; // Tamaño de la pantalla  
    label.position =  ccp( size.width /2 , size.height/2 ); // Centrar el mensaje  
    [self removeAllChildrenWithCleanup:YES]; // eliminar todos los hijos  
    [self addChild:label]; // añadirle el mensaje a la capa  
    [[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:3.0f scene:[HelloWorld scene]]];  
    // Llamar al director para que sustituya esta escena por otra nueva, con una transicion de 3.0 segundos  
} 

Pues ya hemos terminado nuestra aplicación, combiene borrar los mensajes que estabamos poniendo en el log para depurarla, ya que no los vamos a seguir usando. Y tambien sería conveniente mirar la gestión de memoria, y arreglar algún bug que sigue teniendo la aplicación.


5. Conclusiones.

Como dije al principio, la aplicación era sencilla pero espero que os haya servido para obtener unos conocimientos básicos de cocos2d. Y todavía habría que aprender a gestionar la memoria, que es algo muy importante cuando se programa para moviles. Pero bueno, como primera aplicación tampoco esta del todo mal.

Si quereís hacer algún comentario, sugerencia o preguntar alguna duda podeís hacerlo en la zona de comentarios.

Un saludo.

César López.

1 COMENTARIO

  1. Genial, maravilloso, estaba que me tiraba de los pelos buscando algún tutorial en cocos2d para crear juegos, todos los que he visto son en inglés y no me enteraba a penas… Hoy es la primera vez que he visitado la página y tengo que felicitaros, enhorabuena y seguiré por aquí.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad