Bouw een Caterpillar-game met Cocos2D Collision Detection

Dit is het zesde deel van onze Cocos2D-lessenreeks over het klonen van Centipede voor iOS. Zorg ervoor dat je de voorgaande delen hebt voltooid voordat je begint.


Laatste keer…

In de laatste zelfstudie heb ik je laten zien hoe je een reeks raket-objecten maakt en een constante stroom van ze afvuurt. Je hebt ook geleerd over elementaire spelerinteractie in Cocos2D om de speler te verplaatsen.

In de tutorial van vandaag zullen we onderzoeken hoe we kunnen instellen basis- botsingsdetectie in Cocos2D. Hoewel dit niet altijd de optimale oplossing is, is het zeker de snelste en gemakkelijkst te implementeren.


Stap 1: raket- / spruitbotsing

De raketbotsing met de spruiten lijkt veel op de botsing van de speler met de spruiten. We controleren eenvoudig de grenzen van elke raket in het spel tegen de grenzen van elke sprout in het spel en bepalen of er een botsen. Wanneer een spruit is geraakt, verlagen we de levensduur, passen we de dekking aan en verwijderen deze als de levensduur 0 is.

Open Missile.m, importeer Sprout.h en voeg de volgende code toe aan de onderkant van de updatemethode:

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

Terwijl elke raket wordt bijgewerkt, somt het alle spruiten op die in het spel zijn en wordt gecontroleerd of hun begrenzingsrechters elkaar kruisen. Als er een botsing is, zetten we de raket op "vuil" en verlagen de levens van de spruit dienovereenkomstig. We hebben de code al geschreven om de dekking van de spruiten aan te passen op basis van de levens, dus als je het spel nu uitvoert, zou je de spruiten moeten zien veranderen als ze geraakt worden. Het probleem is dat ze nog steeds spelen. We moeten kiemen verwijderen met 0 levens.

Dit kan gedaan worden in de bijwerken: methode in GameLayer.m. Open GameLayer.m en voeg de volgende code toe aan de onderkant van de bijwerken: methode:

 // 1 __block Sprout * deadSprout = nihil; [self.sprouts enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Sprout * sprout = (Sprout *) obj; if (sprout.lives == 0) deadSprout = ontspruiten; * stop = JA; ]; // 2 if (deadSprout) [self.spritesBatchNode removeChild: deadSprout.sprite opruimen: YES]; [self.sprouts removeObject: deadSprout]; 
  1. We inventariseren elk van de spruiten op zoek naar een dode. Om de zaken te vereenvoudigen, verwijderen we slechts één spruit per iteratie.
  2. Als er een dode spruit bestaat, verwijderen we de sprite van het batchknooppunt en verwijderen we de spruit uit onze spruitenreeks.

Voer het spel nu uit en je ziet de spruitjes vervagen wanneer ze worden geraakt en uiteindelijk verdwijnen.


Stap 2: Caterpillar Collision

We moeten nu botsingsdetectie toevoegen tussen de raket en de rups. Deze interactie maakt jouw app echt tot een spel. Eenmaal geraakt, zou de rups zich moeten splitsen in het hit-segment en elke nieuwe "rups" zou in verschillende richtingen moeten reizen. Het segment dat werd geraakt wordt vervolgens omgezet in een spruit.

Begin met het openen van Missile.m, importeer Caterpillar.h en Segment.h en voeg de volgende code toe aan de onderkant van de bijwerken: methode:

 __blok Caterpillar * hitCaterpillar = nil; __blok Segment * hitSegment = nihil; // 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 = [segment getBounds]; // 2 if (CGRectIntersectsRect (missileRect, segmentRect)) self.dirty = YES; hitCaterpillar = [rupsband behouden]; hitSegment = [segment behouden]; * stop = JA; ]; ]; // 3 if (hitCaterpillar && hitSegment) [self.gameLayer splitCaterpillar: hitCaterpillar atSegment: hitSegment]; [hitSegment-release]; [hitCaterpillar-release]; 
  1. Vermeld alle rupsen in het spel en inventariseer elk van hun segmenten.
  2. Controleer op een botsing en onthoud welk segment van die rups werd geraakt.
  3. Als we een hit hebben, splitsen we de rups op het huidige segment. Maak je geen zorgen, we zullen deze methode binnenkort implementeren.

