Nuestro primer juego en cocos2d

Vamos a empezar a aprender cocos2d y para ello lo mejor es empezar con un sencillo juego .. un mata mata de avioncitos, veremos como dibujar sprites implementar acciones, explosiones, usar un Joystick, sonidos, transiciones … uhhhh de todo de todo


Cuando empecé a ver el cocos2d me puse a buscar por internet tutoriales o algunas demos interesantes, y llegué a esta página muy recomendable donde además había un tutorial para hacer tu primer juego:
 How to make a simple iPhone Game with cocos2d tutorial
Este tutorial cubre muchos aspectos básicos de cocos2d y os recomiendo que le echéis un vistazo también, antes o después de este, el orden da igual … porque prácticamente me he basado en la misma sencillez/efectividad de Ray Wenderlich .. pero intentando aportar algo distinto.

Capas y Sprites

Empezamos por crear un proyecto de cero partiendo del template base de cocos2d y sobre esto vamos a ir añadir objetos a nuestro juego. El principio será bastante rápido y el código es suficientemente explicativo.

En la mayoría de ejemplos que encontraremos de cocos2d se añaden todos los objetos a la misma capa (en self, que normalmente hereda de un CCLayer), y todos se muestran uno encima de otro en el orden que se añaden. Nosotros vamos a empezar por tener esto bien organizado y vamos a crear 4 capas, luego cada objeto irá en su correspondiente capa, así no nos llevaremos sorpresas al final del proyecto que nos obliguen a reestructurar todo. De esta forma vamos a garantizar que nuestras explosiones y nuestros FX estarán siempre por encima de todo:

  • l1: En esta capa dibujaremos únicamente el fondo, en principio solo vamos a meter una imagen de backgroud, todo lo que metamos en esta capa estará siempre debajo de nuestras naves
  • l2: capa destinada para nuestros sprites, aqui es donde se crearán todas las bombas y misiles … petardos varios, ademas en esta capa meteremos las explosiones.
  • l3: esta capa solo será ocupada por nuestro player, de este forma nuestro player siempre estará por encima de las explosiones y de los misiles.
  • l4: Nuestra ultima capa solo tendrá los botones del joystick y el score.

Comenzamos por declarar unos cuantos objetos en la interfaz de nuestro objeto:

 C |  copy code |? 
01
 
02
 @interface CataPum : CCLayer 
03
 { 
04
 CCLayer *l1, *l2, *l3, *l4; // 4 capas para nuestros gráficos 
05
 CCSprite *background; // Sprite para el fondo 
06
 CCSprite *player; // nuestro player 
07
 CGPoint velocity; // un vector para guardar la velocidad 
08
 } 
09
 
10
 +(id) scene; 
11
 
12
 -(void) BuildBackground; 
13
 -(void) BuildJoystick; 
14
 -(void) BuildPlayer; 
15
 

Y a continuación el cuerpo de los métodos, empezamos por modificar nuestro init y crear todos los objetos, creamos las 4 capas y las añadimos a la vista principal (que no es mas que otra capa). A continuación pasaremos a construir el fondo y nuestra nave, a partir de este momento crearemos cada objeto en su capa. (aqui los graficos, el background y el player)

 C |  copy code |? 
01
 
02
 -(id) init 
03
 { 
04
 if( (self=[super init] )) { 
05
 
06
 //Creamos 4 capas y las añadimos 
07
 l1 = [CCLayer node]; // background 
08
 l2 = [CCLayer node]; // sprites del juego (bombas y misiles) 
09
 l3 = [CCLayer node]; // Player 
10
 l4 = [CCLayer node]; // Joystick y puntuacion 
11
 
12
 // añadimos las 4 capas 
13
 [self addChild:l1]; 
14
 [self addChild:l2]; 
15
 [self addChild:l3]; 
16
 [self addChild:l4]; 
17
 
18
 // construimos los distintos objetos 
19
 [self BuildBackground]; 
20
 [self BuildJoystick]; 
21
 [self BuildPlayer]; 
22
 } 
23
 return self; 
24
 } 
25
 
26
 -(void) BuildBackground 
27
 { 
28
 // Cargamos una imagen y la dejamos de background 
29
 CGSize winSize = [[CCDirector sharedDirector] winSize]; 
30
 
31
 background = [CCSprite spriteWithFile:@"background.jpg"]; 
32
 background.position = ccp(winSize.width/2, winSize.height/2); 
33
 [l1 addChild:background]; // añadimos el sprite a la capa l1 
34
 } 
35
 
36
 -(void) BuildJoystick 
37
 { 
38
 // de momento no hacemos nada aquí 
39
 } 
40
 
41
 -(void) BuildPlayer 
42
 { 
43
 CGSize winSize = [[CCDirector sharedDirector] winSize]; 
44
 
45
 player = [CCSprite spriteWithFile:@"Player.png"]; 
46
 player.position = ccp(winSize.width/2, winSize.height/2); 
47
 [l3 addChild:player]; // capa 3 para nuestro player 
48
 } 
49
 

Y ya está!! ya tenemos nuestro escenario y nuestra nave peleona

Vamos a tener que familiarizarnos mucho con este objecto: CCDirector es como el master del universo del cocos2d, es quien controla todas las capas y renderiza los objetos. En principio solo lo vamos a usar para que nos de el tamaño de la ventana:

 C |  copy code |? 
1
CGSize winSize = [[CCDirector sharedDirector] winSize];

Lo vamos a necesitar siempre que queramos centrar un objeto en pantalla, o cuando queramos moverlos hasta un área no visible, cosa que en este juego se va a usar mucho.

Joystick

Ya tenemos nuestro player en pantalla .. así que ahora solo nos falta movernos. Cocos2d no incluye de serie un control para hacer un joystick (de momento) y por ahora solo lo encontraremos como una clase Extra que trae. Pero por desgracia no lo llevan muy actualizado y tiene un par de fallitos. En el post anterior ya expliqué como usar esta clase, asi que os remito a ‘Joystick en cocos2d‘ para ver como funciona esta clase, de todos modos os vinculo aquí los sources para incluir al proyecto:
 Joystick.h v1.1 (2.29 Kb)
 Joystick.m v1.1 (4.45 Kb)

Así que ahora llega el momento de completar el método BuildJoystick que habíamos dejado a medias, también el sprite del stick:

 C |  copy code |? 
01
 
02
 -(void) BuildJoystick 
03
 { 
04
 //	Primero cargamos un gráfico que nos representará el joystick 
05
 CCSprite *joystick1 = [CCSprite spriteWithFile:@"JoyStick.png"]; 
06
 joystick1.position = ccp(60, 60); 
07
 [l4 addChild:joystick1]; // <- Lo añadimos a la capa 4 (encima de todo) 
08
 
09
 // creamos el objeto joystick en el cuadrado indicado 
10
 joystick = [[Joystick alloc] initWithRect:CGRectMake(0, 0, 120, 120)]; 
11
 
12
 // y le indicamos donde está su centro 
13
 [joystick setStaticCenter:60 y:60]; 
14
 } 
15
 

Con esto cargamos el sprite del Stick y lo ponemos en su sitio, y configuramos el joystick para que nos calcule el movimiento al pulsar sobre esa zona. Nuestro stick mide 120×120 pixels, así que lo ponemos en esa posición y configuramos esa área al objeto, indicándole también que el centro del mismo está en la coordenada (60,60):

