Come creare un corridore infinito su iOS: Cocos2D, automazione e altro

Pubblicato: 2022-03-11

Lo sviluppo di giochi per iOS può essere un'esperienza arricchente in termini di crescita personale e finanziaria. All'inizio di quest'anno, ho distribuito un gioco basato su Cocos2D, Bee Race, sull'App Store. Il suo gameplay è semplice: un corridore infinito in cui i giocatori (in questo caso le api) raccolgono punti ed evitano gli ostacoli. Vedi qui per una demo.

In questo tutorial spiegherò il processo alla base dello sviluppo di giochi per iOS, da Cocos2D fino alla pubblicazione. Per riferimento, ecco un breve sommario:

  • Sprite e oggetti fisici
  • Una breve introduzione a Cocos2D
  • Utilizzo di Cocos2D con gli storyboard
  • Gameplay e (breve) descrizione del progetto
  • Automatizza i lavori. Usa gli strumenti. Sii calmo.
  • Fatturazione in-app
  • Gioco multiplayer con il Game Center
  • Margini di miglioramento
  • Conclusione

Sprite e oggetti fisici

Prima di entrare nei dettagli grintosi, sarà utile capire la distinzione tra sprite e oggetti fisici.

Per ogni data entità che appare sullo schermo di un gioco di endless runner, la rappresentazione grafica di tale entità viene definita sprite , mentre la rappresentazione poligonale di quell'entità nel motore fisico viene definita oggetto fisico .

Quindi lo sprite viene disegnato sullo schermo, supportato dal suo oggetto fisico corrispondente, che viene quindi gestito dal tuo motore fisico. Tale configurazione può essere visualizzata qui, dove gli sprite vengono visualizzati sullo schermo, con le loro controparti fisiche poligonali delineate in verde:

Nei giochi per corridori infiniti per iOS, coesistono sprite e oggetti fisici.

Gli oggetti fisici non sono collegati ai rispettivi sprite per impostazione predefinita, il che significa che tu come sviluppatore iOS puoi scegliere quale motore fisico utilizzare e come connettere sprite e corpi. Il modo più comune è sottoclassare lo sprite predefinito e aggiungere ad esso un corpo fisico concreto.

Con quello in mente…

Un breve tutorial sullo sviluppo di giochi iOS di Cocos2D

Cocos2D-iphone è un framework open source per iOS che utilizza OpenGL per l'accelerazione grafica hardware e supporta i motori fisici Chipmunk e Box2D.

Prima di tutto, perché abbiamo bisogno di un tale quadro? Bene, per cominciare, i framework implementano i componenti spesso utilizzati dello sviluppo del gioco. Ad esempio, Cocos2D può caricare sprite (in particolare fogli sprite (perché?)), avviare o arrestare un motore fisico e gestire correttamente tempi e animazioni. E fa tutto questo con un codice che è stato ampiamente rivisto e testato: perché dedicare il tuo tempo a riscrivere il codice probabilmente inferiore?

Forse la cosa più importante, tuttavia, lo sviluppo di giochi Cocos2D utilizza l'accelerazione hardware grafica . Senza tale accelerazione, qualsiasi gioco per corridori infiniti iOS con anche un numero moderato di sprite funzionerà con prestazioni notevolmente scarse. Se proviamo a creare un'applicazione più complicata, probabilmente inizieremo a vedere un effetto "bullet-time" sullo schermo, cioè copie multiple di ogni sprite mentre tenta di animare.

Infine, Cocos2D ottimizza l'utilizzo della memoria poiché memorizza nella cache gli sprite. Pertanto, qualsiasi sprite duplicato richiede una memoria aggiuntiva minima, che è ovviamente utile per i giochi.

Utilizzo di Cocos2D con gli storyboard

Dopo tutti gli elogi che ho elogiato su Cocos2D, potrebbe sembrare illogico suggerire di utilizzare Storyboard. Perché non manipolare i tuoi oggetti con Cocos2D, ecc.? Bene, ad essere onesti, per le finestre statiche è spesso più conveniente usare Interface Builder di Xcode e il suo meccanismo Storyboard.

