Bygg et Caterpillar-spill med Cocos2D Kollisjonsdeteksjon

Dette er den sjette delen av vår Cocos2D opplæringsserie om kloning av Centipede for iOS. Pass på at du har fullført de forrige delene før du begynner.


Sist…

I den siste opplæringen viste jeg deg hvordan du lager en rekke rakettobjekter og brenner en konstant strøm av dem. Du lærte også om grunnleggende spillerinteraksjon i Cocos2D for å flytte spilleren.

I dagens veiledning vil vi undersøke hvordan du skal sette opp grunnleggende kollisjonsdeteksjon i Cocos2D. Selv om dette ikke alltid er den optimale løsningen, er det definitivt den raskeste og enkleste å implementere.


Trinn 1: Missile / Sprout Collision

Missil kollisjonen med spirer er mye som spilleren kollisjon med spirer. Vi kontrollerer bare grensene til hvert missil i lek mot grensene til hver spire i spill og avgjør om noen kolliderer. Når en spire er truffet, reduserer vi livet, endrer opaciteten, og fjerner det hvis livet når 0.

Åpne opp Missile.m, importer Sprout.h, og legg til følgende kode nederst i oppdateringsmetoden:

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

Som hver missil oppdateringer, det oppregner alle spirer i spill, kontroller for å se om deres avgrensende rettene krysser. Hvis det er en kollisjon, setter vi raketten til "skitten" og reduserer spruitens liv tilsvarende. Vi har allerede skrevet koden for å modifisere spiralens opasitet basert på dets liv, så hvis du kjører spillet på dette punktet, bør du se spirene endres når de blir rammet. Problemet er at de fortsatt er i spill. Vi må fjerne spirer med 0 liv.

Dette kan gjøres inne i Oppdater: metode i GameLayer.m. Åpne GameLayer.m og legg til følgende kode i bunnen av Oppdater: metode:

 // 1 __block Sprout * deadSprout = null; [self.sprouts enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stopp) Sprout * sprout = (Sprout *) obj; hvis (sprout.lives == 0) deadSprout = sprout; * Stopp = JA; ]; // 2 hvis (deadSprout) [self.spritesBatchNode removeChild: deadSprout.sprite cleanup: JA]; [self.sprouts removeObject: deadSprout]; 
  1. Vi teller hver av spirene på jakt etter en død. For å forenkle ting, fjerner vi bare en spire per iterasjon.
  2. Hvis en død spire eksisterer, fjerner vi sprite fra batch node og fjern spire fra vår spire.

Nå løp spillet og du vil se spirene fading når du treffer og til slutt forsvinner.


Trinn 2: Caterpillar Collision

Vi må nå legge til kollisjonsdeteksjon mellom missilet og larven. Denne interaksjonen er det som virkelig gjør appen din et spill. Når en gang er slått, bør larven splitte seg på slagssegmentet og hver ny "caterpillar" skal reise i separate retninger. Segmentet som ble rammet, blir så omgjort til en spire.

Start med å åpne Missile.m, importere Caterpillar.h og Segment.h, og legg til følgende kode nederst på siden Oppdater: metode:

 __block Caterpillar * hitCaterpillar = nil; __block Segment * hitSegment = nil; // 1 [self.gameLayer.caterpillars enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stopp) Caterpillar * caterpillar = (Caterpillar *) obj; [caterpillar.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stopp) Segment * segment = (Segment *) obj; CGRect segmentRect = [segment getBounds]; // 2 hvis (CGRectIntersectsRect (missileRect, segmentRect)) self.dirty = YES; hitCaterpillar = [caterpillar behold]; hitSegment = [segment behold]; * Stopp = JA; ]; ]; // 3 hvis (hitCaterpillar && hitSegment) [self.gameLayer splitCaterpillar: hitCaterpillar atSegment: hitSegment]; [hitSegment release]; [hitCaterpillar release]; 
  1. Oppsummer alle caterpillars i spill og oppsummer hver av segmentene.
  2. Sjekk etter et kollisjon og husk hvilket segment av hvilken caterpillar som ble truffet.
  3. Hvis vi har en hit, deler vi larven på det nåværende segmentet. Ikke bekymre deg, vi vil iverksette denne metoden innen kort tid.

Nå som vi har missilen kolliderer med larven, er det noen ting vi må gjøre. Den første er å skrive logikken for å dele larven på et gitt segment. Dette vil bli gjort i GameLayer.m. Først åpner du GameLayer.h og legger til følgende fremoverklassdeklarasjoner.

 @ klasse Caterpillar; @ klasse Segment;