Very Important: Añadimos en nuestro método ‘init’ estas lineas para indicarle que el touch está activado y que así podamos recibir los eventos necesarios:

 C |  copy code |? 
1
 
2
 // Con esto le decimos a cocos2d que nos de los eventos del touch 
3
 self.isTouchEnabled = YES; 
4
 // Y con esto configuramos la vista para que admita multitouchs 
5
 [[[CCDirector sharedDirector] openGLView] setMultipleTouchEnabled:YES];

Y ahora nos tocará añadir los 3 eventos del touch y dárselos a la clase del joystick para que internamente calcule si algún dedo se encuentra dentro de su cuadrado para asignar el movimiento:

 C |  copy code |? 
01
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
02
 { 
03
 [joystick touchesBegan:touches withEvent:event]; 
04
 } 
05
 -(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
06
 { 
07
 [joystick touchesMoved:touches withEvent:event]; 
08
 } 
09
 -(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
10
 { 
11
 [joystick touchesEnded:touches withEvent:event]; 
12
 }

A partir de este momento la clase Joystick ya almacena el estado el mismo y podemos consultar si se está pulsando alguna dirección, para ello contaremos con estos metodos:

  • -(CGPoint)getCurrentVelocity: Este método nos devolverá un CGPoint(con sus coordenadas X Y) indicándonos el punto donde estamos pulsando respecto del centro que le indicamos
  • -(CGPoint)getCurrentDegreeVelocity: Este método nos devolverá un CGPoint donde nos pondrá en el valor X el angulo (sobre la horizontal) y en la posición Y el modulo (osea, la distancia al centro)

Vamos a nuestro metodo init() y añadimos esta línea

 C |  copy code |? 
1
 [self schedule: @selector(updateJoystick:)];

Mediante esto le estamos diciendo al cocos2d que nos llame al metodo updateJoystick() cada vez que pueda (osea .. siempre)… el sample del joystick se explica como funciona este metodo, os recomiendo que lo mireis de nuevo si tenéis cualquier duda porque ahi se explica como crear un Schedule y que ventajas tienen sobre los NSTimer.

Ahora viene nuestra función updateJoystick que lo único que hace es tomar el estado del joystick (la velocidad) y se la pasa a nuestro Player … con lo que ya nos podemos mover por la pantalla:

 C |  copy code |? 
01
 
02
 -(void) updateJoystick: (ccTime) delta 
03
 { 
04
 CGPoint newPosition; 
05
 
06
 //sacamos la velocidad del joystick 
07
 CGPoint jvel = [joystick getVelocity:@"Stick1"]; 
08
 
09
 // y controlamos los limites 
10
 if(jvel.x<-60) jvel.x = -60; 
11
 if(jvel.x> 60) jvel.x =  60; 
12
 if(jvel.y<-60) jvel.y = -60; 
13
 if(jvel.y> 60) jvel.y =  60; 
14
 
15
 // incrementamos la velocidad a nuestro vector 
16
 // delta nos indica el tiempo desde la última vez que pasamos por esta funcion 
17
 // lo tomamos 
18
 velocity.x += jvel.x * delta; 
19
 velocity.y += jvel.y * delta; 
20
 
21
 // esto nos añade la resistencia necesaria para frenar el avion 
22
 // si dejamos de pulsar el joystick 
23
 velocity.x *= .9f; 
24
 velocity.y *= .9f; 
25
 
26
 // calculamos la nueva posición 
27
 newPosition.x = player.position.x + velocity.x; 
28
 newPosition.y = player.position.y + velocity.y; 
29
 
30
 // y una vez mas controlamos los limites para no salir de la pantalla 
31
 int sizex = player.contentSize.width / 2; 
32
 int sizey = player.contentSize.height / 2; 
33
 CGSize winSize = [[CCDirector sharedDirector] winSize]; 
34
 
35
 if(newPosition.x < sizex) newPosition.x = sizex; 
36
 if(newPosition.x > winSize.width -sizex) newPosition.x = winSize.width-sizex; 
37
 if(newPosition.y < sizey) newPosition.y = sizey; 
38
 if(newPosition.y > winSize.height -sizey) newPosition.y = winSize.height -sizey; 
39
 
40
 // ya está ... el player se mueve 
41
 [player setPosition:newPosition]; 
42
 
43
 }

Super truco del almendruco: Si además de mover nuestro sprite añadimos movimiento al background conseguiremos un bonito efecto (esa era la razón por la que nuestro background media tanto). Añadiremos también estas lineas al final del metodo updateJoystick:

 C |  copy code |? 
1
 
2
 int cx = winSize.width/2; 
3
 int cy = winSize.height/2; 
4
 background.position = ccp(cx + (cx-player.position.x), cy + (cy-player.position.y)); 
5
 

Enemigos

Ya tenemos nave y podemos movernos, ahora nos falta añadirle un poco de gracia al asunto, así que vamos a empezar a tirar bombas y a ver que ocurre después:

Añadimos esta nueva lína al init para que nos llame a este método cada segundo:

 C |  copy code |? 
1
[self schedule: @selector(createTargets:) interval:1.0];

Y aquí el correspondiente método para crear bombas: Este método saca una coordeanada X aleatoria y sitúa la bomba por encima de la pantalla (donde no podamos verla) y la movemos verticalmente hasta llegar a la parte inferior de la pantalla … todo ese desplazamiento en 2 segundos.

 C |  copy code |? 
01
 
02
 -(void) createTargets: (ccTime) delta 
03
 { 
04
 CGSize winSize = [[CCDirector sharedDirector] winSize]; 
05
 
06
 CCSprite *target = [CCSprite spriteWithFile:@"Bomb.png" ]; 
07
 
08
 // Buscamos una X aleatoria .. en lugar de ir de 0 a 480 
09
 // vamos a preocuparnos que ninguna bomba se salga por los limites latelares 
10
 int minSizeX = target.contentSize.width / 2; 
11
 int x = (arc4random() % (int)(winSize.width - minSizeX * 2) ) + minSizeX; 
12
 int y = winSize.height - target.contentSize.height / 2; 
13
 
14
 // El punto destino Y será abajo del todo 
15
 int destY = 0 - target.contentSize.height/2; 
16
 int duration = 2.0f; 
17
 
18
 // le damos la posición de origen y creamos las acciones con el movimiento 
19
 target.position = ccp(x,y); 
20
 
21
 // la acción de movimiento y el evento cuando termine esta acción 
22
 id action = [CCMoveTo actionWithDuration:duration position:ccp(x,destY)]; 
23
 id event  = [CCCallFuncN actionWithTarget:self selector:@selector(targetFinished:)]; 
24
 
25
 // y se las damos al petardaco para que lo ejecute 
26
 [target runAction:[CCSequence actions:action, event, nil]]; 
27
 
28
 [l2 addChild:target]; // <- lo añadimos a la capa 2 
29
 } 
30
 

Podemos ver que le estamos dando a la bomba una secuencia de acciones, en primer lugar le damos el ‘action’ que representa el movimiento, y como segundo parámetro le damos el evento que debe ejecutar a continuación. De esta forma haremos que cada bomba nos llame al evento targetFinished cuando se salga de pantalla, y podemos suprimirla:

 C |  copy code |? 
1
 
2
 -(void)targetFinished:(id)sender { 
3
 // este metodo se llama cada vez que una bomba termina su accion 
4
 // eliminamos el objeto de su capa 
5
 CCSprite *sprite = (CCSprite *)sender; 
6
 [l2 removeChild:sprite cleanup:YES]; 
7
 } 
8
 

Vamos a implementar también los disparos, para ello empezamos por añadir 2 botones al joystick, y en nuestro método updateJoystick implementar el disparo cuando cuando se pulsan los botones.
(aqui los sprites de los botones A y B)

 C |  copy code |? 
01
 
02
 -(void) BuildJoystick 
03
 { 
04
 CCSprite *Stick = [CCSprite spriteWithFile:@"Stick.png"]; 
05
 Stick.position = ccp(60, 60); 
06
 [l4 addChild:Stick]; // <- Lo añadimos a la capa 3 (overlay) 
07
 
08
 CCSprite *ButtonA = [CCSprite spriteWithFile:@"ButtonA.png"]; 
09
 ButtonA.position = ccp(480 - 42 - 84, 42); 
10
 [l4 addChild:ButtonA]; 
11
 
12
 CCSprite *ButtonB = [CCSprite spriteWithFile:@"ButtonB.png"]; 
13
 ButtonB.position = ccp(480 - 42, 42); 
14
 [l4 addChild:ButtonB]; 
15
 
16
 // creamos el objeto joystick 
17
 joystick = [[Joystick alloc] init]; 
18
 
19
 [joystick addStick:@"Stick1" rect:CGRectMake(0, 0, 120, 120)]; 
20
 [joystick addButton:@"A" rect:CGRectMake(ButtonA.position.x-32, ButtonA.position.y-32,64,64)]; 
21
 [joystick addButton:@"B" rect:CGRectMake(ButtonB.position.x-32, ButtonB.position.y-32,64,64)]; 
22
 [joystick setDelayFor:@"A" delay:0.5f]; 
23
 [joystick setDelayFor:@"B" delay:0.5f]; 
24
 } 
25
 

A continuación añadimos este codigo en nuestro metoo updateJoystick

 C |  copy code |? 
01
 
02
 
03
 if([joystick isDown:@"A"]) 
04
 [self PlayerShoot]; 
05
 
06
 if([joystick isDown:@"B"]) 
07
 { 
08
 [self PlayerShoot]; 
09
 [self PlayerShoot]; 
10
 } 
11
 

y nuestro metodo PlayerShoot para tirar Misiles viene a ser esto:

 C |  copy code |? 
01
 
02
 -(void) PlayerShoot 
03
 { 
04
 // Sacamos las coordenadas del avion 
05
 int x = player.position.x; 
06
 int y = player.position.y - 18; 
07
 
08
 // estas cordenadas son al centro del avion, asi que está bien 
09
 // tirar los petardos desde ahi .. unicamente vamos a variar la coordenada 
10
 // x para que cada petardo salga por un lado 
11
 static int disparoCount = 0; 
12
 disparoCount++; 
13
 if(disparoCount % 2 == 0) x+=15; 
14
 else x-=15; 
15
 
16
 // Creamos el petardaco y le damos la posición de inicio 
17
 CCSprite *petardaco = [CCSprite spriteWithFile:@"Misil.png" ]; 
18
 petardaco.position = ccp(x,y); 
19
 
20
 // la posición fin será la misma x y en Y vamos a subir hasta que no se vea 
21
 CGSize winSize = [[CCDirector sharedDirector] winSize]; 
22
 int posY = winSize.height + petardaco.contentSize.height / 2; 
23
 
24
 // calculamos la velocidad que queremos para el petardo 
25
 float len = posY - y; 
26
 float vel = 320/1; // 320pixels/1sec 
27
 float duration = len/vel; 
28
 
29
 // creamos el sprite con nuestro disparo 
30
 // la accion de movimiento y el evento cuando termine esta accion 
31
 id action = [CCMoveTo actionWithDuration:duration position:ccp(x,posY)]; 
32
 id event  = [CCCallFuncN actionWithTarget:self selector:@selector(petardacoFinished:)]; 
33
 
34
 // y se las damos al petardaco para que lo ejecute 
35
 [petardaco runAction:[CCSequence actions:action, event, nil]]; 
36
 
37
 [l2 addChild:petardaco]; 
38
 } 
39
 

Vemos que la lógica para crear los disparos es la misma que cuando hemos creado las bombas, lo único que tenemos que tener en cuenta es que en cocos2d todas las acciones se determinan con un tiempo de duración. Al tirar las bombas sabíamos que iban a recorrer toda la pantalla de forma vertical, por lo que todos recorrían el mismo espacio. Cada vez que disparamos vamos a crear un petardaco y lo vamos a mover hasta que salga por el limite superior de la pantalla, debemos saber la distancia en pixels que va a recorrer para ajustar el tiempo que debe tardar en recorrerlo, para ello este calculo:

 C |  copy code |? 
1
 
2
 float len = posY - y; 
3
 float vel = 320/1; // 320pixels/1sec 
4
 float duration = len/vel; 
5
 

Bueno .. el siguiente paso será implementar el método que se ejecuta cuando el petardaco llega a su destino y lo debemos eliminar del juego:

 C |  copy code |? 
1
 
2
 -(void)petardacoFinished:(id)sender { 
3
 CCSprite *sprite = (CCSprite *)sender; 
4
 [l2 removeChild:sprite cleanup:YES]; 
5
 } 
6
 

Es exactamente el mismo código cuando un misil salía de pantalla, podríamos haber usado el mismo metodo, pero no tiene mucho sentido ahora mismo optimizar en estos detalles.

Colisiones

Lo único que nos falta para poder echarnos las primeras partidas a nuestro juego es programar las colisiones de los objetos, tanto de las bombas con nuestra nave como la de los misiles. Cuando hemos creado las bombas y los misiles los hemos añadido a su capa y como ya tenían programado su movimiento nos hemos olvidado de ellos, confiamos en que cuando terminen su movimiento van a ser eliminados (tanto de la capa como de la memoria). Ahora queremos controlar las colisiones asi que no podemos perder la referencia a todas las bombas que hayan actualmente en juego. Para ello vamos a preparar un array donde meteremos las bombas a la hora de crearlas, y donde también las sacaremos al destruirse. Así luego podremos controlar mediante este array todas sus colisiones.

Empezamos por declarar estas 2 variables en el .h de nuestra clase:

 C |  copy code |? 
1
 
2
 NSMutableArray *arrayBomb; 
3
 NSMutableArray *arrayMisil;

A continuación las inicializamos en nuestro método init:

 C |  copy code |? 
1
 
2
 arrayBomb = [[NSMutableArray alloc] init]; 
3
 arrayMisil = [[NSMutableArray alloc] init];

Y luego deberemos añadir cada objeto a su correspondiente array en el metodo de creación, y el correspondiente remove al destruirse … viene a ser algo asi:

 C |  copy code |? 
01
 
02
 ... 
03
 // En el método createTargets justo después de añadir el 'target' a nuestra capa 2 
04
 // lo guardamos en su array: 
05
 [arrayBomb addObject:target]; 
06
 // y en el método 'targetFinished' justo después de borrar el 'sprite' de la capa 
07
 // lo quitamos del array 
08
 [arrayBomb removeObject:sprite]; 
09
 ... 
10
 
11
 // para nuestro metoto 'PlayerShoot' pasa lo mismo, creamos el disparo 
12
 // y lo guardamos en el array 
13
 [arrayMisil addObject:petardaco]; 
14
 // y del mismo modo lo borramos del array en su método 'petardacoFinished' 
15
 [arrayMisil removeObject:sprite]; 
16
 

De esta forma tenemos en todo momento los Sprites en juego facilmente accesibles en un array, y podemos saber donde se encuentran en cada momento. Ahora solo nos falta crear un metodo que se esté ejecutando constantemente y busque las colisiones de todos estos objetos. Para ello vamos al init y ponemos otro Scheduler

 C |  copy code |? 
1
 
2
 [self schedule: @selector(updateCollisions:)];

La logica al buscar colisiones, será recorrer todas las bombas del arrayBomb y sacamos el Rect que define el sprite, y luego anidamos otro bucle para recorrer el arrayMisil para ver si el Rect de la bomba colisiona con cada uno de los misiles. Cuando encontramos una colisión debemos destruir tanto la bomba como el misil.

Un punto MUY importante a tener en cuenta es que NUNCA es buena idea eliminar objetos de un array mientras lo estas recorriendo, porque eso casi se seguro te va a provocar saltos en los indices y siempre te dejarás algún objeto sin comparar. Así que lo que hacemos es declararnos un par de arrays temporales, y cada objeto que queramos suprimir lo vamos añadiendo a ese otro array, luego una vez hemos terminado el bucle que busca las colisiones solo tenemos que recorrer estos array para ver cuales de todos los sprites han sido marcados para borrar:

 C |  copy code |? 
01
 
02
 - (void)updateCollisions:(ccTime)dt 
03
 { 
04
 NSMutableArray *BombToDelete = [[NSMutableArray alloc] init]; 
05
 NSMutableArray *MisilToDelete = [[NSMutableArray alloc] init]; 
06
 
07
 // bucle para recorrer todas las bombas 
08
 for (CCSprite *Bomb in arrayBomb) 
09
 { 
10
 CGRect BombRect = CGRectMake(Bomb.position.x - (Bomb.contentSize.width/2), Bomb.position.y - (Bomb.contentSize.height/2), Bomb.contentSize.width, Bomb.contentSize.height); 
11
 
12
 // bucle para recorrer todos los misisles 
13
 for (CCSprite *Misil in arrayMisil) 
14
 { 
15
 CGRect MisilRect = CGRectMake(Misil.position.x - (Misil.contentSize.width/2),Misil.position.y - (Misil.contentSize.height/2),  Misil.contentSize.width, Misil.contentSize.height); 
16
 
17
 // comprobamos si los RECT de cada sprite colisionan 
18
 if (CGRectIntersectsRect(BombRect, MisilRect)) 
19
 { 
20
 [BombToDelete addObject:Bomb]; 
21
 [MisilToDelete addObject:Misil]; 
22
 } 
23
 } 
24
 } 
25
 
26
 // destruimos las bombas 
27
 for (CCSprite *Bomb in BombToDelete) 
28
 { 
29
 // Quitamos la bomba de su capa 
30
 [l2 removeChild:Bomb cleanup:YES]; 
31
 // la borramos del array porque ya no está en juego 
32
 [arrayBomb removeObject:Bomb]; 
33
 // creamos una explosion 
34
 [self CreateExplosion:Bomb.position.x y:Bomb.position.y]; 
35
 } 
36
 
37
 // destruimos los Misiles 
38
 for (CCSprite *Misil in MisilToDelete) 
39
 { 
40
 // Quitamos el misil de su capa, y de su array 
41
 [l2 removeChild:Misil cleanup:YES]; 
42
 [arrayMisil removeObject:Misil]; 
43
 } 
44
 
45
 // una vez hemos terminado ya podemos liberar los arrays temporales 
46
 [BombToDelete release]; 
47
 [MisilToDelete release]; 
48
 } 
49
 

Es muy facil de comprender, y lo único nuevo que hemos hecho ha sido definir un método CreateExplosion cada vez que se produce una colisión entre una bomba y un misil. Esta explosion va a ser un sprite animado que definimos a continuacion, empezamos por declarar de forma global este objeto:

 C |  copy code |? 
1
 
2
 CCSpriteBatchNode *batchNote;

Este Sprite será el grafico que tiene todos los frames de la explosion, en total 16. Tengo que admitir que esta parte no me gusta y no creo que esté muy bien implementada en cocos2d porque normalmente cuando haces una animación sabes de sobra que se va a repetir para un montón de objetos, no solo para 1. Y cocos2d te obliga a definir para cada uno de ellos el cuadrado del frame de cada sprite. He probado a crear el objeto CCAnimation de forma global y que todas las bombas usen el mismo (que seria lo lógico) pero no funciona, una vez que la primera explosion termina su animación y la eliminas, también se carga en cadena todos los frames que ha usado, con lo que la siguiente con lo que para la siguiente explosión te toca crear de nuevo los frames.

Quizá sea desconocimiento mio y hay algo que estoy usando mal … supongo que lo descubriré con el tiempo, pero de momento aqui teneis la explosión:

 C |  copy code |? 
01
 
02
 -(void)CreateExplosion:(int)x y:(int)y 
03
 { 
04
 // Cargamos el grafico con todos los frames 
05
 if(batchNote == nil) 
06
 { 
07
 batchNote = [CCSpriteBatchNode batchNodeWithFile:@"Explosion.png"]; 
08
 [l3 addChild:batchNote]; 
09
 } 
10
 
11
 // y creamos la animación con todos los frames 
12
 CCAnimation * animation = [CCAnimation animationWithName:@"explosion" delay:1.0f/24.0f]; 
13
 
14
 // ponemos cada uno de los 16 frames del gráfico 
15
 for (int y = 0; y < 4; y++) 
16
 for (int x = 0; x < 4; x++) 
17
 { 
18
 CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:batchNote.texture rect:CGRectMake(x*64,y*64,64,64) ]; 
19
 [animation addFrame:frame]; 
20
 } 
21
 
22
 CCSprite *explosion = [CCSprite spriteWithBatchNode:batchNote rect:CGRectMake(3*64,3*64,64,64)]; 
23
 explosion.position = ccp(x,y); 
24
 
25
 id anim = [CCAnimate actionWithDuration:1.0f animation:animation restoreOriginalFrame:NO]; 
26
 id event  = [CCCallFuncN actionWithTarget:self selector:@selector(explosionFinished:)]; 
27
 
28
 [explosion runAction:[CCSequence actions:anim, event, nil]]; 
29
 [batchNote addChild:explosion]; // <-- el sprite animado se añade a batchNode 
30
 } 
31
 
32
 

Y a continuación el evento que se realiza cuando la explosión ya ha hecho su animación. Fijaros bien que los sprites de las explosiones se añaden y se quitan de la capa ‘batchNode’ que esta es la que está en la capa3 … no intentéis añadir una animación a una capa directamente porque explota (nunca mejor dicho)

 C |  copy code |? 
1
 
2
 -(void)explosionFinished:(id)sender { 
3
 CCSprite *sprite = (CCSprite *)sender; 
4
 [batchNote removeChild:sprite cleanup:YES]; 
5
 }

Hasta aquí ya tenemos las colisiones con los objetos pero nos falta por implementar la colisión de las bombas con nuestra nave. Hay que destacar que estamos buscando colisiones a partir del rectángulo que define el sprite, y esto para las bombas y los misiles era funcional, pero para muestra nave no va a ser útil, tener en cuenta que nuestra nave, por su forma tiene un Rect bastante grande con muchas áreas vacías, sería muy dificil salir con vida si cualquier bomba tocase el Rect que define la nave, por eso vamos a trabajar con un Area manipulada a nuestras necesidades, mas pequeña.

En rojo podéis ver el Rect real que define el Sprite, y en negro el área de impacto que vamos a tener en cuenta:

Lo ideal en estos casos seria poder definir con detalle las areas de nuestra nave, aunque ello suponga tener que comparar 2 Rects. Así por ejemplo podríamos definir este caso mediante 2 rectángulos y podríamos ajustar con mucho detalle el perfil real de nuestra nave:

Bueno … sigamos, aquí va la rutina que buscaría si alguna de las bombas colisiona con nuestra nave, muy importante indicar que debemos realizarla después de haber borrado las bombas que habíamos marcado para borrar. Ya que si lo hacemos antes puede darse el caso que estemos colisionando con una bomba que también colisionaba con un misil … es cuestión de gustos saber que lógica se quiere implementar primero .. pero en nuestro caso vamos a ser buenos y consideramos que la bomba ya no existe y por tanto no nos puede matar:

 C |  copy code |? 
01
 
02
 // Sacamos el Rect de la nave - le restamos unos picos 
03
 CGRect PlayerRect = CGRectMake(player.position.x - (player.contentSize.width/2) -8, 
04
 player.position.y - (player.contentSize.height/2) - 15, 
05
 player.contentSize.width -16, 
06
 player.contentSize.height -15); 
07
 
08
 // Bucle para recorrer todas las bombas 
09
 for (CCSprite *Bomb in arrayBomb) 
10
 { 
11
 CGRect BombRect = CGRectMake(Bomb.position.x - (Bomb.contentSize.width/2) - 8, 
12
 Bomb.position.y - (Bomb.contentSize.height/2) -15, 
13
 Bomb.contentSize.width-16, 
14
 Bomb.contentSize.height-15); 
15
 
16
 // Miramos si colisiona con alguna bomba 
17
 if (CGRectIntersectsRect(BombRect, PlayerRect)) 
18
 { 
19
 // GAME OVER !!!!! 
20
 } 
21
 }

Con este código ya detectamos cuando una bomba colisiona con nuestra nave, solo nos falta crear otra Scena de Game Over y cambiar la vista. Esto lo veremos en la ultima parte .. de momento ya podemos jugar y matar, que tiene su gracia

Sonido

Siguiente paso: Sonido … por suerte cocos2d trae unas funciones que nos lo dan todo ya hecho y no tendremos que preocuparnos mucho, solo debemos conseguir unos buenos efectos especiales y una melodia marchosa, y colocarlo donde nos interese
 Sound.zip (1.01 Mb)

Comenzamos por poner la música de background en nuestro método init()

 C |  copy code |? 
1
 
2
 [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"catapum.mp3"];

Crear los efectos de audio será muy fácil, porque tenemos bien localizadas nuestras funciones que disparan y crean explosiones, será tan sencillo como añadir este código en su correspondiente método:

 C |  copy code |? 
01
 
02
 -(void) PlayerShoot 
03
 { 
04
 ... 
05
 [[SimpleAudioEngine sharedEngine] playEffect:@"shoot.caf"]; 
06
 } 
07
 
08
 -(void)CreateExplosion:(int)x y:(int)y 
09
 { 
10
 ... 
11
 [[SimpleAudioEngine sharedEngine] playEffect:@"bomb.caf"]; 
12
 } 
13
 

Es importante que tengáis muy localizado donde se inicializa la música y donde se para. Tener muy estructurado la secuencia de escenas que vais a hacer en un proyecto y donde se inicia la música ya que podemos dejar la musica sonar durante el cambio de escenas. Para este juego, por ejemplo, hemos iniciado la música en el método init(), ahora cuando nos maten vamos a irnos a una nueva escena con un Game Over en pantalla, podríamos dejar que la música siga sonando, pero tras el Game Over al volver de nuevo al juego pasamos de nuevo por el init, y la música se lanza de nuevo (haciendo un reset a su posición inicial) y notaremos el salto.

De momento es muy fácil de solucionar, basta con hacer un stop cuando cambiamos a la escena del Game Over, pero en un proyecto final hay que prestar mucha atención a estos detalles. La solución siempre pasa por hacer un buen esquema con la lógica de vuestras escenas, localizar bien la entrada y salida de cada una de ellas.

Score

Para la puntuación en pantalla vamos a intentar añadir dinamismo, está claro que nuestro score empezará en 0 e iremos añadiendo puntos a medida que consigamos nuestros éxitos, yo suelo ser bastante exigente a la hora de dar puntos … por ejemplo podemos hacer que el player consiga puntos solo por esquivar una bomba .. pero también podemos hacer que un disparo que no ha dado a ninguna bomba nos reste puntuación, así vamos a penalizar a los que se pongan a disparar a saco

No queremos que nuestra puntuación se incremente de golpe, para un score os recomiendo siempre que hagais toda la transición de números cada vez que sumamos.

Para ello empezamos por definir estas variables de forma global:

 C |  copy code |? 
1
 
2
 @interface Catapum : CCLayer 
3
 { 
4
 ... 
5
 int puntuacion; 
6
 int puntuacionVisible; 
7
 CCLabelTTF *labelScore; 
8
 } 
9
 

Y las inicializamos en el metodo init()

 C |  copy code |? 
1
 
2
 labelScore = [CCLabelTTF labelWithString:@"" fontName:@"Marker Felt" fontSize:32]; 
3
 labelScore.position =  ccp(labelScore.contentSize.width / 2 , winSize.height - labelScore.contentSize.height); 
4
 [labelScore setColor:ccBLACK]; 
5
 [l4 addChild:labelScore]; 
6
 puntuacion = 0; 
7
 puntuacionVisible=0; 
8
 

A continuación preparamos otro schedule que será el que nos actualice el Label que hemos puesto en la escena con el valor de la puntuación que tengamos en cada momento:

 C |  copy code |? 
1
 
2
 [self schedule: @selector(updateScore:)];

El siguiente paso será ir incrementando nuestra variable ‘puntuacion’ según los logros del player … vamos a dar 10 puntos cada vez que una bomba salga de pantalla sin que nos de, vamos a dar 100 puntos por cada bomba que destruimos, pero también vamos a quitarle 25 puntos por cada misil que dispare y no le de a ninguna bomba … así conseguiremos penalizar a los que se pongan a disparar a saco.

 C |  copy code |? 
01
 
02
 -(void)targetFinished:(id)sender { 
03
 ... 
04
 puntuacion += 10; 
05
 } 
06
 
07
 -(void)petardacoFinished:(id)sender { 
08
 ... 
09
 puntuacion -= 25; 
10
 } 
11
 
12
 - (void)updateCollisions:(ccTime)dt 
13
 { 
14
 ... 
15
 // destruimos las bombas 
16
 for (CCSprite *Bomb in BombToDelete) 
17
 { 
18
 ... 
19
 puntuacion+=100; 
20
 } 
21
 } 
22
 

La variable ‘puntuacion’ tendrá en todo momento la puntuación actual del player, pero esta NO va a ser la puntuación que mostramos en pantalla, si por ejemplo el player tiene 0 puntos y ahora consigue 100 no queremos cambiar de golpe de 0 a 100, queremos que el marcador se incremente poco a poco (pero rápido) hasta llegar a 100, para ello tenemos otra variable que se llama ‘puntuacionVisible’, en esta variable es donde pondremos el valor que queramos visible en pantalla.

Tenemos 2 lógicas posibles para ir incrementando esta variable

 C |  copy code |? 
1
 
2
 // Incrementar puntuacion de 1 en 1 
3
 if(puntuacion > puntuacionVisible) puntuacionVisible ++; 
4
 if(puntuacion < puntuacionVisible) puntuacionVisible --; 
5
 
6
 // Incrementar puntuacion de forma proporcional 
7
 int diferencia = puntuacion - puntuacionVisible; 
8
 puntuacionVisible += diferencia * 0.5f;

El primer método tiene la pega de que puede ser muy lento según lo facil que sea en nuestro juego conseguir puntuación .. imaginar por ejemplo que poneis un objetivo que te da 10.000 puntos extra, mediante esta forma de dar puntos estaríais 10.000 frames incrementando el score … y claro está que durante esos frames la puntuación ha ido creciendo, asi que al final el usuario NO ve la puntuación real que tiene.

El segundo método será mucho mas real, lo que hacemos es sacar la diferencia entre la puntuación visible y la real (pongamos el caso que tenemos 0 y la puntuación incremente 10.000). la diferencia es 10.000

 C |  copy code |? 
01
 
02
 [primera vuelta de buble] 
03
 int diferencia = 10000 - 0; 
04
 puntuacionVisible += 10000 * 0.5f; // incrementamos de golpe 5000 
05
 
06
 [segunda vuelta de buble] 
07
 int diferencia = 5000 - 0; 
08
 puntuacionVisible += 5000 * 0.5f; // incrementamos de golpe 2500 
09
 
10
 [tercera vuelta de buble] 
11
 int diferencia = 7500 - 0; 
12
 puntuacionVisible += 2500 * 0.5f; // incrementamos de golpe 1250 
13
 

De este método hacemos que el incremento de puntuación entre la real y la visible sea muy rápido cuanto mas distantes se encuentren, y a medida que nos acercamos a la puntuación real este incremento será cada vez más lento.
Este método tiene una única pega, y es que al ser nuestra variable de tipo int y multiplicar por un float corremos el riesgo de quedarnos siempre con 1 punto de diferencia sobre la puntuación final (por problemas de redondeo) … aunque este problema es muy fácil de solucionar si estamos atentos a ese caso:

 C |  copy code |? 
01
 
02
 - (void)updateScore:(ccTime)dt 
03
 { 
04
 // no queremos tener puntuaciones negativas 
05
 if(puntuacion < 0) puntuacion = 0; 
06
 
07
 // puntuacionVisible se incrementa con una parte proporcional 
08
 puntuacionVisible += (puntuacion - puntuacionVisible) * 0.3f; 
09
 
10
 // arreglamos los fallos de coma flotante cuando estemos a 1 punto de diferencia 
11
 if(puntuacionVisible == puntuacion-1 ) puntuacionVisible  = puntuacion; 
12
 if(puntuacionVisible == puntuacion+1 ) puntuacionVisible = puntuacion; 
13
 
14
 // pasamos el valor al Label de pantalla 
15
 NSString *stringScore = [NSString stringWithFormat: @"Score: %d", puntuacionVisible]; 
16
 [labelScore setString:stringScore]; 
17
 
18
 CGSize winSize = [[CCDirector sharedDirector] winSize]; 
19
 labelScore.position =  ccp(labelScore.contentSize.width / 2 + 4, winSize.height - labelScore.contentSize.height / 2 -4); 
20
 
21
 } 
22
 

Game Over !!!!

Finalmente llegamos a la escena del Game Over, en este ejemplo no me interesa mucho explicar como funcionan las escenas ni como hacer las transiciones, vamos a hacer lo básico. Crear una nueva clase para usarla como escena .. poner un label con el Game Over y la puntuación final obtenida y cambiarnos a esa escena:

Creamos una nueva clase herando de NSObject y la llamamos GamveOverScene. a continuación aqui teneis el .h y el .m correspondiente:

 C |  copy code |? 
01
 
02
 #import "cocos2d.h" 
03
 
04
 @interface GameOverLayer : CCColorLayer { 
05
 CCLabelTTF *_label1; 
06
 CCLabelTTF *_label2; 
07
 } 
08
 @property (nonatomic, retain) CCLabelTTF *label1; 
09
 @property (nonatomic, retain) CCLabelTTF *label2; 
10
 @end 
11
 
12
 
13
 @interface GameOverScene : CCScene { 
14
 GameOverLayer *_layer; 
15
 } 
16
 @property (nonatomic, retain) GameOverLayer *layer; 
17
 @end

 C |  copy code |? 
01
 
02
 #import "GameOverScene.h" 
03
 #import "CataPumScene.h" 
04
 
05
 @implementation GameOverScene 
06
 @synthesize layer = _layer; 
07
 
08
 - (id)init { 
09
 if ((self = [super init])) { 
10
 self.layer = [GameOverLayer node]; 
11
 [self addChild:_layer]; 
12
 } 
13
 return self; 
14
 } 
15
 
16
 - (void)dealloc { 
17
 [_layer release]; 
18
 _layer = nil; 
19
 [super dealloc]; 
20
 } 
21
 
22
 @end 
23
 
24
 @implementation GameOverLayer 
25
 @synthesize label1 = _label1; 
26
 @synthesize label2 = _label2; 
27
 
28
 -(id) init 
29
 { 
30
 if( (self=[super initWithColor:ccc4(0,0,0,255)] )) { 
31
 
32
 CGSize winSize = [[CCDirector sharedDirector] winSize]; 
33
 
34
 self.label1 = [CCLabelTTF labelWithString:@"X" fontName:@"Arial" fontSize:48]; 
35
 self.label2 = [CCLabelTTF labelWithString:@"X" fontName:@"Arial" fontSize:32]; 
36
 
37
 _label1.color = ccc3(255,255,255); 
38
 _label1.position = ccp(winSize.width/2, winSize.height/2 + _label1.contentSize.height/2 - 10); 
39
 
40
 _label2.color = ccc3(200,200,200); 
41
 _label2.position = ccp(winSize.width/2, winSize.height/2 - _label2.contentSize.height/2 - 10); 
42
 
43
 [self addChild:_label1]; 
44
 [self addChild:_label2]; 
45
 
46
 [self runAction:[CCSequence actions: 
47
 [CCDelayTime actionWithDuration:10], 
48
 [CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)], 
49
 nil]]; 
50
 
51
 } 
52
 return self; 
53
 } 