In primo luogo, mi permette di trascinare e posizionare tutti i miei elementi grafici per il mio gioco di corsa senza fine con il mio mouse. In secondo luogo, l'API Storyboard è molto, molto utile. (E sì, conosco Cocos Builder).

Ecco un rapido assaggio del mio Storyboard:

Per imparare a creare un gioco di corsa senza fine, inizia con un buon Storyboard.

Il controller di visualizzazione principale del gioco contiene solo una scena Cocos2D con alcuni elementi HUD in alto:

L'inizio del nostro tutorial su Cocos2D è nel controller di visualizzazione.

Presta attenzione allo sfondo bianco: è una scena Cocos2D, che caricherà tutti gli elementi grafici necessari in fase di esecuzione. Altre viste (indicatori live, denti di leone, pulsanti, ecc.) sono tutte viste Cocoa standard, aggiunte allo schermo utilizzando Interface Builder.

Non mi soffermerò sui dettagli: se sei interessato, puoi trovare esempi su GitHub.

Gameplay e (breve) descrizione del progetto

(Per fornire un po' di motivazione in più, vorrei descrivere il mio gioco di corridori senza fine in modo leggermente più dettagliato. Sentiti libero di saltare questa sezione se vuoi procedere alla discussione tecnica.)

Durante il gioco dal vivo, l'ape è immobile e il campo stesso sta effettivamente correndo, portando con sé vari pericoli (ragni e fiori velenosi) e vantaggi (tarassaco e i loro semi).

Cocos2D ha un oggetto fotocamera progettato per seguire il personaggio; in pratica era meno complicato manipolare il CCLayer contenente il mondo di gioco.

I controlli sono semplici: toccando lo schermo si sposta l'ape verso l'alto e un altro tocco lo si sposta verso il basso.

Lo strato del mondo stesso ha in realtà due sottostrati. All'avvio del gioco, il primo sottolivello viene popolato da 0 a BUF_LEN e visualizzato inizialmente. Il secondo sottolivello viene popolato in anticipo da BUF_LEN a 2*BUF_LEN. Quando l'ape raggiunge BUF_LEN, il primo sottostrato viene pulito e immediatamente ripopolato da 2*BUF_LEN a 3*BUF_LEN, e viene presentato il secondo sottostrato. In questo modo, alterniamo i livelli, senza mai conservare oggetti obsoleti, una parte importante per evitare perdite di memoria.

Il mio gioco di corridori infiniti è composto da un mondo a più livelli.

In termini di motori fisici, ho usato Chipmunk per due motivi:

  1. È scritto in puro Objective-C.
  2. Ho già lavorato con Box2D in precedenza, quindi volevo confrontare i due.

Il motore fisico è stato effettivamente utilizzato solo per il rilevamento delle collisioni. A volte, mi viene chiesto: "Perché non hai scritto il tuo rilevamento delle collisioni?". In realtà, non ha molto senso. I motori fisici sono stati progettati proprio per questo scopo: possono rilevare collisioni tra corpi di forme complicate e ottimizzare tale processo. Ad esempio, i motori fisici spesso dividono il mondo in celle ed eseguono controlli di collisione solo per i corpi nelle celle stesse o adiacenti.

Automatizza i lavori. Usa gli strumenti. Sii calmo.

Una componente chiave dello sviluppo di giochi per corridori infiniti indie è evitare di inciampare in piccoli problemi. Il tempo è una risorsa cruciale durante lo sviluppo di un'app e l'automazione può far risparmiare incredibilmente tempo.

Ma a volte, l'automazione può anche essere un compromesso tra il perfezionismo e il rispetto delle scadenze. In questo senso, il perfezionismo può essere un killer di Angry Birds.

Ad esempio, in un altro gioco iOS che sto attualmente sviluppando, ho creato un framework per creare layout utilizzando uno strumento speciale (disponibile su GitHub). Questo framework ha i suoi limiti (ad esempio, non ha transizioni piacevoli tra le scene), ma usarlo mi permette di creare le mie scene in un decimo del tempo.

Quindi, anche se non puoi creare il tuo superframework con supertool speciali, puoi e dovresti automatizzare il maggior numero possibile di queste piccole attività.

Il perfezionismo può essere un killer di Angry Birds. Il tempo è una risorsa cruciale nello sviluppo di giochi iOS.
Twitta

Nella costruzione di questo corridore infinito, l'automazione è stata ancora una volta la chiave. Ad esempio, il mio artista mi inviava grafica ad alta risoluzione tramite una cartella Dropbox speciale. Per risparmiare tempo, ho scritto alcuni script per creare automaticamente set di file per le varie risoluzioni target richieste dall'App Store, aggiungendo anche -hd o @2x (detti script sono basati su ImageMagick).

In termini di strumenti aggiuntivi, ho trovato TexturePacker molto utile: può comprimere sprite in fogli sprite in modo che la tua app consumi meno memoria e si carichi più velocemente, poiché tutti i tuoi sprite verranno letti da un singolo file. Può anche esportare trame in quasi tutti i possibili formati di framework. (Nota che TexturePacker non è uno strumento gratuito, ma penso che valga il prezzo. Puoi anche controllare alternative gratuite come ShoeBox.)

La principale difficoltà associata alla fisica del gioco è creare poligoni adatti per ogni sprite. In altre parole, creare una rappresentazione poligonale di un'ape o di un fiore dalla forma oscura. Non provare nemmeno a farlo a mano: usa sempre applicazioni speciali, di cui ce ne sono molte. Alcuni sono persino piuttosto... esotici, come creare maschere vettoriali con Inkspace e poi importarle nel gioco.

Per lo sviluppo del mio gioco per corridori infiniti, ho creato uno strumento per automatizzare questo processo, che chiamo Andengine Vertex Helper. Come suggerisce il nome, è stato inizialmente progettato per il framework Andengine, anche se al giorno d'oggi funzionerà in modo appropriato con un certo numero di formati.

Nel nostro caso, dobbiamo usare il modello plist:

 <real>%.5f</real><real>%.5f</real>

Successivamente, creiamo un file plist con le descrizioni degli oggetti:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>jet_ant</key> <dict> <key>vertices</key> <array> <real>-0.18262</real><real>0.08277</real> <real>-0.14786</real><real>-0.22326</real> <real>0.20242</real><real>-0.55282</real> <real>0.47047</real><real>0.41234</real> <real>0.03823</real><real>0.41234</real> </array> </dict> </dict> </plist>

E un caricatore di oggetti:

 - (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"]; // <- load plist NSDictionary *objConfigs = [[[NSDictionary alloc] initWithContentsOfFile:path] autorelease]; NSArray *vertices = [[objConfigs objectForKey:namePrefix] objectForKey:@"vertices"]; shape = [ChipmunkUtil polyShapeWithVertArray:vertices withBody:body width:self.sprite.contentSize.width height:self.sprite.contentSize.height]; shape->e = 0.7; shape->u = 1.0; shape->collision_type = OBJ_COLLISION_TYPE; cpSpaceAddShape(space, shape); }

Per testare come gli sprite corrispondano ai loro corpi fisici, guarda qui.

Molto meglio, vero?

In sintesi, automatizza sempre quando possibile. Anche semplici script possono farti risparmiare un sacco di tempo. E, soprattutto, quel tempo può essere utilizzato per la programmazione invece del clic del mouse. (Per ulteriore motivazione, ecco un token XKCD.)

Fatturazione in-app

I blowball raccolti nel gioco fungono da valuta in-app, consentendo agli utenti di acquistare nuove skin per la loro ape. Tuttavia, questa valuta può essere acquistata anche con denaro reale. Un punto importante da notare rispetto alla fatturazione in-app è se è necessario eseguire o meno controlli lato server per la validità dell'acquisto. Poiché tutti i beni acquistabili sono essenzialmente uguali in termini di gameplay (solo alterando l'aspetto dell'ape), non è necessario eseguire un controllo del server per la validità dell'acquisto. Tuttavia, in molti casi, dovrai assolutamente farlo.

Per di più, Ray Wenderlich ha il tutorial di fatturazione in-app perfetto.

Gioco multiplayer con il Game Center

Nei giochi mobili, socializzare è molto più che aggiungere un pulsante "Mi piace" di Facebook o impostare classifiche. Per rendere il gioco più eccitante, ho implementato una versione multiplayer.

Come funziona? Innanzitutto, due giocatori sono collegati utilizzando il matchmaking in tempo reale di iOS Game Center. Poiché i giocatori stanno davvero giocando allo stesso gioco di corridori infiniti, è necessario che ci sia un solo set di oggetti di gioco. Ciò significa che l'istanza di un giocatore deve generare gli oggetti e l'altra riproduzione li leggerà. In altre parole, se i dispositivi di entrambi i giocatori generassero oggetti di gioco, sarebbe difficile sincronizzare l'esperienza.

Con questo in mente, dopo aver stabilito la connessione, entrambi i giocatori si scambiano un numero casuale. Il giocatore con il numero più alto funge da "server", creando oggetti di gioco.

Ricordi la discussione sulla generazione del mondo porzionato? Dove avevamo due sottolivelli, uno da 0 a BUF_LEN e l'altro da BUF_LEN a 2*BUF_LEN? Questa architettura non è stata utilizzata per caso: era necessario fornire una grafica fluida su reti ritardate. Quando una parte di oggetti viene generata, viene impacchettata in un plist e inviata all'altro giocatore. Il buffer è abbastanza grande da consentire al secondo giocatore di giocare anche con un ritardo di rete. Entrambi i giocatori si scambiano la loro posizione attuale con un periodo di mezzo secondo, inviando immediatamente anche i loro movimenti up-down. Per rendere più fluida l'esperienza, la posizione e la velocità vengono corrette ogni 0,5 secondi con un'animazione fluida, quindi in pratica sembra che l'altro giocatore si muova o acceleri gradualmente.

Ci sono sicuramente più considerazioni da fare per quanto riguarda il gameplay multiplayer senza fine, ma si spera che questo ti dia un'idea dei tipi di sfide coinvolte.

Margini di miglioramento

I giochi non sono mai finiti. Certo, ci sono diverse aree in cui vorrei migliorare la mia, vale a dire:

  1. Problemi di controllo: toccare è spesso un gesto non intuitivo per i giocatori che preferiscono scivolare.
  2. Il livello mondiale viene spostato utilizzando l'azione CCMoveBy. Questo andava bene quando la velocità del livello mondiale era costante, poiché l'azione CCMoveBy veniva ciclata con 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]; }

    Ma in seguito, ho aggiunto un aumento della velocità mondiale per rendere il gioco più difficile man mano che procede:

     -(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]; }

    Questa modifica causava lo sfaldamento dell'animazione a ogni riavvio dell'azione. Ho provato a risolvere il problema, inutilmente. Tuttavia, i miei beta tester non hanno notato il comportamento, quindi ho posticipato la correzione.

  3. Da un lato, non è stato necessario scrivere la mia autorizzazione per il multiplayer quando si utilizza Game Center o si esegue il mio server di gioco. D'altra parte, ha reso impossibile creare bot, cosa che mi piacerebbe cambiare.

Conclusione

Creare il tuo gioco di corridori infiniti indie può essere una grande esperienza. E una volta che arrivi alla fase di pubblicazione del processo, può essere una sensazione meravigliosa mentre rilasci la tua creazione in libertà.

Il processo di revisione può variare da diversi giorni a diverse settimane. Per ulteriori informazioni, qui c'è un sito utile che utilizza i dati di crowdsourcing per stimare i tempi di revisione correnti.

Inoltre, consiglio di utilizzare AppAnnie per esaminare varie informazioni su tutte le applicazioni nell'App Store e anche la registrazione con alcuni servizi di analisi come Flurry Analytics può essere utile.

E se questo gioco ti ha incuriosito, assicurati di dare un'occhiata a Bee Race nel negozio.