In questa lezione vedremo come implementare le classi che rappresenteranno i principali attori della nostra app: Monkey, Pineapple e Statue.
La classe Monkey
Questa classe rappresenterà l’entità della scimmia, ovvero il personaggio controllato dall’utente. Dovrà esporre 3 metodi che poi verranno utilizzati da GameScene, ovvero:
-
startMovingToLeft()
-
startMovingToRight()
-
stop()
Da Xcode scegliamo File > New > File… quindi scegliamo Swift File, assegniamo il nome Monkey al file e premiamo Create.
Copiamo nel file sorgente questo codice.
import SpriteKit
class Monkey: SKSpriteNode {
static let category: UInt32 = 0x1 << 1
init() {
let texture = SKTexture(imageNamed: "monkey")
super.init(texture: texture, color: .clearColor(), size: texture.size())
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.restitution = 1.0
physicsBody.affectedByGravity = false
physicsBody.friction = 0.0
physicsBody.linearDamping = 0.0
physicsBody.categoryBitMask = Monkey.category
physicsBody.contactTestBitMask = Pineapple.category | Statue.category
physicsBody.collisionBitMask = 0
physicsBody.angularDamping = 0.0
physicsBody.dynamic = false
self.physicsBody = physicsBody
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func startMovingToLeftEdge() {
self.removeAllActions()
let delta = self.position.x
let duration = NSTimeInterval(delta / 120)
let moveLeft = SKAction.moveToX(self.frame.width / 2, duration: duration)
self.runAction(moveLeft)
}
func startMovingToRightEdge() {
self.removeAllActions()
let delta = self.scene!.frame.width - self.position.x
let duration = NSTimeInterval(delta / 120)
let moveRight = SKAction.moveToX(self.scene!.frame.width - self.frame.width / 2, duration: duration)
self.runAction(moveRight)
}
func stop() {
self.removeAllActions()
}
}
Vediamo ora il significato del codice che abbiamo inserito.
class Monkey: SKSpriteNode
Questa riga ci dice che la nostra classe estende SKSpriteNode
. Quindi la classe avrà una texture e potrà essere rappresentata graficamente sullo schermo.
static let category: UInt32 = 0x1 << 1
Associando una categoria distinta a Monkey, Pineapple e Statue potremo indicare al motore fisico quali collisioni devono esserci
notificate. Vedremo meglio questa logica più avanti.
init() {
let texture = SKTexture(imageNamed: "monkey")
super.init(texture: texture, color: .clearColor(), size: texture.size())
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
La prima parte del metodo init()
utilizza l’immagine monkey per creare la texture di questo sprite.
physicsBody.restitution = 1.0
physicsBody.affectedByGravity = false
physicsBody.friction = 0.0
physicsBody.linearDamping = 0.0
physicsBody.categoryBitMask = Monkey.category
physicsBody.contactTestBitMask = Pineapple.category | Statue.category
physicsBody.collisionBitMask = 0
physicsBody.angularDamping = 0.0
physicsBody.dynamic = false
self.physicsBody = physicsBody
}
La seconda parte del metodo init()
si occupa di costruire un body fisico associato a questo sprite. In particolare stiamo indicando che questo body fisico dovrà inviarci notifiche di collisione quando entra in contatto con i body di Pineapple e Statue (che definiremo tra poco).
func startMovingToLeftEdge() {
self.removeAllActions()
let delta = self.position.x
let duration = NSTimeInterval(delta / 120)
let moveLeft = SKAction.moveToX(self.frame.width / 2, duration: duration)
self.runAction(moveLeft)
}
Questo metodo avvia un’azione che sposta il nostro sprite Monkey verso l’estremo sinistro dello schermo. Tra poco faremo in modo che questo metodo venga chiamato quando l’utente tocca il lato sinistro dello schermo.
Analogamente il metodo startMovingToRight
avvia un’azione per spostare Monkey verso il bordo destro dello schermo.
func stop() {
self.removeAllActions()
}
Infine il metodo stop rimuove tutte le azioni attualmente in esecuzione su Monkey, arrestandone il movimento. Chiameremo questo metodo quando l’utente rilascia il dito dallo schermo.
Se proviamo a compilare adesso noteremo 2 errori, poiché non abbiamo ancora definito le classi Pineapple e Statue.
La classe Pineapple
La classe Pineapple rappresenta l’ananas che cade dal lato superiore dello schermo. Creiamo un nuovo file sorgente in Swift con nome Pineapple e incolliamo il seguente sorgente al suo interno:
import SpriteKit
class Pineapple: SKSpriteNode {
static let category: UInt32 = 0x1 << 2
init() {
let texture = SKTexture(imageNamed: "pineapple")
super.init(texture: texture, color: .clearColor(), size: texture.size())
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.restitution = 1.0
physicsBody.affectedByGravity = false
physicsBody.dynamic = true
physicsBody.friction = 0.0
physicsBody.linearDamping = 0.0
physicsBody.categoryBitMask = Pineapple.category
physicsBody.contactTestBitMask = Monkey.category
physicsBody.angularDamping = 0.0
self.physicsBody = physicsBody
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Vediamo in dettaglio il codice.
class Pineapple: SKSpriteNode
Anche la classe Pineapple
, come Monkey
, estende SKSpriteNode
.
La categoria di questa classe sarà 2.
Il metodo init()
si occupa di applicare una texture basata sull’immagine pineapple che abbiamo precedentemente aggiunto al progetto.
physicsBody.contactTestBitMask = Monkey.category
Questa riga del metodo init()
indica che vogliamo essere avvertiti quando avviene una collisione tra Pineapple e Monkey.
La classe Statue
Creiamo infine l’ultima classe, quella che rappresenta le statue che cadono dal cielo. Aggiungiamo un nuovo file sorgente Swift e assegniamogli il nome Statue.
import SpriteKit
class Statue: SKSpriteNode {
static let category: UInt32 = 0x1 << 3
init() {
let texture = SKTexture(imageNamed: "statue")
super.init(texture: texture, color: .clearColor(), size: texture.size())
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.restitution = 1.0
physicsBody.affectedByGravity = false
physicsBody.dynamic = true
physicsBody.friction = 0.0
physicsBody.linearDamping = 0.0
physicsBody.categoryBitMask = Pineapple.category
physicsBody.contactTestBitMask = Monkey.category
physicsBody.angularDamping = 0.0
self.physicsBody = physicsBody
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Il codice è molto simile a quello di Pineapple con l’eccezione che la category in questo caso ha valore 3.
Premendo CMD + B possiamo ora compilare il progetto senza errori.
Una extension per SKSprite
Esiste un’ultima funzionalità che vogliamo aggiungere a Pineapple e Statue. Si tratta del metodo startFallingFromRandomPosition()
.
Questo metodo deve provvedere a posizionare lo sprite in un punto sopra il bordo superiore dello schermo (quindi in un luogo invisibile all’utente) con una coordinata x casuale. Inoltre il metodo deve avviare un’azione che (con velocità casuale) farà muovere il blocco verso il bordo inferiore dello schermo, fino a uscire nuovamente dalla scena.
Invece di scrivere lo stesso metodo in entrambi i sorgenti di Pineapple e Statue possiamo usare una extension
per aggiungere il metodo a SKSpriteNode
. Siccome sia Pineapple che Statue estendono SKSpriteNode
, automaticamente lo erediteranno.
Creiamo un nuovo file Swift con nome Extensions.swift e inseriamo il seguente codice:
import SpriteKit
extension SKSpriteNode {
func startFallingFromRandomPosition() {
let random01 = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
let randomX = CGFloat(random01 * self.scene!.frame.width)
self.position.x = randomX
self.position.y = self.scene!.frame.height
let randomDuration = NSTimeInterval(arc4random_uniform(3) + 1)
let fallAction = SKAction.moveToY(-self.frame.width, duration: randomDuration)
self.runAction(fallAction) { [weak self] in
self?.removeFromParent()
}
}
}