Skip to main content

Come sviluppare un gioco stile Breakout per iPhone

Questo tutorial è stato scritto per Xcode 6.3 e Swift 1.2

In questo tutorial imparerete a sviluppare un gioco stile Breakout per iPhone utilizzando SpriteKit (il framework per giochi 2D di iOS e OS X), Swift e il Physics Engine integrato in Sprite Kit. Nel filmato qui sotto vedete una registrazione del gioco che state per costruire.

Dopo aver scritto il progetto ho riscritto alcune componenti per 3 volte fino a renderlo il più chiaro e comprensibile possibile (spero di esserci riuscito).

Creiamo il progetto

Scaricate Xcode 6.3 (o successivo), apritelo e premete ⇧ + cmd + N per create un nuovo progetto.

Selezionate iOS > Application > Game

Xcode - new game

 

Premete Next.

Nel campo Organization Name inserite il vostro nome mentre in Organization Identifier una qualche stringhe che vi identifica (ad esempio un dominio che possedete inserito al contrario). Il resto compilatelo come in figura.

xcode options for project

Next.

Selezionate una cartella in cui volete creare il progetto (ad esempio Documents) e premete Create.

Tra qualche secondo Xcode avrà creato il progetto per voi.

Vogliamo che il gioco funzioni solo con l’iPhone in verticale quindi:

  1. Selezionate la radice del progetto dal Navigator (il pannello sulla sinistra)
  2. Poi selezionate il Target Breakout
  3. Infine nella sezione Device Orientation deselezionate le opzioni Landscape Left e Landscape Right.

breakout_portrait

Aggiungiamo le immagini

Guardando il filmato precedente vi sarete resi conto che avremo bisogno di alcune immagini. Le trovate in questo zip  (sia in versione standard che @2x), scaricatele tutte, poi create una cartella sul Desktop con nome Sprites.atlas e trascinate al suo interno le 6 immagini.

Ora da Xcode accedete al Navigator (il pannello sulla sinistra e selezionate il gruppo Breakout).

Poi selezionate File > Add files to “Breakout”…

Selezionate la cartella Sprites.atlas sul Desktop, assicuratevi che 3  le voci sia selezionate

  1. Copy items if needed
  2. Create groups
  3. Breakout

Premete Add. Bene, avete appena creato un Texture Atlas per la gestione delle immagini del gioco!

La struttura del gioco

Come dicevo ho riscritto il progetto più di una volta in modo da ottenere una struttura (secondo me) ottimale del codice e delle classi.

  • GameScene: rappresenta il livello, contiene gli Sprite (una Ball, una Bar e 24 Brick). Inoltre referenzia un’oggetto di tipo Logic e un PhysicsContactDelegate. Questa classe dialoga con Logic comunicandogli lo stato dell’azione e muove gli sprite in base a come indicato da Logic.
  • Logic: questa classe contiene le regole del gioco. Immaginatela come una scatola completamente svincolata dalla tecnologia del gioco. Deve semplicemente essere in grado di rispondere a domande del tipo: Cosa succede quando una Ball capisce un Brick? Oppure: Cosa succede quando tutti i Brick sono stati eliminati?
  • PhysicsContactDelegate: Questa classe espone dei metodi che vengono chiamati dal Physics Engine al momento opportuno. Ad esempio il metodo didBeginContact viene chiamato ogni volta che 2 corpi fisici entrano in contatto. Useremo questa classe per tradurre questi eventi fisici in eventi logici da comunicare a Logic.
  • Ball: Questa classe rappresenta la pallina del gioco.
  • Bar: Rappresenta la barra che comanda il giocatore.
  • Brick: Rappresenta il singolo mattone nella parte superiore dello schermo (ce ne saranno 24 in tutto).

Costruiremo il gioco con un approccio bottom-up: partiremo dalle fondamenta e poi saliremo di livello utilizzando gli oggetti creati in precedenza.

Confusi? Niente paura, nei prossimi paragrafi dovrebbe diventare tutto più chiaro. Tuttavia se qualcosa continua a sfuggirvi scrivetelo nei commenti e cercherò di aiutarvi.

La classe Ball