54
 
55
 - (void)gameOverDone { 
56
 [[CCDirector sharedDirector] replaceScene:[Catapum scene]]; 
57
 } 
58
 
59
 - (void)dealloc { 
60
 [_label1 release]; 
61
 [_label2 release]; 
62
 _label1 = nil; 
63
 _label2 = nil; 
64
 [super dealloc]; 
65
 } 
66
 @end 
67
 

Como veis se trata de una escena que tiene 2 labels, los pone en pantalla y ejecuta una secuencia de acciones:

 C |  copy code |? 
1
 
2
 [self runAction:[CCSequence actions: 
3
 [CCDelayTime actionWithDuration:10], 
4
 [CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)], 
5
 nil]]; 
6
 

Aquí lo que hacemos es encadenar estas 2 acciones: CCDelayTiem es un pause, la escena se queda 10 segundos parada y luego llama al método GameOverDone, que como podéis ver nos regresa a la escena del juego.

Observar que esta escena es muy básica, no se encarga de poner los textos, por lo que la podéis usar para imprimir un Game Over o cualquier mensaje genérico que queráis dar. Será nuestro juego quien establezca cada uno de los labels que quiere mostrar en pantalla, eso lo haremos cuando detectamos la colision entre una bomba y nuestra nave:

 C |  copy code |? 
