I 3 attori del nostro gioco (Monkey, Pineapple e Statue) sono finalmente pronti a eseguire gli ordini. Dobbiamo adesso scrivere la logica che leghi gli input dell’utente con le informazioni ricevute dal motore fisico in merito alle collisioni, e che muova gli sprite di conseguenza. Apriamo quindi il file GameScene.swift e sovrascriviamolo con il codice seguente:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
private var monkey = Monkey()
private enum Direction { case Left, Right }
private var score = 0 {
didSet {
scoreLabel.text = "\(score)"
}
}
private var gameEnded = false
private var justGotPineapple = false
private var scoreLabel = SKLabelNode(fontNamed:"Chalkduster")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.backgroundColor = .blackColor()
scoreLabel.text = "0"
scoreLabel.fontSize = 20
scoreLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:self.frame.maxY - 30)
self.addChild(scoreLabel)
monkey.position.x = frame.width / 2
monkey.position.y = monkey.frame.height
self.addChild(monkey)
startAddingBlocks()
self.physicsWorld.contactDelegate = self
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard !gameEnded else { return }
guard let location = touches.first?.locationInNode(self) else { return }
let direction: Direction
if location.x > frame.width / 2 {
direction = .Right
} else {
direction = .Left
}
startMoving(direction)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
monkey.stop()
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if justGotPineapple {
justGotPineapple = false
score += 1
}
}
private func startAddingBlocks() {
let addBlockAction = SKAction.runBlock {
self.createRandomBlock()
}
let delayAction = SKAction.waitForDuration(2)
let sequence = SKAction.sequence([addBlockAction, delayAction])
let repeatForever = SKAction.repeatActionForever(sequence)
self.runAction(repeatForever)
}
private func startMoving(direction: Direction) {
self.monkey.removeAllActions()
switch direction {
case .Left: monkey.startMovingToLeftEdge()
case .Right: monkey.startMovingToRightEdge()
}
}
private func createRandomBlock() {
let randomBlock = arc4random_uniform(10) > 3 ? Statue() : Pineapple()
self.addChild(randomBlock)
randomBlock.startFallingFromRandomPosition()
}
private func endGame() {
self.removeAllActions()
self.children.forEach { $0.removeAllActions() }
scoreLabel.text = "Game Over - Score \(score)"
gameEnded = true
}
func didBeginContact(contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node, nodeB = contact.bodyB.node else { return }
if nodeA is Statue || nodeB is Statue {
endGame()
return
}
if let pineapple = nodeA as? Pineapple ?? nodeB as? Pineapple {
pineapple.removeFromParent()
justGotPineapple = true
}
}
}
Vediamo cosa fanno i vari metodi di questa classe.
-
didMoveToView(view: SKView)
Questo metodo si occupa di preparare la scena, viene chiamato automaticamente da SpriteKit nel momento in cui la scena viene visualizzata nella view corrente, ed è il posto migliore per creare e posizionare gli oggetti che faranno parte della schermata del nostro gioco.
Il codice che abbiamo inserito in questo metodo posiziona una label (
scoreLabel
) in alto al centro dello schermo e ne inizializza il contenuto con la stringa “0”.Viene quindi posizionato lo sprite monkey, e successivamente viene invocato il metodo startAddingBlocks() (che vedremo in dettaglio tra poco).
Infine la scena viene impostata come il contactDelegate del motore fisico. Questo significa che ogni volta che il motore fisico individuerà una collisione tra sprite, esso invocherà il metodo didBeginContact(contact: SKPhysicsContact) della scena (anche questo metodo lo vedremo a breve).
-
touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
Questo metodo viene invocato automaticamente da SpriteKit ogni volta che viene rilevato un evento di tipo touch sullo schermo del dispositivo. Ricordiamo che il gioco ha un framerate di 60 fotogrammi per secondo quindi l’utente potrebbe generare più eventi di tipo touch per frame. In tal caso il metodo viene chiamato una sola volta e tutti gli eventi touch rilevati vengono passati nel parametro
touches
che è, appunto, unSet
di tipoUITouch
.Nella prima riga di questo metodo verifichiamo che la partita non sia terminata, ovvero che la property
gameEnded
siafalse
.Nelle righe successive viene recuperato un evento qualsiasi da
touches
, e si verifica se l’utente ha toccato la metà destra o sinistra dello schermo. Quindi viene invocato startMoving passandogli appunto la direzione destra o sinistra. -
touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
Questo evento viene rilevato quando SpriteKit rileva la fine di un touch da parte dell’utente. In tal caso comunichiamo a monkey di fermarsi.
-
update(currentTime: CFTimeInterval)
Questo metodo viene chiamato per ogni frame.
Il codice al suo interno verifica se la property
justGotPineapple
siatrue
. Questo significa che c’è stata una collisione tra la scimmia e un’ananas. In tal caso viene incrementato lo score e la propertyjustGotPineapple
viene nuovamente portata a false. -
startAddingBlocks()
Abbiamo visto che questo metodo viene invocato quando la scena è presentata all’utente. Il codice al suo interno si occupa di creare una action infinita che ogni 2 secondi invocherà il metodo
createRandomBlock
. -
startMoving(direction: Direction)
Questo metodo è eseguito quando viene rilevato un touch. Inoltre, come abbiamo visto, il metodo riceve in input un valore relativo alla direzione (
Left
oppureRight
).Il codice che segue semplicemente comunica a Monkey che deve iniziare a muoversi verso il lato sinistro o destro dello schermo.
-
createRandomBlock()
In base al codice che abbiamo scritto in
startAddingBlocks
, questo metodo verrà invocato ogni 2 secondi.Il codice al suo interno decide (casualmente) se il nuovo blocco dovrà essere un ananas o una statua. Successivamente lo crea, lo aggiunge alla scena e ne invoca il metodo
startFallingFromRandomPosition
che, come abbiamo visto in precedenza, lo posiziona e poi inizia a farlo cadere verso il basso. -
endGame()
Questo metodo viene invocato quando la partita è terminata, ovvero quando monkey è stata colpita da una statua.
Il codice al suo interno rimuove tutte le azioni in esecuzione nella scena (quindi fermando tutti gli sprite), scrive “Game Over” nella label dello score e imposta la variabile
gameEnded
atrue
. -
didBeginContact(contact: SKPhysicsContact)
Questo metodo viene invocato dal motore fisico ogni volta che avviene una collisione tra monkey e pineapple oppure tra monkey e statue.
Il codice al suo interno verifica quale oggetto è entrato in collisione con monkey. Se si tratta di una statua allora la partita viene terminata. Se invece si tratta di un ananas, lo score viene incrementato di 1 e lo sprite dell’ananas viene rimosso.
Conclusione
A questo punto non resta che avviare il progetto nel simulatore, utilizzando la combinazione di tasti CMD + R, e potremo iniziare a giocare.
Non resta che iniziare a sperimentare, modificando parti del codice per imparare sul campo come esso funzioni in ogni sua parte: come esercizio si può provare ad aggiungere altri tipi di oggetti che cadono dall’alto, modificandone la velocità e gli effetti sulla dinamica di gioco.