Nu dat de raket tegen de rups botst, zijn er een paar dingen die we moeten doen. De eerste is om de logica te schrijven om de rups op een bepaald segment te splitsen. Dit gebeurt in GameLayer.m. Open eerst GameLayer.h en voeg de volgende forward class-declaraties toe.

 @class Caterpillar; @class-segment;

Verklaar vervolgens de volgende methode:

 - (void) splitCaterpillar: (Caterpillar *) caterpillar atSegment: (Segment *) segment;

Voordat we met de implementatie beginnen, moeten we twee bestanden aan het project toevoegen. Download NSArray + Reverse, pak het uit en sleep beide bestanden naar uw project. Het is gewoon een categorie op NSMutableArray die ons een omgekeerde methode geeft. Open GameLayer.m nu, importeer Segment.h en NSArray + Reverse.h en voer de volgende methode uit:

 - (void) splitCaterpillar: (Caterpillar *) caterpillar atSegment: (Segment *) segment // 1 if ([caterpillar.segments count] == ​​1) [self.spritesBatchNode removeChild: segment.sprite cleanup: NO]; [self.caterpillars removeObject: caterpillar]; [self createSproutAtPostion: segment.position]; terug te keren;  // 2 [self.spritesBatchNode removeChild: segment.sprite opruimen: NEE]; // 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 = [[[Caterpillar alloc] initWithGameLayer: self segments: tailsSegments level: self.level] autorelease]; newCaterpillar.position = [[tailsSegments objectAtIndex: 0] positie]; // 9 if (caterpillar.currentState == CSRight || caterpillar.previousState == CSRight) // Werd naar rechts geleid als (caterpillar.currentState == CSDownLeft || caterpillar.currentState == CSDownRight) // gaat naar beneden newCaterpillar .previousState = CSUpRight;  else // Staat op nieuwCaterpillar.previousState = CSDownRight;  newCaterpillar.currentState = CSLeft;  else // was op weg naar links als (caterpillar.currentState == CSDownLeft || caterpillar.currentState == CSDownRight) // gaat naar beneden newCaterpillar.previousState = CSUpRight;  else // Staat op nieuwCaterpillar.previousState = CSDownRight;  newCaterpillar.currentState = CSRight;  [self.caterpillars addObject: newCaterpillar];  // 10 if ([headSegments count]> 0) caterpillar.segments = headSegments;  else [self.caterpillars removeObject: caterpillar]; 
  1. Als we een rups met één segment raken (alleen een kop), verwijderen we die rups uit het spel en zetten deze om in een spruit.
  2. Verwijder de sprite van het segment dat werd geraakt van het batchknooppunt.
  3. Converteer het hitsegment naar een sprout (we zullen deze methode tijdelijk implementeren).
  4. We moeten de rups splitsen in twee reeksen, het hoofdgedeelte en het staartgedeelte.
  5. Bepaal waar de andere segmenten vallen (kop of staartgedeelte) en voeg ze toe aan de juiste arrays.
  6. Controleer of er staartsegmenten zijn.
  7. Keer de reeks staartsegmenten om. Dit is een categorie op NSMutableArray die hierboven werd genoemd. We moeten de segmenten omkeren om de rups in de tegenovergestelde richting te verplaatsen.
  8. Maak een nieuw rupsobject met behulp van het staartgedeelte. We zullen deze methode tijdelijk implementeren.
  9. Dit is het lef van deze methode. Hier bepalen we de huidige richting (links of rechts) en de huidige algemene richting (omhoog of omlaag) van de rups die werd geraakt om de nieuwe rups in de tegenovergestelde richting te sturen.
  10. Als er nog steeds een hoofd over is, plaatsen we de rups die de segmenten van de hit was, gewoon op de resterende kopsegmenten.

Wauw, dat was veel om op te nemen. Ben je nog steeds bij me? Er zijn een aantal methoden die we hier en in de vorige code hebben gebruikt die nog moeten worden geïmplementeerd. De eerste methode om te implementeren is createSproutAtPosition:. Voeg de volgende methodeverklaring toe aan uw persoonlijke interface bovenaan GameLayer.m:

 - (Void) createSproutAtPostion: (CGPoint) positie;