01
 
02
 if (CGRectIntersectsRect(BombRect, PlayerRect)) 
03
 { 
04
 // Paramos el audio y lanzamos un fx de gameover 
05
 [[SimpleAudioEngine sharedEngine] stopBackgroundMusic]; 
06
 [[SimpleAudioEngine sharedEngine] playEffect:@"gameover.caf"]; 
07
 
08
 // GAME OVER !!!!! 
09
 // NSString * scoreStr = [self stringWithFormat:@"Score: %d", puntuacion]; 
10
 NSString stringScore = [NSString initWithFormat:@"Score: %d", puntuacion]; 
11
 
12
 // Creamos la escena y le damos los 2 strings, el GameOver y la puntuacion obtenida 
13
 GameOverScene *gameOverScene = [GameOverScene node]; 
14
 [gameOverScene.layer.label1 setString:@"GAME OVER"]; 
15
 [gameOverScene.layer.label2 setString:stringScore]; 
16
 
17
 // Cambiamos a la escena del game over 
18
 [[CCDirector sharedDirector] replaceScene:gameOverScene]; 
19
 } 
20
 
21
 

Desde os podeis descargar el proyecto completo
 Catapum.zip (1.97 Mb)

FIN!!!!!!

28 Comments

Jorge Vega  on December 27th, 2010