Deretter erklæres følgende metode:

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

Før vi begynner med implementeringen, må vi legge til to filer til prosjektet. Last ned NSArray + Omvendt, pakke ut det, og dra begge filene inn i prosjektet. Det er rett og slett en kategori på NSMutableArray som gir oss en omvendt metode. Nå, åpne GameLayer.m, importere Segment.h og NSArray + Reverse.h, og implementer følgende metode:

 - (tomt) splitCaterpillar: (Caterpillar *) caterpillar atSegment: (Segment *) segment // 1 hvis ([caterpillar.segments count] == ​​1) [selv.spritesBatchNode removeChild: segment.sprite cleanup: NO]; [self.caterpillars removeObject: caterpillar]; [self createSproutAtPostion: segment.position]; komme tilbake;  // 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 * stopp) if (idx < indexOfSegement)  [headSegments addObject:obj];  else if(idx > indexOfSegement) [tailsSegments addObject: obj]; ]; // 6 hvis ([tailsSegments count]> 0) // 7 [halerSegmenter omvendt]; // 8 Caterpillar * newCaterpillar = [[[Caterpillar alloc] initWithGameLayer: selvsegmenter: halerSegmentnivå: self.level] autorelease]; newCaterpillar.position = [[tailsSegments objectAtIndex: 0] posisjon]; // 9 hvis (caterpillar.currentState == CSRight || caterpillar.previousState == CSRight) // Gikk rett hvis (caterpillar.currentState == CSDownLeft || caterpillar.currentState == CSDownRight) // Skal ned newCaterpillar .previousState = CSUpRight;  ellers // Er på vei opp newCaterpillar.previousState = CSDownRight;  newCaterpillar.currentState = CSLeft;  ellers // Var overskriften til venstre hvis (caterpillar.currentState == CSDownLeft || caterpillar.currentState == CSDownRight) // Skal ned newCaterpillar.previousState = CSUpRight;  ellers // Er på vei opp newCaterpillar.previousState = CSDownRight;  newCaterpillar.currentState = CSRight;  [self.caterpillars addObject: newCaterpillar];  // 10 hvis ([headSegments count]> 0) caterpillar.segments = headSegments;  ellers [self.caterpillars removeObject: caterpillar]; 
  1. Hvis vi treffer en enkelt segmenter caterpillar (bare et hode), fjerner vi denne larven fra spillet og konverterer den til en spire.
  2. Fjern sprite av segmentet som ble truffet fra batchnoden.
  3. Konverter hit-segmentet til en spire (vi vil implementere denne metoden kort tid).
  4. Vi må dele larven i to arrays, hodeseksjonen og halen.
  5. Bestem hvor de andre segmentene faller (hodet eller halen) og legg dem til de riktige skikkene.
  6. Sjekk om det er noen hale segmenter.
  7. Omvendt halen segmenter array. Dette er en kategori på NSMutableArray som ble nevnt ovenfor. Vi må reversere segmentene for å flytte larven i motsatt retning.
  8. Opprett et nytt larverobjekt ved hjelp av haleavsnittet. Vi vil implementere denne metoden øyeblikkelig.
  9. Dette er guts av denne metoden. Her bestemmer vi den nåværende retningen (venstre eller høyre) og nåværende overordnede retning (opp eller ned) av larven som ble truffet for å sende den nye larven i motsatt retning.
  10. Hvis det fortsatt er et hode igjen, satte vi bare larven som ble slått segmenter til de øvrige hodesegmentene.

Wow, det var mye å ta med. Er du fortsatt med meg? Det er et par metoder vi brukte her og i forrige kode som fortsatt må implementeres. Den første metoden for å implementere er createSproutAtPosition:. Legg til følgende metodedeklarasjon til ditt private grensesnitt øverst på GameLayer.m:

 - (Void) createSproutAtPostion: (CGPoint) posisjon;

Nå, implementer følgende metode:

 - (void) createSproutAtPostion: (CGPoint) posisjon // 1 int x = (position.x - kGameAreaStartX) / kGridCellSize; int y = (kGameAreaStartY - kGridCellSize + kGameAreaHeight + kGridCellSize / 2 - posisjon.y) / kGridCellSize; // 2 Sprout * sprout = [[Sprout alloc] initWithGameLayer: self]; sprout.position = posisjon; [self.sprouts addObject: spire]; _locations [x] [y] = YES; 
  1. Dette oversetter vår posisjon på skjermkoordinatene til nettkoordinater. Ved å bruke vår 8. klasse algebra skillz, utleder vi dette fra posisjonskoden som er skrevet i del 2.
  2. Lag en ny spire og legg den til i spillet.

