IOS-pelien kehittäminen voi olla rikastuttava kokemus sekä henkilökohtaisten että taloudellinen kasvu. Aiemmin tänä vuonna otin käyttöön Cocos2D-pohjaisen pelin, Mehiläiskilpailu , App Storeen. Sen pelattavuus on yksinkertainen: ääretön juoksija, jossa pelaajat (tässä tapauksessa mehiläiset) keräävät pisteitä ja välttävät esteitä. Katso tässä esittelyä varten.
Tässä opetusohjelmassa selitän iOS-pelien kehittämisen prosessin Cocos2D: stä julkaisemiseen. Tässä on lyhyt sisällysluettelo:
Ennen kuin ryhdymme yksityiskohtiin, on hyödyllistä ymmärtää niiden välinen ero spritit ja fyysiset esineet.
Kaikille tietyille entiteeteille, jotka näkyvät loputtoman juoksijapelin näytöllä, kyseisen yksikön graafista esitystä kutsutaan sprite , kun taas kyseisen yksikön polygonaaliseen esitykseen fysiikkamoottorissa viitataan nimellä fyysinen esine .
Joten sprite piirretään näytölle sen fyysisen objektin tukemana, jota sitten fysiikkamoottorisi käsittelee. Tämä asetus voidaan visualisoida täällä, missä spritit näytetään ruudulla fyysisten monikulmaisten vastineidensa kanssa vihreällä:
Fyysisiä esineitä ei ole oletusarvoisesti liitetty vastaaviin spritteihin, mikä tarkoittaa, että sinä olet iOS-kehittäjä voi valita käytettävän fysiikkamoottorin ja kuinka yhdistää spritit ja kappaleet. Yleisin tapa on luokitella oletussprite ja lisätä siihen konkreettinen fyysinen ruumis.
Se mielessä…
Cocos2D-iphone on avoimen lähdekoodin kehys iOS: lle, joka käyttää OpenGL: ää laitegrafiikan kiihdytykseen ja tukee Maaorava ja Box2D fysiikan moottorit.
Ensinnäkin, miksi tarvitsemme tällaisia puitteita? Ensinnäkin, kehykset toteuttavat usein käytettyjä pelikehityksen komponentteja. Esimerkiksi Cocos2D voi ladata sprittejä (erityisesti sprite-levyt ( miksi? )), käynnistä tai pysäytä fysiikkamoottori ja käsittele ajoitusta ja animaatiota oikein. Ja se tekee kaiken tämän koodilla, joka on tarkistettu ja testattu perusteellisesti - miksi omistaa oma aikasi todennäköisesti huonompi-koodin kirjoittamiseen uudelleen?
Ehkä tärkeintä kuitenkin - Cocos2D-pelikehityksessä käytetään grafiikkalaitteistokiihdytystä . Ilman tällaista kiihtyvyyttä mikä tahansa iOS: n ääretön juoksijapeli, jolla on jopa kohtalainen määrä sprittejä, suoritetaan huomattavasti huonolla suorituskyvyllä. Jos yritämme tehdä monimutkaisemman sovelluksen, alamme todennäköisesti nähdä 'bullet-time' -vaikutuksen näytöllä, eli useita kopioita kustakin spriteistä, kun se yrittää animoida.
Lopuksi, Cocos2D optimoi muistin käytön siitä lähtien välimuistit . Siten kaikki päällekkäiset spritit vaativat vähän lisämuistia, mikä on tietysti hyödyllistä peleissä.
Kaikkien Cocos2D: lle ylistämieni kiitosten jälkeen se saattaa tuntua epäloogiselta ehdottaa Storyboardsin käyttöä . Miksi ei vain manipuloida esineitäsi esimerkiksi Cocos2D: llä? No, ollakseni rehellinen, staattisissa ikkunoissa on usein helpompaa käyttää Xcode's Interface Builderia ja sen Storyboard-mekanismia.
Ensinnäkin se antaa minun vetää ja sijoittaa kaikki graafiset elementtini loputtomalle juoksijapelille hiirellä. Toiseksi Storyboard API on erittäin, erittäin hyödyllinen. (Ja kyllä, tiedän Cocos Builder ).
Tässä on lyhyt katsaus Storyboardiin:
Pelin päänäkymän ohjaimessa on vain Cocos2D-kohtaus, jossa on joitain HUD-elementtejä:
Kiinnitä huomiota valkoiseen taustaan: se on Cocos2D-kohtaus, joka lataa kaikki tarvittavat graafiset elementit ajon aikana. Muut näkymät (live-indikaattorit, voikukat, painikkeet jne.) Ovat kaikki vakiokaakaonäkymät, jotka on lisätty näyttöön Interface Builderin avulla.
En aio jäädä yksityiskohtiin - jos olet kiinnostunut, esimerkkejä löytyy GitHubista.
(Motivaation lisäämiseksi haluaisin kuvata loputtoman juoksijapelini hieman yksityiskohtaisemmin. Voit ohittaa tämän osan, jos haluat siirtyä tekniseen keskusteluun.)
Live-pelaamisen aikana mehiläinen on liikkumaton, ja kenttä itse asiassa kiirehtii mukana tuoden mukanaan erilaisia vaaroja (hämähäkit ja myrkylliset kukat) ja etuja (voikukat ja niiden siemenet).
Cocos2D: ssä on kameran esine, joka on suunniteltu seuraamaan hahmoa; käytännössä pelimaailman sisältävän CCLayerin käsittely oli vähemmän monimutkaista.
Hallintalaitteet ovat yksinkertaisia: näytön napauttaminen siirtää mehiläisen ylös ja toinen napautus alaspäin.
Itse maailmankerroksessa on kaksi alikerrosta. Kun peli alkaa, ensimmäinen alikerros täytetään välillä 0 - BUF_LEN ja näytetään alun perin. Toinen alikerros täytetään etukäteen välillä BUF_LEN - 2 * BUF_LEN. Kun mehiläinen saavuttaa BUF_LEN-tason, ensimmäinen alikerros puhdistetaan ja uudelleensijoitetaan välittömästi 2 * BUF_LEN-arvosta 3 * BUF_LEN-tasoon, ja toinen alikerros esitetään. Tällä tavoin vaihdamme kerroksia keskenään, emmekä koskaan säilytä vanhentuneita esineitä, mikä on tärkeä osa muistivuotojen välttämisessä.
Fysiikkamoottorien osalta käytin maaoravetta kahdesta syystä:
Fysiikan moottoria käytettiin oikeastaan vain törmäysten havaitsemiseen. Joskus minulta kysytään: 'Miksi et kirjoittanut omaa törmäystunnistustasi?'. Todellisuudessa sillä ei ole paljon järkeä. Fysiikkamoottorit on suunniteltu juuri tähän tarkoitukseen: ne voivat havaita monimutkaisten muotojen törmäykset ja optimoida prosessin. Esimerkiksi fysiikkamoottorit jakavat maailman usein soluiksi ja suorittavat törmäystarkastuksia vain samoissa tai viereisissä soluissa olevien kappaleiden osalta.
Indie-äärettömän juoksijan pelikehityksen keskeinen osa on välttää kompastumista pieniin ongelmiin. Aika on ratkaiseva resurssi sovellusta kehitettäessä, ja automaatio voi säästää uskomattoman aikaa.
Mutta joskus automaatio voi myös olla kompromissi perfektionismin ja määräajan noudattamisen välillä. Tässä mielessä perfektionismi voi olla Angry Birds -tappaja.
Esimerkiksi toisessa parhaillaan kehitettävässä iOS-pelissä rakensin kehyksen asettelujen luomiseen erityisellä työkalulla (saatavilla osoitteessa GitHub ). Tällä kehyksellä on rajoituksensa (esimerkiksi sillä ei ole mukavia siirtymiä kohtausten välillä), mutta sen käyttäminen antaa minun tehdä kohtaukseni kymmenesosassa.
Joten vaikka et voi rakentaa omaa superkehystäsi erikoistyökaluilla, voit silti automatisoida mahdollisimman monen näistä pienistä tehtävistä.
Perfektionismi voi olla Angry Birds -tappaja. Aika on keskeinen resurssi iOS-pelikehityksessä. TweetTämän äärettömän juoksijan rakentamisessa automaatio oli jälleen kerran avainasemassa. Esimerkiksi taiteilijani lähetti minulle korkean resoluution grafiikkaa erityisen Dropbox-kansion kautta. Ajan säästämiseksi kirjoitin joitain komentosarjoja rakentamaan tiedostojoukot automaattisesti App Storen vaatimille erilaisille kohdetarkkuuksille lisäämällä myös -hd tai @ 2x (mainitut komentosarjat perustuvat ImageMagick ).
Lisätyökalujen suhteen löysin TexturePacker olla erittäin hyödyllinen - se voi pakata spritit sprite-arkeihin, jotta sovelluksesi kuluttaa vähemmän muistia ja latautuu nopeammin, koska kaikki spritit luetaan yhdestä tiedostosta. Se voi myös viedä tekstuureja lähes kaikissa mahdollisissa kehysmuodoissa. (Huomaa, että TexturePacker ei ole ilmainen työkalu, mutta mielestäni se on hintansa arvoinen. Voit myös tarkistaa ilmaisia vaihtoehtoja, kuten Kenkälaatikko .)
Pelin fysiikkaan liittyvä suurin vaikeus on luoda sopivia polygoneja kullekin spriteille. Toisin sanoen luomalla monikulmainen esitys jostakin epämääräisen muotoisesta mehiläisestä tai kukasta. Älä edes yritä tehdä tätä käsin - käytä aina erikoissovelluksia, joita on paljon. Jotkut ovat jopa… eksoottisia - kuten luoda vektorinaamioita Inkspacen avulla ja tuoda ne sitten peliin.
Omalle loputtomalle juoksijapelikehitykselleni loin työkalun tämän prosessin automatisoimiseksi (katso tässä oikeaan käyttöön), jota kutsun Andengine Vertex Helper . Kuten nimestä voi päätellä, se on alun perin suunniteltu Andengine-kehys , vaikka se toimii sopivasti useilla muodoilla nykyään.
Meidän tapauksessamme meidän on käytettävä plist-mallia:
%.5f%.5f
Seuraavaksi luomme plist-tiedoston, jossa on objektien kuvaukset:
jet_ant vertices -0.182620.08277 -0.14786-0.22326 0.20242-0.55282 0.470470.41234 0.038230.41234
Ja esineiden latauslaite:
- (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); }
Katso, kuinka testata kuinka spritit vastaavat heidän fyysistä kehoaan tässä .
Paljon parempi, eikö?
Yhteenvetona, automatisoi aina kun mahdollista. Jopa yksinkertaiset komentosarjat voivat säästää aikaa. Ja mikä tärkeintä, sitä aikaa voidaan käyttää ohjelmointiin hiiren napsautuksen sijaan. (Lisämotivaatiota varten tässä on a tunnus XKCD .)
Pelissä kerätyt pallot toimivat sovelluksen sisäisenä valuuttana, jonka avulla käyttäjät voivat ostaa uusia mehuja mehilleen. Tämän valuutan voi kuitenkin ostaa myös oikealla rahalla. Tärkeä huomio sovelluksen sisäisestä laskutuksesta on, onko sinun suoritettava palvelinpuolen tarkistukset ostoksen voimassaolon suhteen. Koska kaikki ostettavissa olevat tuotteet ovat olennaisesti samanlaisia pelattavuuden suhteen (vain muuttamalla mehiläisen ulkonäköä), palvelimen tarkistusta ostoksen pätevyyden tarkistamiseksi ei ole tarpeen. Monissa tapauksissa sinun on kuitenkin tehtävä se.
Enemmän, Ray Wenderlichillä on täydellinen sovelluksen sisäinen laskutusopastus .
Mobiilipelissä seurustelu on enemmän kuin vain lisätä Facebook-tykkää-painike tai asettaa tulostaulukot. Pelin tekemiseksi mielenkiintoisemmaksi toteutin moninpeliversio.
Kuinka se toimii? Ensinnäkin kaksi pelaajaa yhdistetään iOS-pelikeskuksen reaaliaikaisen ottelun avulla. Koska pelaajat todella pelaavat samaa ääretöntä juoksijapeliä, täytyy olla vain yksi joukko peliobjekteja. Tämä tarkoittaa sitä, että yhden pelaajan instanssin täytyy luoda esineitä, ja toisen pelaajan lukee ne. Toisin sanoen, jos molempien pelaajien laitteet tuottavat peliobjekteja, kokemuksen synkronointi olisi vaikeaa.
Tässä mielessä, kun yhteys on muodostettu, molemmat pelaajat lähettävät toisilleen satunnaisluvun. Pelaaja, jolla on suurempi numero, toimii 'palvelimena' ja luo peliobjekteja.
Muistatko keskustelun ositetusta maailman sukupolvesta? Missä meillä oli kaksi alikerrosta, yksi 0: sta BUF_LEN: iin ja toinen: BUF_LEN: sta 2 * BUF_LEN: iin? Tätä arkkitehtuuria ei käytetty vahingossa - se oli tarpeen tarjota tasainen grafiikka viivästyneissä verkoissa. Kun osa esineistä syntyy, se pakataan plistiin ja lähetetään toiselle pelaajalle. Puskuri on riittävän suuri, jotta toinen pelaaja voi pelata jopa verkon viiveellä. Molemmat pelaajat lähettävät toisilleen nykyisen sijaintinsa puoli sekuntia, lähettämällä myös ylös-alas-liikkeensa välittömästi. Kokemuksen tasoittamiseksi sijainti ja nopeus korjataan 0,5 sekunnin välein tasaisella animaatiolla, joten käytännössä näyttää siltä, että toinen pelaaja liikkuu tai kiihtyy vähitellen.
Monen pelaajan loputtomasta juoksupelistä on varmasti tehtävä lisää huomioita, mutta toivottavasti tämä antaa sinulle käsityksen haastetyypeistä.
Pelit eivät ole koskaan kesken. On tosin useita aloja, joilla haluaisin parantaa omaani, nimittäin:
Maailmakerros siirretään CCMoveBy-toiminnolla. Tämä oli hieno, kun maailman kerroksen nopeus oli vakio, koska CCMoveBy-toiminto sykloitiin CCRepeatForeverin kanssa:
-(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]; }
Mutta myöhemmin lisäsin maailman nopeuden kasvun, jotta peli olisi vaikeampi jatkuessa:
-(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]; }
Tämä muutos aiheutti animaation hajoamisen jokaisen toiminnan uudelleenkäynnistyksen yhteydessä. Yritin korjata ongelman turhaan. Beetatestaajat eivät kuitenkaan huomanneet käyttäytymistä, joten lykkäsin korjausta.
Oman indie-äärettömän juoksijapelin luominen voi olla hieno kokemus. Ja kun pääset prosessin julkaisuvaiheeseen, voi olla upea tunne, kun luovutat oman luomuksesi villiin luontoon.
Tarkastusprosessi voi vaihdella useista päivistä useisiin viikkoihin. Lisää on hyödyllinen sivusto tässä joka käyttää väkijoukkolähtöisiä tietoja arvioidakseen nykyiset tarkasteluajat.
Lisäksi suosittelen käyttöä AppAnnie tutkia erilaisia tietoja kaikista App Storen sovelluksista ja rekisteröityä joihinkin analyysipalveluihin, kuten Flurry Analytics voi olla hyödyllistä myös.
Ja jos tämä peli on kiinnostanut sinua, muista tarkistaa Mehiläiskilpailu kaupassa.