Magnifico tutorial. Que me guardo para mirarme cuando tenga tiempo.

Ahora lanzo una pregunta/reto desde mi puesto de programador principiante para MAC, ¿Y este juego como se podría hacer para ejecutar en MAC (ordenador)?

Creo que sería interesante para ver las diferencias en cuanto a código y librerías.

Un saludo y magnífico trabajo.
Jorge

Jorge Vega  on December 27th, 2010

He puesto un enlace a este tutorial en la nueva comunidad de programacion iPhone-Mac en castellano.

http://www.nscodecenter.com

Un saludo.

neofar  on December 27th, 2010

Pues en teoría las últimas versiones de cocos2d ya se pueden ejecutar en Mac, supongo que el 90% del código es portable con un copy&paste. Lo único que tendría que cambiarse sería el control del joystick.

Si alguien se anima que lo pruebe, un saludo Jorge y gracias por el comentario.

izqui  on December 27th, 2010

Tiene muy buena pinta, muy buena iniciativa.

Jorge Vega  on December 28th, 2010

Pues lo intentaré probar estos días. A ver si lo consigo.
Cambiare el joystick por los cursores del teclado.

Un saludo y feliz navidad.

Jaunty  on January 21st, 2011

Buenas.

1º Pedazo de blog. Me ha sido muy util en estos dias que he estado programando para un minivideojuego xD te doy la enhorabuena.