Den siste metoden vi må implementere for å gjøre dette alt arbeid er initWithGameLayer: segmenter: Nivå: i caterpillar-klassen. Denne metoden vil være ansvarlig for å bygge en ny larve fra segmentene som ble sendt inn. Åpne Caterpillar.h og legg til følgende metodedeklarasjon:

 - (id) initWithGameLayer: (GameLayer *) lagsegmenter: (NSMutableArray *) segmentnivå: (NSInteger) nivå;

Nå, åpne Caterpillar.m og implementer følgende metode:

 - (id) initWithGameLayer: (GameLayer *) lagsegmenter: (NSMutableArray *) segmentnivå: (NSInteger) nivå hvis (selv = [super initWithGameLayer: layer]) self.segments = segmenter; self.level = level; self.currentState = CSRight; self.previousState = CSDownLeft; // sett posisjonen til resten av segmentene __block int x = 0; __block Segment * parentSegment = [self.segments objectAtIndex: 0]; parentSegment.parent = nil; [self.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stopp) Segment * segment = (Segment *) obj; if (x ++> 0) if (! [segment erEqual: parentSegment]) segment.parent = parentSegment;  parentSegment = segment; ];  returner selv; 

Denne metoden er nesten identisk med vår tidligere initWithGameLayer: Nivå: stilling: metode. Den eneste forskjellen er snarere enn å tilordne en rekke segmenter, den setter segmentgruppene til de innkommende segmentene som sendes inn i.

Gå videre og kjør spillet på dette stadiet. Du bør være i stand til å fullt ut drepe larven som er i spill.


Trinn 3: Spiller Caterpillar Collision

Den endelige tingen vi trenger for å fullføre kollisjonsdeteksjonssirkelen av livet er å implementere kollisjonene mellom larven og spilleren. Hvis noen del av larven treffer spilleren, vil vi redusere spillerens antall liv. I tillegg til at spilleren blir uovervinnelig i et kort øyeblikk, slik at larven ikke bare blar rett gjennom ham.

Start med å åpne GameConfig.h og legg til følgende alternativ:

 #define kPlayerInvincibleTime 15

Nå åpner du Caterpillar.m, importerer Player.h og legger til følgende kode nederst på siden Oppdater: metoden like før du setter larvens posisjon:

 statisk int playerInvincibleCount = kPlayerInvincibleTime; statisk BOOL playerHit; CGRect playerRect = [self.gameLayer.player getBounds]; // 1 [self.segments enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stopp) Segment * segment = (Segment *) obj; CGRect segmentRect = [segment getBounds]; hvis (CGRectIntersectsRect (segmentRect, playerRect) && playerInvincibleCount == kPlayerInvincibleTime) * stop = YES; playerHit = YES; // 2 self.gameLayer.player.lives--; [[NSNotificationCenter defaultCenter] postNotificationName: kNotificationPlayerLives objekt: null]; ]; // 3 hvis (playerHit) if (playerInvincibleCount> 0) playerInvincibleCount--;  ellers playerHit = NO; playerInvincibleCount = kPlayerInvincibleTime; 
  1. Oppsummer hver av segmentene for å avgjøre om de kolliderer med spilleren.
  2. Hvis spilleren ble truffet, redusere livene sine og legg inn varselet. Dette bør automatisk reflekteres i brukergrensesnittet basert på koden du skrev i del 3.
  3. Hvis spilleren ble truffet, kontroller om de fortsatt var i uovervinnelig periode. Hvis ikke, tilbakestill den uovervinnelige telleren slik at de kan komme igjen.

Nå løp spillet og la larven slå deg. Det bør fjerne et av livene dine fra toppen av skjermen.


Konklusjon

Kollisjonsdeteksjon er aldri en enkel oppgave, og vi har bare riper overflaten. Hvis du vil grave dypere inn i kollisjonssensor, ta en titt på å bruke Box2D med Cocos2D.


Neste gang

I neste og siste opplæring av serien, diskuterer vi polishen. Dette vil inkludere vinnende forhold, score, lyder, spill over og høy poengsum.

Glad koding!