Dopo aver visto come implementare gli oggetti Coin
e Block
, non ci resta che utilizzarli all'interno della classe GameScene
, in modo che il nostro gioco prenda vita.
Per prima cosa, aggiungiamo una label che mostrerà il punteggio, posizionandola in alto centrata orizzontalmente. Lo faremo usando l’editor grafico di Xcode, senza quindi dover scrivere codice.
Apriamo il file GameScene.sks e trasciniamo l’elemento Label all’interno della scena, come mostrato dalla figura seguente.
Selezioniamo quindi la label, ed impostiamo i seguenti campi:
Campo | Valore |
---|---|
Name | scoreLabel |
Position X | -1,655l |
Position Y | 169,498 |
Passiamo ora al file GameScene.swift, e sovrascriviamolo interamente con il codice che segue:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
private var gameRunning = true
private var mouse: Mouse!
private var didGetCoin = false
private var collectedCoins = 0
private var scoreLabel: SKLabelNode!
override func didMove(to view: SKView) {
startGame()
}
private func startGame() {
mouse = Mouse(gameScene: self)
mouse.position = CGPoint(x: -frame.width / 4, y: frame.midY)
addChild(mouse)
addElementsForever()
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = .zero
scoreLabel = childNode(withName: "scoreLabel") as! SKLabelNode
collectedCoins = 0
scoreLabel.text = "0"
gameRunning = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameRunning {
mouse.fly()
} else {
restart()
}
}
private func restart() {
children.filter { $0 is Coin || $0 is Block || $0 is Mouse }.forEach { $0.removeFromParent() }
startGame()
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if didGetCoin {
didGetCoin = false
collectedCoins += 1
scoreLabel.text = "\(collectedCoins)"
}
}
func didDie() {
gameRunning = false
}
private func addElementsForever() {
let addElement = SKAction.run {
self.addElement()
}
let wait = SKAction.wait(forDuration: 0.5)
let sequence = SKAction.sequence([addElement, wait])
let forever = SKAction.repeatForever(sequence)
self.run(forever)
}
private func addElement() {
let element = arc4random_uniform(2) == 0 ? Coin() : Block()
let randomY = CGFloat(arc4random_uniform(UInt32(self.frame.height - 100))) + 50
element.position = CGPoint(x: self.frame.maxX + element.frame.width, y: randomY - self.frame.height / 2 )
self.addChild(element)
let move = SKAction.moveTo(x: -self.frame.width, duration: 3)
let sequence = SKAction.sequence([move, SKAction.removeFromParent()])
element.run(sequence)
}
func didBegin(_ contact: SKPhysicsContact) {
guard let otherNode = contact.bodyA.node is Mouse ? contact.bodyB.node : contact.bodyA.node else { return }
if let coin = otherNode as? Coin {
coin.removeFromParent()
self.didGetCoin = true
}
if otherNode is Block {
gameRunning = false
self.removeAllActions()
self.children.forEach { $0.removeAllActions() }
scoreLabel.text = " *** GAME OVER *** Score: \(collectedCoins)"
mouse.die()
}
}
}
Esaminiamo il codice in dettaglio.
Le properties
Riportiamo di seguito una tabella riepilogativo delle proprietà definite nella classe GameScene
.
Proprietà | Descrizione |
---|---|
gameRunning |
Indica se il gioco è in esecuzione (true ) o se è terminato (false ) |
mouse |
Riferimento allo sprite del protagonista |
didGetCoin |
Viene impostata a true ogni volta che rileviamo una collisione tra un oggetto Coin e Mouse |
collectedCoins |
Rappresenta il numero di Coin raccolti |
scoreLabel |
Riferimento alla label aggiunta poc'anzi con l’editor grafico |
I metodi
Abbiamo esaminato in precedenza il metodo startGame
, ma questa nuova versione include alcune istruzioni aggiuntive.
In particolare, viene invocato il metodo addElementsForever()
, che si occuperà di aggiungere in continuazione sprite di tipo Coin
o Block
, e di animarli.
Stiamo anche indicando che questa scena è il contactDelegate del mondo fisico simulato. In questo modo, riceveremo notifiche relative alle collisioni che avvengono.
Altro metodo già discusso in una sua precedente versione è touchesBegan
, che si limitava ad invocare il metodo fly()
di Mouse. Adesso, esso controlla anche se il gioco è in esecuzione, e solo in tal caso richiama mouse.fly()
; altrimenti, riavvia la partita.
Il metodo restart
ripulisce la scena eliminando tutti gli sprite di tipo Coin
, Block
e Mouse
, per poi invocare startGame
.
Di fondamentale importanza per tutte le app basate su SpriteKit è il metodo update
, che viene eseguito automaticamente ogni 16 millisecondi e rappresenta la nostra opportunità di aggiornare e/o preparare il prossimo frame. In particolare, lo utilizziamo controllare se si è verificata una collisione tra gli sprite Mouse
e Coin
. In tal caso, incrementiamo il contatore dello score (collectedCoins
) e aggiorniamo la label scoreLabel
.
Altri due metodi che ci interessa attenzionare sono addElementsForever
e addElement
. Il primo si occupa semplicemente di invocare addElement
ogni mezzo secondo. Il secondo metodo posiziona casualmente un Coin
o un Block
in una coordinata Y casuale, associandolo piu ad un’animazione che lo muoverà da sinistra verso destra.
Infine, come abbiamo più volte ripetuto, il metodo didBegin
viene chiamato dal motore fisico ogni volta che avviene una collisione. Al suo interno verifichiamo se la collisione è avvenuta con un oggetto di tipo Coin
o Block
: nel primo caso, rimuoviamo il Coin
e impostiamo didGetCoin
a true
; nel secondo caso, prepariamo la schermata di Game Over.
A questo punto avremo già una prima versione funzionante della nostra app. Possiamo subito provare ad eseguirla sul simulatore, utilizzando la combinazione di tasti CMD + R.