Questa classe rappresenta la pallina del gioco. Espone 1 initializer e la funzionalità startMoving. Il movimento di questo Sprite sarà gestito dal Physics Engine in modo del tutto automatico. L’unica cosa che dovremo fare è definire un corpo fisico associato all’immagine della pallina. Successivamente, per ogni frame, il Physics Engine calcolerà la nuova posizione del corpo fisico e SpriteKit sposterà l’immagine di conseguenza.

  1. Da Xcode premete cmd + N e scegliete iOS > Source > Swift File
  2. Next
  3. Digitate Ball e premete Create
  4. Riempite il file con il codice seguente

Se avete seguito il tutorial Create il vostro Infinite Scrolling Game per iPhone con Swift e SpriteKit questa classe vi sarà in parte familiare. Alla riga #3 stiamo definendo una classe con nome Ball che estende SKSpriteNode (ovvero un oggetto che ha, tra le varie cose, un’immagine associata che viene rappresentata sullo scena).

Alla riga #5 definiamo un inizializer senza parametri che carica l’immagine ball (dallo Sprite Sheet)  e la usa per costruire lo sprite.

Il secondo inizializer è richiesto perché SKSpriteNode è conforme al protocollo NSCoding, tuttavia non lo useremo quindi ci limitiamo a sollevare un fatal_error.

Il metodo startMoving applica un’impulso al corpo fisico associato allo sprite in modo da avviare il movimento della pallina.

Infine il metodo privato createPhysics viene chiamato dal primo initializer e si occupa di costruire un corpo fisico associato allo sprite. I parametri del corpo fisico sono abbastanza intuitivi. Infine nell’ultima riga associamo il corpo fisico allo sprite.

La classe Brick

Analogamente create la classe Brick.

Il primo initializer riceve un colore e “dipinge” l’immagine di quel colore.

Il metodo vanish verrà invocato da Logic quando questo Brick deve scomparire dallo schermo.

Questo oggetto non si muove, infatti stiamo impostando la proprietà physicsBody.dynamic a false.

L’enum Side

Tra poco avremo bisogno di un tipo di dato che rappresenti la direzione della di Bar (destra o sinistra). Potremmo usare una variabile di tipo Bool ma è più chiaro creare un enum apposito.

Create un nuovo file di tipo Swift con nome Enums.

La classe Bar

Questo oggetto si muove in con una Action (non grazie alla fisica), quindi stiamo impostando la variabile physicsBody.dynamic a false.

Il metodo startMovingLeft verrà chiamato quando il giocatore tocca il lato sinistro dello schermo e avvierà il movimento della barra verso sinistra.

Il metodo startMovingRight è analogo.

Il metodo stopMoving verrà invocato quando il giocatore rilascia il dito dallo schermo, questo metodo rimuove le azioni eventualmente in esecuzione sull’oggetto in modo da arrestarne il movimento.

Alla riga #35 definiamo una Computed Property che ritorna un valore di tipo Side? (il punto interrogativo indica che il valore è Optional). Quindi i possibili valori restituiti saranno .Left, .Right oppure nil (in caso la barra non si stia muovendo).

Aggiungere il metodo createPhysics a SKSpriteNode

Le tre classi Ball, Brick e Bar implementano lo stesso metodo createPhysics. Sarebbe meglio evitare di riscrivere questo metodo 3 volte. Abbiamo varie strade a disposizione. Ad esempio potremmo creare una classe PhysicsSprite che estende SKSpriteNode, aggiungere a questa classe createPhysics e lasciare e lasciare che Ball, Brick e Bar estendano PhysicsSprite. Tuttavia questa situazione è ottima per utilizzare la funzionalità extension di Swift. Questa tecnica permette di aggiungere un metodo a una classe di cui non possediamo il sorgente (SKSpriteNode appunto). Se aggiungiamo il metodo createPhysics a SKSpriteNode, automaticamente Ball, Brick e Bar lo erediteranno.

Creiamo il file Extensions di tipo Swift e incolliamoci questo codice.

Ora torniamo su Ball, Brick e Bar e cancelliamo il metodo createPhysics.

Ecco come dovrebbero apparire ora le 3 classi di tipo SKSpriteNode.

Premiamo cmd + B per verificare che tutto stia compilando correttamente. Bene, apprezzatelo perché da ora non compilerà più per un po 😆

La classe PhysicsContactDelegate

Questa classe riceverà delle notifiche dal physics engine relative al contatto tra 2 corpo fisici e le dovrà tradurre il eventi logici da comunicare alla classe Logic (perché ricordiamo che Logic non ha alcuna concezione di fisica o di SpriteKit).