2º Tengo una duda con los efectos basicos: la secuencia, el moveBy… Pongo la duda aqui porque este ejemplo es el que mas se parece a mi duda.

He desarrollado el algoritmo A* con el cocos2d dandole un mapa en .tmx (si quieres paso el codigo para postearlo y que se vea)

El tema esta en que yo recibo un NSMutableArray con todas las casillas por las que hay que pasar y llamo al metodo moverPj dandole como parametro el vector.

- (void)muevePj:(NSMutableArray *)v sprite:(CCSprite *)pj{

//Movimiento de pj celda por celda. Hay que comprobar hacia donde movemos.

//speed
ccTime actualDuration = .1;

// Create the actions
id actionMoveDrcha = [CCMoveBy actionWithDuration:actualDuration
position:ccp(self.tileMap.tileSize.height,0)];

id actionMoveIzqda = [CCMoveBy actionWithDuration:actualDuration
position:ccp(-self.tileMap.tileSize.height,0)];

id actionMoveArriba = [CCMoveBy actionWithDuration:actualDuration
position:ccp(0,self.tileMap.tileSize.width)];

id actionMoveAbajo = [CCMoveBy actionWithDuration:actualDuration
position:ccp(0,-self.tileMap.tileSize.width)];

Celda *act = [v objectAtIndex:0];
Celda *sig;

for (int i = 0; i<[v count]; i++){

sig = [v objectAtIndex:i];

if(act.fila == sig.fila && act.columna sig.columna) [pj runAction:[CCSequence actions:[CCDelayTime actionWithDuration:1.0f],actionMoveIzqda, finishAction, nil]];
if(act.fila sig.fila && act.columna == sig.columna) [pj runAction:[CCSequence actions:[CCDelayTime actionWithDuration:1.0f],actionMoveArriba,finishAction,nil]];

act = sig;
}
}