Implementeer nu de volgende methode:

 - (void) createSproutAtPostion: (CGPoint) positie // 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 = positie; [self.sprouts addObject: sprout]; _locaties [x] [y] = JA; 
  1. Dit vertaalt onze positie op de schermcoördinaten naar rastercoördinaten. Met behulp van onze algebra skillz van de 8e klas, leiden we dit af van de positioneringscode die in deel 2 is geschreven.
  2. Maak een nieuwe sprout en voeg deze toe aan het spel.

De laatste methode die we moeten implementeren om dit allemaal te laten werken is initWithGameLayer: segmenten: level: in de caterpillar-klasse. Deze methode zal verantwoordelijk zijn voor het bouwen van een nieuwe caterpillar uit de segmenten die zijn doorgegeven. Open Caterpillar.h en voeg de volgende methodeverklaring toe:

 - (id) initWithGameLayer: (GameLayer *) -segmenten: (NSMutableArray *) segmentsniveau: (NSInteger) -niveau;

Open nu Caterpillar.m en voer de volgende methode uit:

 - (id) initWithGameLayer: (GameLayer *) -segmenten: (NSMutableArray *) segmentsniveau: (NSInteger) niveau if (self = [super initWithGameLayer: layer]) self.segments = segments; self.level = niveau; self.currentState = CSRight; self.previousState = CSDownLeft; // stel de positie in van de rest van de segmenten __block int x = 0; __block Segment * parentSegment = [self.segments objectAtIndex: 0]; parentSegment.parent = nihil; [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; ];  terugkeer zelf; 

Deze methode is bijna identiek aan onze vorige initWithGameLayer: level: positie: methode. Het enige verschil is dat in plaats van het toewijzen van een array van segmenten, het de segmentarray instelt op de inkomende segmenten die worden doorgegeven.

Ga je gang en voer het spel in dit stadium uit. Je zou de rups die in het spel is volledig moeten kunnen doden.


Stap 3: Speler Caterpillar botsing

Het laatste dat we moeten doen om de botsingsdetectiekring van het leven te voltooien, is het implementeren van de botsingen tussen de rups en de speler. Als een deel van de rups de speler raakt, willen we het aantal levens van de speler verminderen. Bovendien wordt de speler voor een kort moment onoverwinnelijk, zodat de rups niet alleen door hem heen schiet.

Begin door GameConfig.h te openen en de volgende optie toe te voegen:

 #define kPlayerInvincibleTime 15

Open nu Caterpillar.m, importeer Player.h en voeg de volgende code toe aan de onderkant van de bijwerken: methode net voordat u de positie van de rups instelt:

 static int playerInvincibleCount = kPlayerInvincibleTime; statische BOOL spelerHit; CGRect playerRect = [self.gameLayer.player getBounds]; // 1 [self.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) Segment * segment = (segment *) obj; CGRect segmentRect = [segment getBounds]; if (CGRectIntersectsRect (segmentRect, playerRect) && playerInvincibleCount == kPlayerInvincibleTime) * stop = YES; playerHit = YES; // 2 self.gameLayer.player.lives--; [[NSNotificationCenter defaultCenter] postNotificationName: kNotificationPlayerLives-object: nul]; ]; // 3 if (playerHit) if (playerInvincibleCount> 0) playerInvincibleCount--;  else playerHit = NO; playerInvincibleCount = kPlayerInvincibleTime; 
  1. Opsommen elk van de segmenten om te bepalen of ze botsen met de speler.
  2. Als de speler werd geraakt, verlaag dan hun leven en plaats de melding. Deze moeten wordt automatisch weergegeven in de gebruikersinterface op basis van de code die je hebt geschreven in deel 3.
  3. Als de speler werd geraakt, controleer dan of ze zich nog in de onoverwinnelijke periode bevonden. Als dit niet het geval is, stelt u de onoverwinnelijke teller opnieuw in zodat deze opnieuw kan worden geraakt.

Voer het spel nu uit en laat de rups je raken. Het zou een van je levens moeten verwijderen vanaf de bovenkant van het scherm.


Conclusie

Botsingsdetectie is nooit een gemakkelijke taak en we hebben alleen het oppervlak bekrast. Als je dieper in botsingsdetectie wilt graven, bekijk dan Box2D met Cocos2D.


De volgende keer

In de volgende en laatste zelfstudie van de serie bespreken we de game polish. Dit omvat de winnende voorwaarden, score, geluiden, game-over en hoge score.

Happy Coding!