La classe è conforme al protocollo SKPhysicsContactDelegate, questo protocollo dichiara 2 metodi opzionali:

  1. didBeginContact
  2. didEndContact

Tuttavia noi siamo interessati a essere informati solo solo del primo tipo di evento quindi implementiamo solo il primo.

Nella nostra implementazione di didBeginContact estraiamo i 2 corpi fisici che sono entrati in contatto. Da ognuno possiamo arrivare all’oggetto SKNode associato.

Se il contatto è avvenuto tra Ball e Brick allora informiamo Logic invocando brickHit.

Altrimenti se il contratto è avvenuto tra Ball e Scene (quindi la pallina ha toccato il bordo della scena) e la pallina si trova al di sotto della posizione di Bar allora informiamo Logic che proverà a terminare la partita.

La classe Logic

Le classi che abbiamo costruito fino a questo momento erano i mattoni del nostro gioco. Tutte classi il più “stupide” possibile che devono riceve ordini da qualcuno che conosce le regole del gioco.

Prima di passare a implementare la classe Logic però abbiamo bisogno di un secondo enum che definisca lo stato attuale della partita. Aprite Enums.swift e aggiungete l’enum Status.

Create ora classe Logic.swift.

Alla riga #1 stiamo importando il framework Foundation, non importare SpriteKit è un buon sistema per evitare di scrivere in questa classe del codice che non riguarda esclusivamente la logica del gioco (ma naturalmente potrebbero esistere delle eccezioni).

Alla riga #5 dichiariamo una costante che conterrà una referenza alla GameScene. Questa referenza serve per comunicare alla scena (seppure ad alto livello) come muovere gli sprite. La costante è definita con la keyword unowned, questo serve per evitare un ciclo di referenze che spingerebbero ARC a non rilasciare gli oggetti (dedicherò un post apposito a questo argomento).

Inoltre abbiamo una variabile status che rappresenta lo stato attuale della partita. L’initializer riceve un’oggetto di tipo GameScene e lo usa per impostare la costante di cui abbiamo parlato prima.

Il metodo touchbegins(side: Side)

Questo metodo verrà invocato ogni volta che il giocatore tocca con un dito lo schermo. Il parametro ci informa se è stato toccata la metà destra o quella sinistra dello schermo.

Se il gioco è in stato Playing allora spostiamo la bar nella direzione coerente con il touch. Se il gioco è in stato GameOver allora comunichiamo alla scena di rimuovere tutte le entità (Ball, Bricks, Bar), poi le raggiungiamo con le loro posizioni di default e passiamo allo stato Playing.

Se il touch avviene mentre siamo in stato JustDead allora non facciamo nulla.

Il metodo touchEnd(side: Side)

Se il giocatore toglie il dito dal lato destro dello schermo e la barra si stata spostando verso destra allora la arrestiamo. Simmetricamente con la direzione sinistra.

Il metodo ballContactWithDeadLine()

Forse ricorderete che questo metodo viene invocato da PhysicsContactDelegate quando Ball tocca i bordi della scena e si trova sotto Bar. In questo caso lo stato del gioco passa a JustDead.

Il metodo brickHit()

Anche questo viene invocato da PhysicsContactDelegate, precisamente quando Ball urta un Brick. In tal caso decidiamo di invocare vanish su brick in modo da farlo scomparire e rimuoverlo dalla scena.

Il metodo update()

Questo metodo verrà chiamata da GameScene per ogni fotogramma ed ha lo scopo di aggiornare cambiare lo stato corrente del gioco in base alle nuove informazioni disponibili.

Se lo stato attuale è Playing e non ci sono più Brick nella scena, allora il giocatore ha vinto. In tal caso mostriamo il messaggio di vittoria, rimuoviamo Ball dalla scena e impostiamo lo stato attuale a Won.

Se lo stato invece è JustDead, mostriamo il messaggio di GameOver, rimuoviamo Ball dalla scena e passiamo allo stato GameOver.

In tutti gli altri casi non facciamo nulla.

Il progetto attualmente non compila perché Logic sta usando dei metodi e variabile di GameScene che non abbiamo ancora aggiunto.

La classe GameScene

Ci siamo, questa è l’ultima classe necessaria per il nostro gioco. La costruiremo un po’ alla volta in modo da vedere di volta in volta il risultato finale prendere forma sullo schermo del simulatore.