Como se puede ver lo que hago es crear las acciones de movimiento e itero para ver el recorrido. El problema es que en la iteracion, cuando ejecuta [CCSequence runAction...] esta todavia moviendo el anterior y entonces no se mueve en el orden correcto. Tambien he intentado modificar el motor para que runActions: acepte un NSMutable (en el cual meteria la lista de acciones) pero es superlioso :S

Alguna idea despues de toda esta chapa de acer animaciones a base de acciones con diferentes runActions?? y que se complete el movimiento :P

Jaunty  on January 21st, 2011

Vale, despues de tanto escribir se me a puesto mal xD

en la iteracion, compruebo la diferencia de casilla con la siguiente y segun hacia donde tenga que ir (derecha, izq, arriba o abajo) lanzo un

[pj runAction:[CCSequence actions:actionMoveArriba,nil]];

Los delay fueron una prueba fallida :P

El objeto celda es la posicion en la que estoy. Celda = Tile

neofar  on January 22nd, 2011

OK .. mas o menos te he comprendido. tu tienes un mapa 2D con bloques, y quieres que el objeto se mueva en una ruta determinada … parecido a lo que seria mover el caballo en un juego de ajedrez. Eso?

Si es eso lo que estas intentando decir … te cuento que existe una acción para desplazar un objeto por determinados puntos. Es decir .. una acción que le das las 3 (o mas) coordenadas por donde tiene que pasar y el objeto se mueve haciendo esa ruta … así te ahorras tener que ir enlazando los desplazamientos uno a uno

Nunca lo he usado …. pero sería un buen punto para empezar a buscar, dime si es eso lo que estás intentando hacer o me estoy confundiendo.

Un saludo

Jaunty  on January 24th, 2011

Pues si, si le paso a la accion un array con los puntos, perfecto ^^

Como se llama la accion, no he visto nada de eso.

gracias x la ayuda ^^

cloyd  on February 8th, 2011

