Å utvikle iOS-spill kan være en berikende opplevelse når det gjelder både personlige og finansiell vekst. Tidligere i år distribuerte jeg et Cocos2D-basert spill, Bi-løp , til App Store. Spillets spill er enkelt: en uendelig løper der spillere (i dette tilfellet bier) samler poeng og unngår hindringer. Se her for en demo.
I denne veiledningen vil jeg forklare prosessen bak utvikling av spill for iOS, fra Cocos2D til publisering. For referanse, her er en kort innholdsfortegnelse:
Før vi går inn i de grusomme detaljene, vil det være nyttig å forstå skillet mellom sprites og fysiske gjenstander.
For en hvilken som helst enhet som vises på skjermen til et endeløst løperspill, blir den grafiske representasjonen av den enheten referert til som en sprite , mens den polygonale representasjonen av den enheten i fysikkmotoren blir referert til som en fysisk objekt .
Så sprite er tegnet på skjermen, støttet av den tilsvarende fysiske gjenstanden, som deretter håndteres av din fysikkmotor. Dette oppsettet kan visualiseres her, hvor sprites vises på skjermen, med deres fysiske polygonale kolleger skissert i grønt:
Fysiske gjenstander er ikke koblet til deres respektive sprites som standard, noe som betyr at du som iOS-utvikler kan velge hvilken fysikkmotor som skal brukes og hvordan man kobler sammen sprites og kropper. Den vanligste måten er å underklasse standard sprite og legge til en konkret fysisk kropp til den.
Med det i tankene…
Cocos2D-iphone er et open source-rammeverk for iOS som bruker OpenGL for akselerasjon av grafikk for maskinvare og støtter Jordegern og Box2D fysikkmotorer.
Først av alt, hvorfor trenger vi et slikt rammeverk? Vel, til å begynne med implementerer rammeverk de ofte brukte komponentene i spillutviklingen. Cocos2D kan for eksempel laste sprites (spesielt sprite ark ( Hvorfor? )), start eller stopp en fysikkmotor, og håndter timing og animasjon riktig. Og det gjør alt dette med kode som er gjennomgått og testet grundig - hvorfor bruke din egen tid på å omskrive sannsynlig dårligere kode?
Kanskje viktigst, men - Cocos2D spillutvikling bruker akselerasjon av grafikkmaskinvare . Uten slik akselerasjon vil ethvert iOS-uendelig løperspill med til og med et moderat antall sprites kjøre med spesielt dårlig ytelse. Hvis vi prøver å lage en mer komplisert applikasjon, vil vi sannsynligvis begynne å se en 'bullet-time' -effekt på skjermen, dvs. flere kopier av hver sprite når den prøver å animere.
Til slutt optimaliserer Cocos2D minnebruk siden den cacher sprites . Dermed krever dupliserte sprites minimalt med ekstra minne, noe som åpenbart er nyttig for spill.
Etter all ros jeg har lappet på Cocos2D, kan det virke ulogisk å foreslå å bruke Storyboards . Hvorfor ikke bare manipulere objektene dine med Cocos2D, etc.? Vel, for å være ærlig, for statiske vinduer er det ofte mer praktisk å bruke Xcodes Interface Builder og Storyboard-mekanismen.
For det første lar det meg dra og plassere alle mine grafiske elementer for mitt endeløse løperspill med musen min. For det andre er Storyboard API veldig, veldig nyttig. (Og ja, jeg vet om Cocos Builder ).
Her er et raskt glimt av Storyboard:
Spillets hovedvisningskontroller inneholder bare en Cocos2D-scene med noen HUD-elementer på toppen:
Vær oppmerksom på den hvite bakgrunnen: det er en Cocos2D-scene som vil laste alle nødvendige grafiske elementer ved kjøretid. Andre visninger (live-indikatorer, løvetann, knapper osv.) Er alle standard Cocoa-visninger, lagt til skjermen ved hjelp av Interface Builder.
Jeg vil ikke dvele ved detaljene - hvis du er interessert, kan du finne eksempler på GitHub.
(For å gi litt mer motivasjon, vil jeg beskrive det endeløse løperspillet mitt litt mer detaljert. Hopp over denne delen hvis du vil gå videre til den tekniske diskusjonen.)
Under live-spilling er bien urørlig, og selve åkeren skynder seg faktisk og fører med seg ulike farer (edderkopper og giftige blomster) og fordeler (løvetann og deres frø).
Cocos2D har kameraobjekt som ble designet for å følge karakteren; i praksis var det mindre komplisert å manipulere CCLayer som inneholder spillverdenen.
Kontrollene er enkle: å trykke på skjermen flytter bien opp, og en annen trykk flytter den ned.
Verdenslaget i seg selv har faktisk to underlag. Når spillet starter, fylles det første underlaget fra 0 til BUF_LEN og vises først. Det andre underlaget fylles på forhånd fra BUF_LEN til 2 * BUF_LEN. Når bien når BUF_LEN, blir det første underlaget renset og umiddelbart befolket fra 2 * BUF_LEN til 3 * BUF_LEN, og det andre underlaget presenteres. På denne måten veksler vi mellom lag og beholder aldri foreldede objekter, en viktig del av å unngå minnelekkasjer.
Når det gjelder fysikkmotorer, brukte jeg Chipmunk av to grunner:
Fysikkmotoren ble egentlig bare brukt til påvisning av kollisjon. Noen ganger blir jeg spurt: 'Hvorfor skrev du ikke din egen kollisjonsdeteksjon?'. I virkeligheten er det ikke mye mening med det. Fysikkmotorer ble designet for nettopp disse formålene: de kan oppdage kollisjoner mellom kropper med kompliserte former og optimalisere den prosessen. For eksempel deler fysikkmotorer ofte verden i celler og utfører kollisjonskontroll bare for kropper i samme eller tilstøtende celler.
En nøkkelkomponent i uendelig uendelig løperspillutvikling er å unngå å snuble over små problemer. Tid er en viktig ressurs når du utvikler en app, og automatisering kan være utrolig tidsbesparende.
Men noen ganger kan automatisering også være et kompromiss mellom perfeksjonisme og å overholde fristen. I denne forstand kan perfeksjonisme være en Angry Birds-morder.
For eksempel, i et annet iOS-spill jeg for tiden utvikler, bygde jeg et rammeverk for å lage oppsett ved hjelp av et spesialverktøy (tilgjengelig på GitHub ). Dette rammeverket har sine begrensninger (for eksempel har det ikke fine overganger mellom scener), men ved å bruke det kan jeg lage scenene mine på en tidel av tiden.
Så selv om du ikke kan bygge ditt eget superramme med spesielle superverktøy, kan du fortsatt og bør automatisere så mange av disse små oppgavene som mulig.
Perfeksjonisme kan være en Angry Birds-morder. Tid er en viktig ressurs i iOS-spillutvikling. kvitringVed å bygge denne uendelige løperen var automatisering nøkkelen igjen. For eksempel vil artisten min sende meg grafikk med høy oppløsning gjennom en spesiell Dropbox-mappe. For å spare tid skrev jeg noen skript for automatisk å bygge filsett for de forskjellige måloppløsninger som kreves av App Store, og la til -hd eller @ 2x også (nevnte skript er basert på ImageMagick ).
Når det gjelder tilleggsverktøy, fant jeg ut TexturePacker for å være veldig nyttig - den kan pakke sprites inn i sprite ark, slik at appen din bruker mindre minne og lastes raskere, ettersom alle sprites blir lest fra en enkelt fil. Det kan også eksportere teksturer i nesten alle mulige rammeformater. (Merk at TexturePacker ikke er et gratis verktøy, men jeg synes det er verdt prisen. Du kan også sjekke ut gratis alternativer som ShoeBox .)
Den største vanskeligheten forbundet med spillfysikk er å lage egnede polygoner for hver sprite. Med andre ord, å skape en polygonal fremstilling av noen uklart formet bi eller blomst. Ikke engang prøve å gjøre dette for hånd - bruk alltid spesielle applikasjoner, som det er mange av. Noen er til og med ganske ... eksotiske - som å lage vektormasker med Inkspace og deretter importere dem til spillet.
For min egen endeløse utvikling av løperspill, opprettet jeg et verktøy for å automatisere denne prosessen (se her for riktig bruk), som jeg kaller Andengine Vertex Helper . Som navnet antyder, ble det opprinnelig designet for Andemotor rammeverk , selv om det vil fungere hensiktsmessig med en rekke formater i disse dager.
I vårt tilfelle må vi bruke plistmønsteret:
%.5f%.5f
Deretter oppretter vi en plist-fil med objektbeskrivelser:
jet_ant vertices -0.182620.08277 -0.14786-0.22326 0.20242-0.55282 0.470470.41234 0.038230.41234
Og en gjenstandslaster:
- (void)createBodyAtLocation:(CGPoint)location{ float mass = 1.0; body = cpBodyNew(mass, cpMomentForBox(mass, self.sprite.contentSize.width*self.sprite.scale, self.sprite.contentSize.height*self.sprite.scale)); body->p = location; cpSpaceAddBody(space, body); NSString *path =[[NSBundle mainBundle] pathForResource:@'obj _descriptions' ofType:@'plist']; // e = 0.7; shape->u = 1.0; shape->collision_type = OBJ_COLLISION_TYPE; cpSpaceAddShape(space, shape); }
For å teste ut hvordan sprites samsvarer med deres fysiske kropper, se her .
Mye bedre, ikke sant?
Oppsummert, alltid automatiser når det er mulig. Selv enkle skript kan spare deg for mye tid. Og viktigere, den tiden kan brukes til programmering i stedet for museklikking. (For ytterligere motivasjon, her er en token XKCD .)
Samlede blowballs i spillet fungerer som en valuta i appen, slik at brukerne kan kjøpe nye skinn til bien. Imidlertid kan denne valutaen også kjøpes med ekte penger. Et viktig poeng å merke seg med hensyn til fakturering i appen er om du trenger å utføre serversjekk for kjøpsgyldighet. Siden alle kjøpbare varer i det vesentlige er like når det gjelder spill (bare å endre bienes utseende), er det ikke nødvendig å utføre en serverkontroll for kjøpsgyldighet. I mange tilfeller må du imidlertid definitivt gjøre det.
For mer har Ray Wenderlich det perfekte faktureringsveiledning i appen .
I mobilspill, sosialisere er mer enn bare å legge til en Facebook 'Like' -knapp eller sette opp leaderboards. For å gjøre spillet mer spennende implementerte jeg en flerspillerversjon.
Hvordan virker det? For det første kobles to spillere til ved hjelp av iOS Game Center's sanntids matchmaking. Siden spillerne virkelig spiller det samme uendelige løperspillet, må det bare være et enkelt sett med spillobjekter. Det betyr at den ene spillerens forekomst må generere objektene, og den andre spillingen vil lese dem av. Med andre ord, hvis begge spilleres enheter genererte spillobjekter, ville det være vanskelig å synkronisere opplevelsen.
Med det i bakhodet, etter at forbindelsen er opprettet, sender begge spillerne hverandre et tilfeldig tall. Spilleren med det høyere tallet fungerer som “serveren” og skaper spillobjekter.
Husker du diskusjonen om porsjonert verdensgenerasjon? Hvor vi hadde to underlag, ett fra 0 til BUF_LEN og det andre fra BUF_LEN til 2 * BUF_LEN? Denne arkitekturen ble ikke brukt ved et uhell - det var nødvendig å levere jevn grafikk over forsinkede nettverk. Når en del av objekter genereres, blir den pakket inn i en plist og sendt til den andre spilleren. Bufferen er stor nok til å la den andre spilleren spille selv med en nettverksforsinkelse. Begge spillerne sender hverandre sin nåværende posisjon med en periode på et halvt sekund, og sender også sine opp-ned bevegelser umiddelbart. For å glatte ut opplevelsen korrigeres posisjon og hastighet hvert 0,5 sekund med en jevn animasjon, så i praksis ser det ut som den andre spilleren beveger seg eller akselererer gradvis.
Det er absolutt flere hensyn å gjøre når det gjelder endeløs løpende flerspiller, men forhåpentligvis gir dette deg en følelse for hvilke typer utfordringer det er involvert.
Spill er aldri ferdig. Riktignok er det flere områder der jeg vil forbedre mine egne, nemlig:
Verdenslaget flyttes ved hjelp av CCMoveBy-handlingen. Dette var greit når verdenslagets hastighet var konstant, siden CCMoveBy-handlingen ble syklet med CCRepeatForever:
-(void) infiniteMove{ id actionBy = [CCMoveBy actionWithDuration: BUFFER_DURATION position: ccp(-BUFFER_LENGTH, 0)]; id actionCallFunc = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)]; id actionSequence = [CCSequence actions: actionBy, actionCallFunc, nil]; id repeateForever = [CCRepeatForever actionWithAction:actionSequence]; [self.bufferContainer runAction:repeateForever]; }
Men senere la jeg til en verdenshastighetsøkning for å gjøre spillet vanskeligere når det fortsetter:
-(void) infiniteMoveWithAccel { float duration = BUFFER_DURATION-BUFFER_ACCEL*self.lastBufferNumber; duration = max(duration, MIN_BUFFER_DURATION); id actionBy = [CCMoveBy actionWithDuration: duration position: ccp(-BUFFER_LENGTH, 0)]; id restartMove = [CCCallFunc actionWithTarget:self selector:@selector(infiniteMoveWithAccel)]; id fillBuffer = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)]; id actionSequence = [CCSequence actions: actionBy, restartMove, fillBuffer, nil]; [self.bufferContainer runAction:actionSequence]; }
Denne endringen førte til at animasjonen smalt ved hver omstart av handlingen. Jeg prøvde å fikse problemet, til ingen nytte. Betatesterne mine la imidlertid ikke merke til oppførselen, så jeg utsatte løsningen.
Å lage ditt eget uendelige løperspill kan være en flott opplevelse. Og når du først er kommet til publiseringstrinnet i prosessen, kan det være en fantastisk følelse når du slipper din egen kreasjon ut i naturen.
Gjennomgangsprosessen kan variere fra flere dager til flere uker. For mer, det er et nyttig nettsted her som bruker data fra folkemengden til å estimere nåværende gjennomgangstider.
I tillegg anbefaler jeg å bruke AppAnnie å undersøke forskjellig informasjon om alle applikasjonene i App Store, og registrere seg hos noen analytiske tjenester som Flurry Analytics kan være nyttig også.
Og hvis dette spillet har fascinert deg, må du sjekke ut det Bi-løp i butikken.