Prima di tutto eliminiamo tutto il contenuto da GameScene e lasciamola vuota.

Cerchiamo ora di far compilare nuovamente il progetto.

Aggiungiamo le seguenti Property e metodi.

Ora premendo CMD + B il progetto compila tuttavia se lo avviamo (CMD + R) otteniamo una schermava vuota.

iPhone Simulator Empty Scene

Il metodo addBar()

Aggiungiamo a questo metodo il codice per posizionare correttamente l’oggetto Bar e aggiungerlo alla scena.

Il metodo didMoveToView(view: SKView)

In questo metodo invece:

  1. impostiamo il colore di sfondo della scena (riga #3)
  2. indichiamo quale sarà l’oggetto che dovrà ricevere gli eventi relativi alle collisioni degli oggetti (riga #5)
  3. creiamo dei bordi fisici lungo il perimetro della scena (necessari per far rimbalzare Ball)
  4. creiamo creiamo l’oggetto Logic (passandogli come parametro l’oggetto GameScene corrente) e invochiamo logic.start

 

CMD + R, adesso Bar è presente nella scena ma se tocchiamo il lato destro o sinistro dello schermo non si muove. Questo perché la GameScene sta ricevendo gli eventi di tipo UITouch ma non sta a sua volta chiamando Logic.Rilevare il touch

Aggiungiamo questi 3 metodi a GameScene.

Il metodo sideOfTouch ci permette di convertire un UITouch in un valore dell’enum Side: ovvero Right o Left. Questo ci permette di calcolare quale lato dello schermo è stato toccato dal giocatore.

I 2 metodi seguenti vengono chiamati automaticamente da SpriteKit quando inizia un touch (o più di uno) e quando finisce.

Nel primo metodo(riga #3) selezioniamo un touch dal gruppo di touch ricevuti. Poi lo convertiamo in Side (Left o Right) e lo comunichiamo a logic.

Analogamente in touchesEnded trasformiamo un touch in Side e lo comunichiamo a logic.

CMD + R. Ora toccando uno dei 2 lati dello schermo siamo in grado di far muovere Bar.

Il metodo addBall

Ora aggiungiamo il seguente codice al metodo addBall.

Semplicemente posizioniamo Ball, lo aggiungiamo alla scena e avviamo il movimento.

CMD + R. Ora vedremo la pallina rimbalzare nella scena e contro Bar.  Notate che una volta che Ball viene a contatto con il perimetro della scena e questo avviene nei 100 punti inferiore dello schermo, non siamo più in grado di muovere Bar. Questo perché Logic passa alo status JustDead e quindi inibisce ulteriori comportamenti in seguito al touch.

Screen Shot 2015-04-25 at 15.20.26

Aggiungere i mattoni

Implementiamo il metodo addBrick. Stiamo semplicemente aggiungendo al nodo bricks oggetti di tipo Brick assegnando a ognuno un suo colore e una sua posizione. Infine aggiungiamo il nodo bricks alla scena. Avere tutti i mattoni figli di un unico nodo bricks ci permette di organizzare più facilmente altre parti del codice (come la property noMoreBricks).

 

Aggiornare lo stato della partita

setLabelWithStatus metodo si occupa di:

  1. rimuovere eventuali messaggi dallo schermo status = Playing
  2. mostrare il messaggio di congratulazioni se status = Won
  3. mostrare il messaggio di GameOver se status = GameOver

Nel metodo update stiamo chiamando logic.update()  in modo che possa aggiornare il suo stato interno.

Infine il metodo removeEntities si occupa di rimuovere dalla scena tutti i nodi presenti ovvero Bar, Ball e bricks (che è padre degli oggetti Brick che vengono eliminati con lui).

Qui di seguito trovate la classe GameScene completa.

CMD+ R. Buona divertimento perché il gioco è finito!

Conclusioni

Questo progetto è più complesso rispetto al precedente, vi invito a modificarlo e smontarlo per vedere cosa cambia quando apportate dei cambiamenti. Inoltre il progetto introduce il physics engine di SpriteKit, questo riduce la quantità di codice da scrivere ma richiede un impegno maggiore nella comprensione del suo funzionamento.

Se avete domande o dubbi scrivete qui sotto nei commenti.

Trainer • Developer • Writer

Luca Angeletti

Trainer • Developer • Writer

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *