Crea un gioco Caterpillar con Cocos2D Rilevamento collisione

Questa è la sesta puntata della nostra serie di tutorial Cocos2D sulla clonazione di Centipede per iOS. Assicurati di aver completato le parti precedenti prima di iniziare.


Ultima volta…

Nell'ultimo tutorial, ti ho mostrato come creare una schiera di oggetti missilistici e farne un flusso costante. Hai anche imparato a conoscere l'interazione di base del giocatore in Cocos2D per spostare il giocatore.

Nel tutorial di oggi, esploreremo come impostare di base rilevamento delle collisioni in Cocos2D. Anche se questa non è sempre la soluzione ottimale, è sicuramente la più rapida e semplice da implementare.


Fase 1: collisione di missili / germogli

La collisione missilistica con i germogli è molto simile alla collisione del giocatore con i germogli. Controlliamo semplicemente i limiti di ciascun missile in gioco contro i limiti di ogni germoglio in gioco e determiniamo se vi è collisione. Quando un germoglio è stato colpito, decrementiamo la sua vita, ne modificiamo l'opacità e lo rimuoviamo se la sua vita raggiunge 0.

Apri Missile.m, importa Sprout.h e aggiungi il seguente codice alla fine del metodo di aggiornamento:

 CGRect missileRect = [self getBounds]; [self.gameLayer.sprouts enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Sprout * sprout = (Sprout *) obj; CGRect sproutRect = [sprout getBounds]; if (CGRectIntersectsRect (missileRect, sproutRect)) self.dirty = YES; sprout.lives--; ];

Man mano che ogni missile si aggiorna, enumera tutti i germogli in gioco, controllando se i loro reces di delimitazione si intersecano. Se c'è una collisione, impostiamo il missile su "sporco" e diminuiamo di conseguenza le vite del germoglio. Abbiamo già scritto il codice per modificare l'opacità dei germogli in base alle sue vite, quindi se esegui il gioco a questo punto, dovresti vedere i germogli che cambiano quando vengono colpiti. Il problema è che sono ancora in gioco. Dobbiamo rimuovere i germogli con 0 vite.

Questo può essere fatto all'interno del aggiornare: metodo in GameLayer.m. Apri GameLayer.m e aggiungi il seguente codice in fondo al file aggiornare: metodo:

 // 1 __block Sprout * deadSprout = nil; [self.sprouts enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Sprout * sprout = (Sprout *) obj; if (sprout.lives == 0) deadSprout = sprout; * stop = SÌ; ]; // 2 if (deadSprout) [self.spritesBatchNode removeChild: deadSprout.sprite cleanup: YES]; [self.sprouts removeObject: deadSprout]; 
  1. Enumeriamo ciascuno dei germogli in cerca di un morto. Per semplificare le cose, cancelliamo solo un germoglio per iterazione.
  2. Se esiste un germoglio morto, rimuoviamo il suo sprite dal nodo batch e rimuoviamo il germoglio dalla nostra matrice di germogli.

Ora esegui il gioco e vedrai i germogli che svaniscono quando colpiti e alla fine scompaiono.


Passaggio 2: collisione Caterpillar

Ora dobbiamo aggiungere il rilevamento delle collisioni tra il missile e il bruco. Questa interazione è ciò che rende davvero la tua app un gioco. Una volta colpito, il bruco dovrebbe dividersi nel segmento colpito e ogni nuovo "bruco" dovrebbe viaggiare in direzioni separate. Il segmento colpito è diventato un germoglio.

Inizia aprendo Missile.m, importando Caterpillar.h e Segment.h e aggiungi il seguente codice alla fine del file aggiornare: metodo:

 __block Caterpillar * hitCaterpillar = nil; __block Segment * hitSegment = nil; // 1 [self.gameLayer.caterpillars enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Caterpillar * caterpillar = (Caterpillar *) obj; [caterpillar.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Segment * segment = (Segment *) obj; CGRect segmentRect = [segmento getBounds]; // 2 if (CGRectIntersectsRect (missileRect, segmentRect)) self.dirty = YES; hitCaterpillar = [caterpillar retain]; hitSegment = [segment retain]; * stop = SÌ; ]; ]; // 3 if (hitCaterpillar && hitSegment) [self.gameLayer splitCaterpillar: hitCaterpillar atSegment: hitSegment]; [versione di hitSegment]; [rilascio di hitCaterpillar]; 
  1. Enumerare tutti i bruchi in gioco e enumerare ciascuno dei loro segmenti.
  2. Controlla la collisione e ricorda quale segmento del quale è stato colpito il bruco.
  3. Se abbiamo un colpo, dividiamo il bruco nel segmento corrente. Non ti preoccupare, implementeremo questo metodo a breve.

Ora che abbiamo il missile in collisione con il bruco, ci sono alcune cose che dobbiamo fare. Il primo è scrivere la logica per dividere il bruco su un dato segmento. Questo sarà fatto in GameLayer.m. Innanzitutto, apri GameLayer.h e aggiungi le seguenti dichiarazioni di classe forward.

 @class Caterpillar; @class Segment;

Quindi, dichiara il seguente metodo:

 - (vuoto) splitCaterpillar: (Caterpillar *) caterpillar atSegment: segmento (segmento);

Prima di iniziare con l'implementazione, è necessario aggiungere due file al progetto. Scarica NSArray + Reverse, decomprimilo e trascina entrambi i file nel tuo progetto. È semplicemente una categoria su NSMutableArray che ci offre un metodo inverso. Ora, apri GameLayer.m, importa Segment.h e NSArray + Reverse.h e implementa il seguente metodo:

 - (vuoto) splitCaterpillar: (Caterpillar *) caterpillar atSegment: segmento (Segmento *) // 1 if ([conteggio caterpillar.segmenti] == 1) [self.spritesBatchNode removeChild: segment.sprite cleanup: NO]; [self.caterpillars removeObject: caterpillar]; [self createSproutAtPostion: segment.position]; ritorno;  // 2 [self.spritesBatchNode removeChild: segment.sprite cleanup: NO]; // 3 [self createSproutAtPostion: segment.position]; // 4 NSInteger indexOfSegement = [caterpillar.segments indexOfObject: segment]; NSMutableArray * headSegments = [NSMutableArray array]; NSMutableArray * tailsSegments = [NSMutableArray array]; // 5 [caterpillar.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) if (idx < indexOfSegement)  [headSegments addObject:obj];  else if(idx > indexOfSegement) [tailsSegments addObject: obj]; ]; // 6 if ([tailsSegments count]> 0) // 7 [tailsSegments reverse]; // 8 Caterpillar * newCaterpillar = [[[allocazione Caterpillar] initWithGameLayer: segmenti self: tailsSegmenti livello: self.level] autorelease]; newCaterpillar.position = [[tailsSegments objectAtIndex: 0] position]; // 9 if (caterpillar.currentState == CSRight || caterpillar.previousState == CSRight) // Era diretto a destra se (caterpillar.currentState == CSDownLeft || caterpillar.currentState == CSDownRight) // Sta scendendo newCaterpillar .previousState = CSUpRight;  else // Sta andando su newCaterpillar.previousState = CSDownRight;  newCaterpillar.currentState = CSLeft;  else // Si stava dirigendo verso sinistra se (caterpillar.currentState == CSDownLeft || caterpillar.currentState == CSDownRight) // sta scendendo newCaterpillar.previousState = CSUpRight;  else // Sta andando su newCaterpillar.previousState = CSDownRight;  newCaterpillar.currentState = CSRight;  [self.caterpillars addObject: newCaterpillar];  // 10 if ([headSegments count]> 0) caterpillar.segments = headSegments;  else [self.caterpillars removeObject: caterpillar]; 
  1. Se colpiamo un bruco a segmento singolo (solo una testa), rimuoviamo quel bruco dal gioco e lo convertiamo in un germoglio.
  2. Rimuovere lo sprite del segmento colpito dal nodo batch.
  3. Converti il ​​segmento colpito in un germoglio (implementeremo questo metodo per un momento).
  4. Abbiamo bisogno di dividere il bruco in due matrici, la sezione della testa e la sezione della coda.
  5. Determina dove cadono gli altri segmenti (sezione testa o coda) e aggiungili agli array appropriati.
  6. Controlla se ci sono segmenti di coda.
  7. Invertire la matrice dei segmenti di coda. Questa è una categoria su NSMutableArray menzionata sopra. Abbiamo bisogno di invertire i segmenti per spostare il bruco nella direzione opposta.
  8. Crea un nuovo oggetto bruco usando la sezione della coda. Implementeremo questo metodo per un momento.
  9. Questo è il coraggio di questo metodo. Qui determiniamo la direzione corrente (sinistra o destra) e la direzione generale corrente (su o giù) del bruco che è stato colpito al fine di inviare il nuovo bruco nella direzione opposta.
  10. Se c'è ancora una testa a sinistra, abbiamo appena impostato il bruco che ha colpito i segmenti sui restanti segmenti della testa.

Wow, c'era molto da accettare. Sei ancora con me? Ci sono un paio di metodi che abbiamo usato qui e nel codice precedente che deve ancora essere implementato. Il primo metodo per implementare è createSproutAtPosition:. Aggiungi la seguente dichiarazione di metodo all'interfaccia privata nella parte superiore di GameLayer.m:

 - (Vuoto) createSproutAtPostion: posizione (CGPoint);

