Bygg et Caterpillar spill med Cocos2D Sprites & Spillebord

I denne serien vil vi gjenskape det populære Atari-spillet Centipede ved hjelp av Cocos2D-spillmotor for iOS. Dette er den andre avgiften, hvor du lærer å sette opp spillbrettet og fylle det med sprites.

Hvor vi forlot ...

I den første opplæringen i denne serien viste jeg deg hvordan du laster ned, installerer og lager et nytt Cocos2D-prosjekt. Jeg viste deg også hvordan du kan forberede dine eiendeler ved å bruke Texture Packer og laste dem inn i spillet ditt.

I dag vil jeg gå inn i mer detalj om å sette opp brukergrensesnittet for spillet og tegne spillbrettet.

Trinn 1: Spiringsfeltet - Spillobjekt

I den tradisjonelle versjonen av centipede-spillet ble du brukt til å definere lekeområdet til tusenbenet. Vi skal bruke den samme ideen og skape det jeg kaller spirer.

Før vi kan begynne å snakke om noen av "i-spill-objektene", må vi definere en superklasse som vil inneholde noen av de vanlige metodene og egenskapene som hvert objekt vil trenge. Dette vil spare oss litt redundant kode i fremtiden.

Opprett en ny fil som heter GameObject.m, og i hovedfilen skriver du følgende kode:

 #import "cocos2d.h" #import "GameLayer.h" @interface GameObject: NSObject @property (ikkeatomisk, behold) CCSprite * sprite; @property (ikkeatomisk, tilordne) GameLayer * gameLayer; @property (nonatomic) CGPoint posisjon; - (id) initWithGameLayer: (GameLayer *) lag; - (CGRect) getBounds; @slutt

Her er en kort beskrivelse av hver av eiendommene her. Jeg vil gå nærmere om metodene når jeg viser deg implementeringen.

  • sprite: Hvert objekt i spillet trenger noe for å visuelt representere det. Dette blir sprite / bildeobjektet som brukes til å tegne _this_-objektet.
  • gameLayer: Bare en referanse til hovedspillet. Dette vil være nyttig for å svare på kollisjoner, samt å la objekter legge til egne sprites til tegneområdet.
  • stilling: Plasseringen av dette objektet i spillverdenen.

La oss ta en titt på implementeringen. Åpne GameObject.m og legg til følgende kode:

 #import "GameObject.h" @implementation GameObject @synthesize sprite = _sprite; @synthesize gameLayer = _gameLayer; @synthesize posisjon = _position; - (void) dealloc [_sprite release]; [super dealloc];  - (id) initWithGameLayer: (GameLayer *) lag if (self = [super init]) self.gameLayer = layer;  returner selv;  // 1 - (void) setPosition: (CGPoint) posisjon _position = posisjon; _sprite.position = posisjon;  // 2 - (CGRect) getBounds CGSize size = [self.sprite contentSize]; return CGRectMake (self.position.x - size.width * self.sprite.anchorPoint.x, self.position.y - size.height * self.sprite.anchorPoint.y, size.width, size.height);  @slutt

1. Siden objektets posisjon er forskjellig fra spriteens stilling, må vi overskrive setteren for stillingsegenskapen for å sikre at sprite blir flyttet på skjermen.

2. Denne metoden vil bli brukt litt senere når vi implementerer kollisjonsdeteksjonen. Det bestemmer grunnleggende grenseboksen for objektet og returnerer det.

Trinn 2: Spirefeltet - Spireobjekt

Nå som vi har GameObject på plass, blir Sprout-objektet ganske enkelt. Det eneste målet for spirene er å tjene som et hinder for spilleren og tusenbenet.

Opprett en ny GameObject-underklasse som heter Sprout. Igjen, vi trenger å underklasse GameObject for å få noen av disse boilerplate egenskaper og metoder.

Åpne Sprout.h og legg til følgende kode:

 #import "GameObject.h" @interface Sprout: GameObject @property (nonatomic, assign) NSInteger lever; @slutt

Det eneste tillegget Sprout-objektet gjør til GameObject er at det har a bor eiendom. Når spilleren skyter spire, vil vi få det til å slå et visst antall ganger (i dette tilfellet 3) før det forsvinner.

Implementeringen av et Sprout-objekt er også ganske forenklet. Legg til følgende kode i Sprout.m.

 #import "Sprout.h" #import "GameConfig.h" @implementation Sprout @synthesize lives = _lives; - (id) initWithGameLayer: (GameLayer *) lag // 1 hvis (selv = [super initWithGameLayer: lag]) // 2 self.sprite = [CCSprite spriteWithSpriteFrameName: @ "sprout.png"]; // 3 [self.gameLayer.spritesBatchNode addChild: self.sprite]; // 4 self.lives = kSproutLives;  returner selv;  // 5 - (void) setLives: (NSInteger) lever _lives = lives; self.sprite.opacity = (_lives / 3.0) * 255;  @slutt

1. Siden vi overskriver initWithGameLayer metode, vi må også ringe superklasse-versjonen av det også. Dette vil sikre at alt blir satt opp riktig.

2. Dette setter opp vår nåværende sprite til en sprite som kommer ut av vår CCSpriteBatchNode med navnet sprout.png.

3. Legger sprite til batch node laget som skal trekkes.

4. Setter antall liv av spire til kSproutLives konfigurasjonsinnstilling (defineres i et øyeblikk)

5. Vi vil at brukeren får noen tilbakemelding når de treffer en spire og noen idicator om hvor mye helse er igjen. Jeg har gjort dette ved å slippe opaciteten med en tredje gang det er blitt rammet.

Som nevnt i 4, må vi sette opp noen grunnleggende spillkonfigurasjon. Heldigvis har Cocos2D gitt en fil som heter GameConfig.h for å gjøre nettopp det. Åpne den og legg til denne linjen:

 #define kSproutLives 3

Nå som vi har opprettet et grunnleggende Sprout-objekt, kan vi bruke dette til å generere et helt felt for spillet vårt.

Trinn 3: Spiringsfeltet - tilfeldige steder

Kartet for spillet vårt vil bli generert tilfeldig med et minimum av spire i spillet til enhver tid. For å gjøre dette må vi holde rede på hvor vi allerede har plassert spirer. Også antallet spire som legges til kartet, vil være basert på dagens nivå: jo mer spire på kartet, desto vanskeligere er spillet for spilleren. Så, åpne GameLayer.h og legg til en ivar og to egenskaper. Jeg skal bare vise tilleggene i stedet for å repostere koden som vi tidligere skrev.

 #import "GameConfig.h" @interface GameLayer: CCLayer // 1 BOOL _locations [kRows] [kColumns];  // 2 @property (ikkeatomisk, tilordne) NSInteger nivå; @property (nonatomic, behold) NSMutableArray * spire // ... Andre egenskaper ... 

1. Dette er en 2D-serie med boolske verdier som brukes til å betegne hvor i rutenettet vi allerede har plassert Sprout-objekter. Sann betyr at man eksisterer og falsk betyr at man ikke gjør det.

2. Dette er dagens nivå av spillet. Mange andre hendelser vil stole på denne egenskapen også. Den andre egenskapen inneholder en rekke Sprout-objekter som er i gang. Sørg for å @synthesize disse egenskapene i implementasjonsfilen, samt frigjøre spiralarrayet. Dette er siste gang jeg nevner syntetisere og slippe ut for egenskaper. Fra nå av kommer jeg til å anta at du allerede vet det! ;)

En annen ting å merke seg er at du må importere GameConfig.h for å få tilgang til kRows og kColumns-konstantene. Ikke bekymre deg for disse for nå, vi vil definere dem litt senere.

Åpne nå GameLayer.m. Start med å legge til et privat grensesnitt for GameLayer øverst, slik at vi kan definere noen få metoder vi skal bruke. Sørg også for å legge til importeringserklæringen for Sprout.h, da vi vil gjøre mye bruk av det her.

Over @gjennomføring linje, legg til følgende kode:

 #import "Sprout.h" @interface GameLayer (Privat) - (CGPoint) randomEmptyLocation; - (ugyldig) stedRandomSprout; @slutt

Dette er to hjelpemetoder vi skal bruke for å sette opp vårt felt. Jeg vil gå litt mer detaljert om dem under gjennomføringen.

La oss nå legge til følgende kode nederst på init-metoden din (innsiden av hvis ((selv = [super init])) uttalelse braces.

 // 1 self.level = 1; // 2 for (int i = 0; i < kRows; i++)  for(int j = 0; j < kColumns; j++)  _locations[i][j] = NO;   // 3 srand(time(NULL)); _sprouts = [[NSMutableArray alloc] init]; for(int i = 0; i < kStartingSproutsCount; i++)  // 4 [self placeRandomSprout]; 

1. Start spillet på nivå 1

2. Gå over _locations, initialiser dem alle, og sett dem til NEI

3. Loop kStartingSproutsCount tider og sted tilfeldige spirer på rutenettet

Denne koden introduserer noen flere konstanter som vi ennå ikke har definert. Før vi går videre, la oss definere disse konstantene og noen få flere i vår GameConfig.h-fil.

 #define kGridCellSize 16 #define kColumns 18 #define kRows 20 #define kStartingSproutsCount 50 #define kGameAreaStartX 24 #define kGameAreaStartY 64 #define kGameAreaHeight 353 #define kGameAreaWidth 288

Det siste trinnet her er å implementere de to metodene som vi definerte ovenfor. Legg til følgende metoder i GameLayer.m-filen din:

 - (void) placeRandomSprout // 1 Sprout * sprout = [[Sprout alloc] initWithGameLayer: self] autorelease]; CGPoint p = [self randomEmptyLocation]; // 2 sprout.position = ccp (p.x * kGridCellSize + kGameAreaStartX, (kGameAreaStartY - kGridCellSize + kGameAreaHeight) - p.y * kGridCellSize - kGridCellSize / 2); // 3 [self.sprouts addObject: sprout];  - (CGPoint) randomEmptyLocation int kolonne; int rad; BOOL funnet = NEI; // 1 mens (! Funnet) // 2 kolonne = (arc4random ()% kColumns); rad = (arc4random ()% kRows); // 3 hvis (! _ Steder [rad] [kolonne]) funnet = YES; _locations [rad] [kolonne] = JA;  // 4 returnere CGPointMake (kolonne, rad); 

placeRandomSprout

1. Vi starter med å initialisere et nytt Sprout-objekt og hente et tilfeldig sted for å sette det. På dette stadiet er plasseringen i rutenett, vi må konvertere den til verdensrommet for å tegne det.

2. Vi gjør noen grunnleggende matte her for å kartlegge denne spire fra rutenett til verdensrommet basert på størrelsen på avspillingsområdet og cellestørrelsen.

3. Til slutt blir Sprout-objektet lagt til i spekteret av levende spirer

randomEmptyLocation

1. Denne sløyfen vil ikke fullføres før vi finner en tom plassering. Jeg antar at det skulle være en tid da det ikke finnes noen plasseringer, og det vil fortsette for alltid, men i det stadiet vil spillet være litt latterlig, der ingen spiller burde kunne spille. En optimalisering kan være å sjekke inn her for å sikre at hver plass ikke fylles først.

2. Velg en tilfeldig rad og kolonne

3. Sjekk om det finnes en spire på det stedet, hvis ikke, oppdatere _locations Ivar og varsle løkken for å bryte.

4. Til slutt returnerer vi bare det nye punktet som ble opprettet.

På dette tidspunktet, hvis du kjører koden din, bør du se spirefeltet opprettet og spredt rundt på avspillingsområdet. Jeg har også gjort det slik at spire aldri gyter i første eller siste rekke for å gjøre ting interessant for spilleren og larven.

Ditt spill burde nå se slik ut:

Trinn 4: Spillerobjektet

Det neste logiske trinnet i å skape grensesnittet vårt er å bygge Player-objektet. Spillerobjektet vil igjen være ekstremt ganske enkelt takket være hjelp fra vår superklasse.

Opprett en ny GameObject-underklasse som heter spiller og lim inn følgende kode i Player.h-filen.

 #import "GameObject.h" @interface Player: GameObject @property (nonatomic) NSInteger lever; @property (nonatomic) NSInteger score; @slutt

En spiller legger liv og scorer til base GameObject. Vi vil bruke disse egenskapene til å vise de resterende livene samt poengsummen i neste avsnitt.

Nå åpner du Player.m og legger til følgende kode:

 #import "Player.h" #import "GameLayer.h" @implementation Player @synthesize score = _score; @synthesize lives = _lives; - (id) initWithGameLayer: (GameLayer *) lag hvis (selv = [super initWithGameLayer: lag]) self.sprite = [CCSprite spriteWithSpriteFrameName: @ "player.png"]; [self.gameLayer.spritesBatchNode addChild: self.sprite];  returner selv;  @slutt

De initWithGameLayer Metoden for spilleren er nøyaktig den samme som Sprout-metoden, bortsett fra at den legger til et annet sprite i spillet.

Det er alt for Player-objektet. Det siste trinnet er å initialisere spilleren og legge ham til spillet.

Vi må først deklarere et Spillerobjekt i GameLayer.h. Pass på at du også importerer Player-objektet her også.

 @property (nonatomic, behold) Spiller * spiller;

Legg nå følgende kode direkte under koden du skrev i den siste delen init-metoden:

 _player = [[Player alloc] initWithGameLayer: self]; _player.lives = kPlayerStartingLives; _player.position = ccp (kGameAreaStartX + (kGameAreaWidth / 2), 88); _player.score = 0;

Igjen, dette introduserer en konstant som vi må legge til i GameConfig.h

 #define kPlayerStartingLives 3

Hvis du kjører spillet på dette punktet, bør du nå se den røde spillerplassen som er lagt til i spillet.

Trinn 5: Viser spillernes liv

Vi må gi spilleren en slags tilbakemelding om hvor mange liv de har igjen. Måten jeg har valgt for å implementere dette, er å vise spillerens sprite i øvre venstre hjørne. Så, hvis spilleren har 3 liv, vil den vise 3 røde firkanter.

Det er noen måter å håndtere dette kravet på. Jeg har valgt å opprettholde en rekke spiller sprites og bruke det til å styre visning av spillerens livstelling. Begynn med å legge til (du gjettet det) en annen eiendom til GameLayer-klassen som heter livSprites:

 @property (nonatomic, behold) NSMutableArray * livesSprites;

Deretter legger du til følgende kode i init-metoden:

 // init live sprites _livesSprites = [[NSMutableArray alloc] init]; // Registrer deg for livsmeldinger [[NSNotificationCenter defaultCenter] addObserver: selvvalg: @selector (updateLives :) navn: kNotificationPlayerLives objekt: null]; [[NSNotificationCenter defaultCenter] postNotificationName: kNotificationPlayerLives objekt: null];

Legg merke til at jeg har lagt til GameLayer som en observatør til et varsel kNotificationPlayerLives. Siden vi kanskje ikke reduserer spillerens liv inne i GameLayer, trenger vi en måte å gjøre brukergrensesnittet til å svare når spilleren blir truffet. Dette er den enkleste måten å få det til å skje. Legg også merke til at jeg sender meldingen med en gang. Siden denne metoden er ansvarlig for å tegne spillerens liv, må vi kalle det under init for å vise livene i utgangspunktet. Dette legger også til en annen konstant kalt kNotificationPlayerLives. Pass på at du legger til den i GameConfig.h-filen din. Vi skal gjøre det samme med spillerpoeng på et øyeblikk, så du kan også legge til konstantene for begge:

 #define kNotificationPlayerLives @ "PlayerLivesNotification" #define kNotificationPlayerScore @ "PlayerScoreNotification"

Nå skal vi implementere updateLives metode i vår kode. Denne metoden vil være ansvarlig for å tegne livene til spilleren og til slutt overgang til spillet over skjermen når spillerens liv går til 0. Legg til følgende metode i GameLayer.m-filen din:

 - (void) updateLives: (NSNotification *) varsel NSInteger lifeCount = self.player.lives; // 1 [self.livesSprites enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stopp) [self.spritesBatchNode removeChild: obj cleanup: YES]; ]; [self.livesSprites removeAllObjects]; // 2 for (int i = 0; i < lifeCount; i++)  CCSprite *sprite = [CCSprite spriteWithSpriteFrameName:@"player.png"]; sprite.position = ccp(kGameAreaStartX + (i * kGridCellSize * 2), 435); [self.livesSprites addObject:sprite]; [self.spritesBatchNode addChild:sprite];  

1. Først fjerner vi helt ut livSprites array og fjerner sprites fra scenen.

2. Deretter løp vi basert på livet spilleren har forlatt, lag en ny sprite, og legg den til scenen.

Nå som dette er fullført, kan du kjøre din søknad, og du bør se spillernes liv langs toppen:

Trinn 6: Viser spillernes poengsum

Denne prosessen kommer til å være svært lik det vi bare gjorde med spillerens liv, bare vi skal bruke en CCLabelTTF for å vise spillerens poengsum. CCLabelTTFs brukes i Cocos2D for å vise tekst i spillene dine.

Start med å åpne GameLayer.h og legge til følgende eiendom:

 @property (nonatomic, behold) CCLabelTTF * playerScoreLabel;

Deretter går du inn i init-metoden til GameLayer.m og initierer etiketten og varslene med følgende kode:

 _playerScoreLabel = [[CCLabelTTF labelWithString: @ "0" dimensjoner: CGSizeMake (100, 25) justering: UITextAlignmentRight fontName: @ "Helvetica" fontSize: 18] retain]; _playerScoreLabel.position = ccp (254,435); [self addChild: _playerScoreLabel]; [[NSNotificationCenter defaultCenter] addObserver: selvvelger: @selector (updateScore :) navn: kNotificationPlayerScore objekt: null]; [[NSNotificationCenter defaultCenter] postNotificationName: kNotificationPlayerScore objekt: null];

Denne koden skal se veldig rett frem med forklaring på spillerens liv. Det neste trinnet er å implementere updateScore metode for å få poengsummen til å vise. Legg til følgende metode i GameLayer.m-filen din:

 - (void) updateScore: (NSNotification *) varsel [self.playerScoreLabel setString: [NSString stringWithFormat: @ "% d", self.player.score]]; 

En CCLabelTTF fungerer veldig lik en UILabel i UIKit. Du setter strengen og du er god å gå! Nå, når vi oppdaterer poengsummen i spillet, sender vi bare det poengmeldingen og brukergrensesnittet oppdateres tilsvarende.

Den endelige versjonen av spillet ditt skal se slik ut:

Prosjektet begynner virkelig å se ut som et spill nå!

Neste gang

Den neste opplæringen vil dekke den mest komplekse (og mest morsomme) delen av denne serien. Vi vil implementere caterpillar-objektet sammen med dets AI.

Glad koding!