tengo una pregunta este proyecto fue creado para ios 4 ya que trato de compilar tu proyecto y me da muchos errores tengo xcode 3.1 y sdk ios 3 podrias hacer un tuto paso a paso para dummies

neofar  on February 10th, 2011

Pues la respuesta fácil sería que te actualizaras a la ultima versión del IOS :)

A ver … el código que yo he puesto es independiente del IOS, solo depende de cocos2d y no tiene ninguna vinculación con el hardware (mi juego digo). Otra cosa es que este código funciona sobre la ultima versión de cocos2d y éste si que trabaja sobre una base de IOS

Dime donde te están saliendo todos esos fallos .. si son en clases del cocos2d te diré que tienes pocas alternativas, tendrías que ir a la web del cocos2d y ver cual es la ultima versión que compila con el IOS que tengas

Un saludo

Cloyd  on March 7th, 2011

Ya he podido compilarlo excelente aun que al testear en el iPhone he notado que solo se puede o mover o disparar es decir una acción a la vez como se podría solucionar digo tal vez haciendo un layer para el pad y otro para los botones y tengo mas dudas se supone que el cocos2d se puede usar situaciones físicas como gravedad por medio del box2dme gustaría si pudieras poner un tuto al respecto te lo agradecería soy muy nuevo en esto de objective c y no encuentro información contundente me gustaría crear un tipo de Mario bros para iPhone

neofar  on March 7th, 2011

por defecto cocos2d no activa el touch en modo multi y solo recibe un touch, por eso no puedes disparar mientras te estás moviendo. Para ello tienes que activarlo en la vista de OpenGL

// Y con esto configuramos la vista para que admita multitouchs
[[[CCDirector sharedDirector] openGLView] setMultipleTouchEnabled:YES];

cloyd  on March 7th, 2011

ok gracias lo voy a tener en cuenta para futuras referencias, ahora en base a las reacciones fisicas que me puedes decir, sin duda este es el mejor sitio para cocos2d he estado buscando info y videotutoriales pero creo que son muy particulares este sitio es el mejor explicas paso a paso y llegando a un objetivo que es un demo jugable muchas gracias…

cloyd  on March 7th, 2011

ya cheque lo del multitouch agregando a Apdelegate esto [[director openGLView] setMultipleTouchEnabled:YES]; ahora el cocos soporta retina display con [director enableRetinaDisplay:YES]; y mi pregunta es que resolucion de imagenes se necesesitaria para adaptarla.

neofar  on March 7th, 2011

Pues todo x2. Retina display es 960×640 … así que ya sabes todos los gráficos el doble de grandes. Aun así ten en cuenta que si quieres que tu app se vea en el resto de dispositivos tienes que tener los gráficos en las 2 versiones, y cargar uno u otro en función de si ha podido activar la retina.

Ese será mi siguiente tuto así que te toca esperar :)

cloyd  on March 10th, 2011

saludos tengo unas dudas deseando me las resuelvas quisiera saber como puedo añadir animacion a los eventos del joystic, es decir si el avion se mueve a la izq cambiar la imagen por animacion y voltear la anim cuando se mueva a la derecha gracias…..

neofar  on March 17th, 2011

Buenas cloyd, la mejor forma de hacer lo que pides sería directamente en el método ‘updateJoystick’. Este método se está lanzando constantemente para ver los cambios del joystick, lo primero que hace es pedir el vector del desplazamiento del joystick:

CGPoint jvel = [joystick getVelocity:@"Stick1"];

En esta variable ya tienes el desplazamiento del stick en y en función de estos valores puedes cambiar el sprite del player

phynet  on March 31st, 2011

¡Hola!

Antes que nada quería agradecerte por tremendo tutorial que creo que me será muy útil para comenzar.

Ahora, tengo una pregunta técnica que no logro solventar.

Cuando ejecuto el código en el Xcode, me indica: Error from Debugger: failed to launch simulated application: Unknow Error.

Busqué a ver si faltaba algún archivo, pero todo se ve normal. Existe un warning y me muestra:

“animationWithName:delay is deprecated”

En la página de Cocos2D, indican que utilice “Animation” en vez de “animationWithName”, lo cual me deja desconcertada.

¿Por casualidad sabes qué está ocurriendo, y si puedo solventarlo? no logro saber qué sucede :(

¡Muchas Gracias!

Saludos.

alejandro  on June 12th, 2011

Soy un poco nuevo en esto de cocos 2d y no entiendo donde hay que poner esos codigos y demas

alejandro  on June 12th, 2011

Como el archivo de descarga del proyecto no me va pase todos los archivos a un proyecto nuevo borrando antes los anteriores y me da este error al hacer el build
Command /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/llvm-gcc-4.2 failed with exit code 1

Por favor ayudenme!!!!!!

oskxrt  on September 8th, 2011

Enhorabuena, apenas me voy tompando con este blog, me parece excelente este tutorial, espero mañana comenzarlo y les cuanto como me fue, un saludo a todos =)

TeTe  on October 19th, 2011

intente compilarlo con el iphone 4.3 simulator y 5.0 y no funciona, para que version funciona? gracias

Kmylol  on November 22nd, 2011

Tengo un problema al momento de agregar el Joystick, me dice que no encuentra el metodo initWithRect. Gracias

neofar  on November 23rd, 2011

Umm .. pues tienes razon, se me pasó el pasteo de un code antiguo, y en las clases del joystick cambié un poco los métodos para inicializarlo. Pásate por este post donde explico el funcionamiento del joystick (http://idev.ofcode.com/joystick-en-cocos2d/). Más o menos lo que falta es inicializar el “Stick1″

// Esto ya no se inicializa así
joystick = [[Joystick alloc] initWithRect:CGRectMake(0, 0, 120, 120)];
[joystick setStaticCenter:60 y:60];

// se hace asi
[joystick addStick:@"Stick1" rect:( 0,0,120,120)]; // un joystick a la izquierda

De hecho luego en el método updateJoystick se hace referencia a ese stick
//sacamos la velocidad del joystick
CGPoint jvel = [joystick getVelocity:@"Stick1"];

Asi que seria eso .. que se me pasó el copy&paste, en tener un rato lo corregiré en el post. Gracias por el aviso

Kmylol  on November 23rd, 2011

Muchas gracias por la respuesta man!! :D Tranquilo, no tienes que cambiar el post. Fue error mio, ya lo solucione y corre perfecto, es estoy aprendiendo y todavia cometo muchos errores de tipo. No lo cambies, por que al ver que no me corria hizo que me viera en la necesidad de leer el otro post del joystick y asi aprender un poco mas. Gracias por este genial aporte!

fofui  on February 2nd, 2012

Hola muchas gracias por el tutorial, estoy intentando seguirlo la verdad que no se nada de xcode estoy empezando con mac tengo el 4.2 y el iphone simulator 5, con el cocos2d el helloword si me ejecuta pero al seguir tu programa se me queda bloqueado en la imagen inicial del cocos2d y me detiene en el debuger en la linea :
int retVal = UIApplicationMain(argc, argv, nil, @”AppDelegate”);

fofui  on February 2nd, 2012

y me dice que:
“Program received signal: SIGABRT”
También tengo dos warning de uniqueidentifierdeprecated.

Muchas gracias a ver si me puedes echar una mano porque la verdad me gstaria continuar con el tutorial que esta muy bien.
PD: Solo realice la ptimera parte de crear las capas e introducir los sprites.

Leave a Comment