Ora, implementa il seguente metodo:

 - (void) createSproutAtPostion: (CGPoint) position // 1 int x = (position.x - kGameAreaStartX) / kGridCellSize; int y = (kGameAreaStartY - kGridCellSize + kGameAreaHeight + kGridCellSize / 2 - position.y) / kGridCellSize; // 2 Sprout * sprout = [[Sprout alloc] initWithGameLayer: self]; sprout.position = position; [self.sprouts addObject: sprout]; _locations [x] [y] = YES; 
  1. Questo traduce la nostra posizione sulle coordinate dello schermo alle coordinate della griglia. Usando la nostra abilità algebra di 8 ° grado, ne ricaviamo il codice di posizionamento scritto nella parte 2.
  2. Crea un nuovo germoglio e aggiungilo al gioco.

L'ultimo metodo che dobbiamo implementare per far funzionare tutto questo è initWithGameLayer: segmenti: Livello: nella classe del bruco. Questo metodo sarà responsabile della creazione di un nuovo bruco dai segmenti che sono stati passati. Apri Caterpillar.h e aggiungi la seguente dichiarazione di metodo:

 - (id) initWithGameLayer: (GameLayer *) segmenti di livello: (NSMutableArray *) livello dei segmenti: livello (NSInteger);

Ora apri Caterpillar.m e implementa il seguente metodo:

 - (id) initWithGameLayer: (GameLayer *) segmenti di livello: (NSMutableArray *) livello di segmenti: (NSInteger) level if (self = [super initWithGameLayer: layer]) self.segments = segments; self.level = level; self.currentState = CSRight; self.previousState = CSDownLeft; // imposta la posizione del resto dei segmenti __block int x = 0; __block Segment * parentSegment = [self.segments objectAtIndex: 0]; parentSegment.parent = nil; [self.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Segment * segment = (Segment *) obj; if (x ++> 0) if (! [segment isEqual: parentSegment]) segment.parent = parentSegment;  parentSegment = segment; ];  return self; 

Questo metodo è quasi identico al nostro precedente initWithGameLayer: Livello: Posizione: metodo. L'unica differenza è piuttosto che allocare un array di segmenti, imposta l'array dei segmenti sui segmenti in ingresso che vengono passati.

Vai avanti e avvia il gioco in questa fase. Dovresti essere in grado di uccidere completamente il bruco in gioco.


Passaggio 3: Collisione del giocatore Caterpillar

L'ultima cosa di cui abbiamo bisogno per completare il cerchio della vita di collisione è implementare le collisioni tra il bruco e il giocatore. Se qualche parte del bruco colpisce il giocatore, vogliamo ridurre il numero di vite del giocatore. In aggiunta a ciò, il giocatore diventerà invincibile per un breve momento, in modo che il bruco non divampi proprio attraverso di lui.

Inizia aprendo GameConfig.h e aggiungendo la seguente opzione:

 #define kPlayerInvincibleTime 15

Ora apri Caterpillar.m, importa Player.h e aggiungi il seguente codice in fondo al file aggiornare: metodo appena prima di impostare la posizione del bruco:

 static int playerInvincibleCount = kPlayerInvincibleTime; player BOOL staticoHit; CGRect playerRect = [self.gameLayer.player getBounds]; // 1 [self.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Segment * segment = (Segment *) obj; CGRect segmentRect = [segmento getBounds]; if (CGRectIntersectsRect (segmentRect, playerRect) && playerInvincibleCount == kPlayerInvincibleTime) * stop = YES; playerHit = YES; // 2 self.gameLayer.player.lives--; [[NSNotificationCenter defaultCenter] postNotificationName: kNotificationPlayerLives object: nil]; ]; // 3 if (playerHit) if (playerInvincibleCount> 0) playerInvincibleCount--;  else playerHit = NO; playerInvincibleCount = kPlayerInvincibleTime; 
  1. Enumerare ciascuno dei segmenti per determinare se si scontrano con il giocatore.
  2. Se il giocatore è stato colpito, decrementa le loro vite e pubblica la notifica. Questo dovrebbero si riflettono automaticamente nell'interfaccia utente in base al codice che hai scritto nella parte 3.
  3. Se il giocatore è stato colpito, controlla se erano ancora nel periodo invincibile. In caso contrario, resettare il contatore invincibile in modo che possano essere colpiti di nuovo.

Ora avvia il gioco e lascia che il bruco ti colpisca. Dovrebbe rimuovere una delle tue vite dalla parte superiore dello schermo.


Conclusione

Il rilevamento delle collisioni non è mai un compito facile e abbiamo solo graffiato la superficie. Se vuoi approfondire il rilevamento delle collisioni, dai un'occhiata a Box2D con Cocos2D.


La prossima volta

Nel prossimo e ultimo tutorial della serie, discuteremo del gioco polacco. Questo includerà le condizioni vincenti, punteggio, suoni, game over e punteggio alto.

Felice codifica!