How To Clone 
FlappyBird 
in 
Swift
A little introduction
Who Am I? 
@giordanoscalzo 
https://github.com/gscalzo
Who Am I? 
@giordanoscalzo 
https://github.com/gscalzo 
A developer
Who Am I? 
@giordanoscalzo 
https://github.com/gscalzo 
An iOS developer
Who Am I? 
@giordanoscalzo 
https://github.com/gscalzo 
A Swift beginner
How to implement Hello World in Swift?
println("Hello, World!")
println("Hello, World!") 
Not exciting :-(
far to be perfect
but it was fun
instructions
git clone 
http://github.com/gscalzo/FlappySwift.git
./setup
./setup 1 
./setup 2 
./setup 3 
...
walking skeleton 
./setup 1
class GameScene: SKScene { 
private var screenNode: SKSpriteNode! 
override func didMoveToView(view: SKView) { 
// ... 
}
override func didMoveToView(view: SKView) { 
screenNode = SKSpriteNode(color: UIColor.clearColor(), size: self.size) 
addChild(screenNode) 
let backgroundNode = SKSpriteNode(imageNamed: "background") 
backgroundNode.anchorPoint = CGPointZero 
backgroundNode.position = CGPointZero 
screenNode.addChild(backgroundNode) 
let groundNode = SKSpriteNode(imageNamed: "ground") 
groundNode.anchorPoint = CGPointZero 
groundNode.position = CGPointZero 
screenNode.addChild(groundNode) 
}
parallax layers 
./setup 2
override func didMoveToView(view: SKView) { 
//... 
Background(textureNamed: "background").addTo(screenNode).start() 
Ground(textureNamed: "ground").addTo(screenNode).start() 
}
protocol Startable { 
func start() -> Startable 
func stop() -> Startable 
}
class Background { 
private var parallaxNode: ParallaxNode! 
private let textureName: String 
init(textureNamed textureName: String) { 
} 
func addTo(parentNode: SKSpriteNode!) -> Background { 
return self 
} 
}
init(textureNamed textureName: String) { 
self.textureName = textureName 
} 
func addTo(parentNode: SKSpriteNode!) -> Background { 
let width = parentNode.size.width 
let height = parentNode.size.height 
parallaxNode = ParallaxNode(width: width, 
height: height, 
textureNamed: textureName).addTo(parentNode) 
return self 
}
extension Background : Startable { 
func start() -> Startable { 
parallaxNode.start(duration: 20.0) 
return self 
} 
func stop() -> Startable { 
parallaxNode.stop() 
return self 
} 
}
class Ground { 
private var parallaxNode: ParallaxNode! 
private let textureName: String 
init(textureNamed textureName: String) { 
} 
func addTo(parentNode: SKSpriteNode!) -> Ground { 
return self 
} 
}
init(textureNamed textureName: String) { 
self.textureName = textureName 
} 
func addTo(parentNode: SKSpriteNode!) -> Ground { 
let width = parentNode.size.width 
let height = CGFloat(60.0) 
parallaxNode = ParallaxNode(width: width, 
height: height, 
textureNamed: textureName).zPosition(5).addTo(parentNode) 
return self 
}
extension Ground : Startable { 
func start() -> Startable { 
parallaxNode.start(duration: 5.0) 
return self 
} 
func stop() -> Startable { 
parallaxNode.stop() 
return self 
} 
}
How to implement ParallaxNode?
class ParallaxNode { 
private let node: SKSpriteNode! 
init(width: CGFloat, height: CGFloat, textureNamed: String) { 
} 
private func createNode(textureNamed: String, x: CGFloat) -> SKNode { 
} 
func zPosition(zPosition: CGFloat) -> ParallaxNode { 
} 
func addTo(parentNode: SKSpriteNode) -> ParallaxNode { 
} 
func start(#duration: NSTimeInterval) { 
} 
func stop() { 
} 
}
init(width: CGFloat, height: CGFloat, textureNamed: String) { 
let size = CGSizeMake(2*width, height) 
node = SKSpriteNode(color: UIColor.whiteColor(), size: size) 
node.anchorPoint = CGPointZero 
node.position = CGPointZero 
node.addChild(createNode(textureNamed, x: 0)) 
node.addChild(createNode(textureNamed, x: width)) 
}
private func createNode(textureNamed: String, x: CGFloat) -> SKNode { 
let node = SKSpriteNode(imageNamed: textureNamed, normalMapped: true) 
node.anchorPoint = CGPointZero 
node.position = CGPoint(x: x, y: 0) 
return node 
}
func zPosition(zPosition: CGFloat) -> ParallaxNode { 
node.zPosition = zPosition 
return self 
} 
func addTo(parentNode: SKSpriteNode) -> ParallaxNode { 
parentNode.addChild(node) 
return self 
} 
func stop() { 
node.removeAllActions() 
}
func start(#duration: NSTimeInterval) { 
node.runAction(SKAction.repeatActionForever(SKAction.sequence( 
[ 
SKAction.moveToX(-node.size.width/2.0, duration: duration), 
SKAction.moveToX(0, duration: 0) 
] 
))) 
}
Our hero 
./setup 3
override func didMoveToView(view: SKView) { 
physicsWorld.gravity = CGVector(dx: 0, dy: -3) 
//... 
bird = Bird(textureNames: ["bird1", "bird2"]).addTo(screenNode) 
bird.position = CGPointMake(30.0, 400.0) 
bird.start() 
}
class Bird { 
private let node: SKSpriteNode! 
private let textureNames: [String] 
var position : CGPoint { 
set { node.position = newValue } 
get { return node.position } 
} 
init(textureNames: [String]) { 
self.textureNames = textureNames 
node = createNode() 
} 
func addTo(scene: SKSpriteNode) -> Bird{ 
scene.addChild(node) 
return self 
} 
}
extension Bird { 
private func createNode() -> SKSpriteNode { 
let birdNode = SKSpriteNode(imageNamed: textureNames.first!) 
birdNode.setScale(1.8) 
birdNode.zPosition = 2.0 
birdNode.physicsBody = SKPhysicsBody(rectangleOfSize: birdNode.size) 
birdNode.physicsBody!.dynamic = true 
return birdNode 
} 
}
extension Bird : Startable { 
func start() -> Startable { 
animate() 
return self 
} 
func stop() -> Startable { 
node.physicsBody!.dynamic = false 
node.removeAllActions() 
return self 
} 
}
private func animate(){ 
let animationFrames = textureNames.map { texName in 
SKTexture(imageNamed: texName) 
} 
node.runAction( 
SKAction.repeatActionForever( 
SKAction.animateWithTextures(animationFrames, timePerFrame: 0.1) 
)) 
}
func update() { 
switch node.physicsBody!.velocity.dy { 
case let dy where dy > 30.0: 
node.zRotation = (3.14/6.0) 
case let dy where dy < -100.0: 
node.zRotation = -1*(3.14/4.0) 
default: 
node.zRotation = 0.0 
} 
}
Bird Physics 101
Impulse
func flap() { 
node.physicsBody!.velocity = CGVector(dx: 0, dy: 0) 
node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 6)) 
}
Pipes!
Pipes! 
./setup 4
class GameScene: SKScene { 
override func didMoveToView(view: SKView) { 
//... 
Pipes(textureNames: ["pipeTop.png", "pipeBottom.png"]).addTo(screenNode).start() 
}
class Pipes { 
// class let createActionKey = "createActionKey" // class variables not yet supported 
private class var createActionKey : String { get {return "createActionKey"} } 
private var parentNode: SKSpriteNode! 
private let textureNames: [String] 
init(textureNames: [String]) { 
self.textureNames = textureNames 
} 
func addTo(parentNode: SKSpriteNode) -> Pipes { 
self.parentNode = parentNode 
return self 
} 
}
func start() -> Startable { 
let createAction = SKAction.repeatActionForever( 
SKAction.sequence( 
[ 
SKAction.runBlock { 
self.createNewPipe() 
}, 
SKAction.waitForDuration(3) 
] 
) ) 
parentNode.runAction(createAction, withKey: Pipes.createActionKey) 
return self 
}
func stop() -> Startable { 
parentNode.removeActionForKey(Pipes.createActionKey) 
let pipeNodes = parentNode.children.filter { (node: AnyObject?) -> Bool in 
(node as SKNode).name == PipePair.kind 
} 
for pipe in pipeNodes { 
pipe.removeAllActions() 
} 
return self 
}
private func createNewPipe() { 
PipePair(textures: textureNames, centerY: centerPipes()).addTo(parentNode).start() 
} 
private func centerPipes() -> CGFloat { 
return parentNode.size.height/2 - 100 + 20 * CGFloat(arc4random_uniform(10)) 
}
class PipePair { 
// class let kind = "PIPES" // class variables not yet supported 
class var kind : String { get {return "PIPES"} } 
private let gapSize: CGFloat = 50 
private var pipesNode: SKNode! 
private var finalOffset: CGFloat! 
private var startingOffset: CGFloat!
init(textures: [String], centerY: CGFloat){ 
pipesNode = SKNode() 
pipesNode.name = PipePair.kind 
let pipeTop = createPipe(imageNamed: textures[0]) 
let pipeTopPosition = CGPoint(x: 0, y: centerY + pipeTop.size.height/2 + gapSize) 
pipeTop.position = pipeTopPosition 
pipesNode.addChild(pipeTop) 
let pipeBottom = createPipe(imageNamed: textures[1]) 
let pipeBottomPosition = CGPoint(x: 0 , y: centerY - pipeBottom.size.height/2 - gapSize) 
pipeBottom.position = pipeBottomPosition 
pipesNode.addChild(pipeBottom) 
let gapNode = createGap(size: CGSize( 
width: pipeBottom.size.width, 
height: gapSize*2)) 
gapNode.position = CGPoint(x: 0, y: centerY) 
pipesNode.addChild(gapNode) 
finalOffset = -pipeBottom.size.width/2 
startingOffset = -finalOffset 
}
func start() { 
pipesNode.runAction(SKAction.sequence( 
[ 
SKAction.moveToX(finalOffset, duration: 6.0), 
SKAction.removeFromParent() 
] 
)) 
}
private func createPipe(#imageNamed: String) -> SKSpriteNode { 
let pipeNode = SKSpriteNode(imageNamed: imageNamed) 
return pipeNode 
} 
private func createGap(#size: CGSize) -> SKSpriteNode { 
let gapNode = SKSpriteNode(color: UIColor.clearColor(), 
size: size) 
return gapNode 
}
Contact 
./setup 5
enum BodyType : UInt32 { 
case bird = 1 // (1 << 0) 
case ground = 2 // (1 << 1) 
case pipe = 4 // (1 << 2) 
case gap = 8 // (1 << 3) 
} 
class GameScene: SKScene {
extension Bird { 
private func createNode() -> SKSpriteNode { 
//... 
birdNode.physicsBody = SKPhysicsBody(rectangleOfSize: birdNode.size) 
birdNode.physicsBody?.dynamic = true 
birdNode.physicsBody?.categoryBitMask = BodyType.bird.toRaw() 
birdNode.physicsBody?.collisionBitMask = BodyType.bird.toRaw() 
birdNode.physicsBody?.contactTestBitMask = BodyType.world.toRaw() | 
BodyType.pipe.toRaw() | 
BodyType.gap.toRaw()
Or better, using a builder closure... 
extension Bird { 
private func createNode() -> SKSpriteNode { 
//... 
birdNode.physicsBody = SKPhysicsBody.rectSize(birdNode.size) { 
body in 
body.dynamic = true 
body.categoryBitMask = BodyType.bird.toRaw() 
body.collisionBitMask = BodyType.bird.toRaw() 
body.contactTestBitMask = BodyType.world.toRaw() | 
BodyType.pipe.toRaw() | 
BodyType.gap.toRaw() 
}
Handy builder closure extension 
extension SKPhysicsBody { 
typealias BodyBuilderClosure = (SKPhysicsBody) -> () 
class func rectSize(size: CGSize, 
builderClosure: BodyBuilderClosure) -> SKPhysicsBody { 
let body = SKPhysicsBody(rectangleOfSize: size) 
builderClosure(body) 
return body 
} 
}
class Ground { 
func addTo(parentNode: SKSpriteNode!) -> Ground { 
//... 
groundBody.physicsBody = SKPhysicsBody.rectSize(size) { body in 
body.dynamic = false 
body.affectedByGravity = false 
body.categoryBitMask = BodyType.ground.toRaw() 
body.collisionBitMask = BodyType.ground.toRaw() 
}
extension PipePair { 
private func createPipe(#imageNamed: String) -> SKSpriteNode { 
pipeNode.physicsBody = SKPhysicsBody.rectSize(pipeNode.size) { 
body in 
body.dynamic = false 
body.affectedByGravity = false 
body.categoryBitMask = BodyType.pipe.toRaw() 
body.collisionBitMask = BodyType.pipe.toRaw() 
} 
} 
private func createGap(#size: CGSize) -> SKSpriteNode { 
gapNode.physicsBody = SKPhysicsBody.rectSize(size) { 
body in 
body.dynamic = false 
body.affectedByGravity = false 
body.categoryBitMask = BodyType.gap.toRaw() 
body.collisionBitMask = BodyType.gap.toRaw() 
} 
} 
}
extension GameScene: SKPhysicsContactDelegate { 
func didBeginContact(contact: SKPhysicsContact!) { 
} 
func didEndContact(contact: SKPhysicsContact!) { 
} 
}
func didBeginContact(contact: SKPhysicsContact!) { 
let contactMask = contact.bodyA.categoryBitMask | 
contact.bodyB.categoryBitMask 
switch (contactMask) { 
case BodyType.pipe.toRaw() | BodyType.bird.toRaw(): 
log("Contact with a pipe") 
case BodyType.ground.toRaw() | BodyType.bird.toRaw(): 
log("Contact with ground") 
default: 
return 
} 
}
func didEndContact(contact: SKPhysicsContact!) { 
let contactMask = contact.bodyA.categoryBitMask | 
contact.bodyB.categoryBitMask 
switch (contactMask) { 
case BodyType.gap.toRaw() | BodyType.bird.toRaw(): 
log("Exit from gap") 
default: 
return 
} 
}
Final touches 
./setup 6
class GameScene: SKScene { 
private var actors: [Startable]! 
private var score: Score!
override func didMoveToView(view: SKView) { 
//... 
let bg = Background(textureNamed: "background").addTo(screenNode) 
let gr = Ground(textureNamed: "ground").addTo(screenNode) 
bird = Bird(textureNames: ["bird1", "bird2"]).addTo(screenNode) 
bird.position = CGPointMake(30.0, 400.0) 
let pi = Pipes(textureNames: ["pipeTop.png", "pipeBottom.png"]).addTo(screenNode) 
actors = [bg, gr, pi, bird] 
score = Score().addTo(screenNode) 
for actor in actors { 
actor.start() 
} 
}
class Score { 
private var score: SKLabelNode! 
private var currentScore = 0 
func addTo(parentNode: SKSpriteNode) -> Score { 
score = SKLabelNode(text: "(currentScore)") 
score.fontName = "MarkerFelt-Wide" 
score.fontSize = 30 
score.position = CGPoint(x: parentNode.size.width/2, 
y: parentNode.size.height - 40) 
parentNode.addChild(score) 
return self 
} 
func increase() { 
currentScore += 1 
score.text = "(currentScore)" 
} 
}
func didBeginContact(contact: SKPhysicsContact!) { 
//... 
case BodyType.pipe.toRaw() | BodyType.bird.toRaw(): 
bird.pushDown() 
case BodyType.ground.toRaw() | BodyType.bird.toRaw(): 
for actor in actors { 
actor.stop() 
} 
let shakeAction = SKAction.shake(0.1, amplitudeX: 20) 
screenNode.runAction(shakeAction)
func didEndContact(contact: SKPhysicsContact!) { 
//... 
case BodyType.gap.toRaw() | BodyType.bird.toRaw(): 
score.increase()
extension Bird { 
func flap() { 
if !dying { 
node.physicsBody!.velocity = CGVector(dx: 0, dy: 0) 
node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 6)) 
} 
} 
func pushDown() { 
dying = true 
node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: -10)) 
}
Into the Cave 
./setup 7
override func didMoveToView(view: SKView) { 
let textures = Textures.cave() 
let bg = Background(textureNamed: textures.background).addTo(screenNode) 
let te = Ground(textureNamed: textures.ground).addTo(screenNode) 
bird = Bird(textureNames: textures.bird).addTo(screenNode) 
let pi = Pipes(textureNames: textures.pipes).addTo(screenNode)
struct Textures { 
let background: String 
let ground: String 
let pipes: [String] 
let bird: [String] 
static func classic() -> Textures { 
return Textures( 
background: "background.png", 
ground: "ground.png", 
pipes: ["pipeTop.png", "pipeBottom.png"], 
bird: ["bird1", "bird2"]) 
} 
static func cave() -> Textures { 
return Textures( 
background: "cave_background.png", 
ground: "cave_ground.png", 
pipes: ["cave_pipeTop.png", "cave_pipeBottom.png"], 
bird: ["bird1", "bird2"]) 
} 
}
extension Bird { 
private func addLightEmitter() { 
let light = SKLightNode() 
light.categoryBitMask = BodyType.bird.toRaw() 
light.falloff = 1 
light.ambientColor = UIColor.whiteColor() 
light.lightColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5) 
light.shadowColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5) 
node.addChild(light) 
} 
}
extension PipePair { 
private func createPipe(#imageNamed: String) -> SKSpriteNode { 
let pipeNode = SKSpriteNode(imageNamed: imageNamed) 
pipeNode.shadowCastBitMask = BodyType.bird.toRaw() 
pipeNode.physicsBody = SKPhysicsBody.rectSize(pipeNode.size) {
private func createNode(textureNamed: String, x: CGFloat) -> SKNode { 
let node = SKSpriteNode(imageNamed: textureNamed, normalMapped: true) 
node.lightingBitMask = BodyType.bird.toRaw()
What if 
Michael Bay 
was the designer? 
./setup 8
Explosions!
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) { 
switch touches.count { 
case 1: bird.flap() 
default: shoot() 
} 
}
extension GameScene { 
private func shoot(#emitterName: String, finalYPosition: CGFloat) { 
let fireBoltEmmitter = SKEmitterNode.emitterNodeWithName(emitterName) 
fireBoltEmmitter.position = bird.position 
fireBoltEmmitter.physicsBody = SKPhysicsBody.rectSize(CGSize(width: 20, height: 20)) { 
body in 
body.dynamic = true 
body.categoryBitMask = BodyType.bomb.toRaw() 
body.collisionBitMask = BodyType.bomb.toRaw() 
body.contactTestBitMask = BodyType.pipe.toRaw() 
} 
screenNode.addChild(fireBoltEmmitter) 
fireBoltEmmitter.runAction(SKAction.sequence( 
[ 
SKAction.moveByX(500, y: 100, duration: 1), 
SKAction.removeFromParent() 
])) 
} 
private func shoot() { 
shoot(emitterName: "fireBolt", finalYPosition: 1000) 
}
Game menu
Leaderboard
Swifterims!
Please: Fork and PRs 
github.com/gscalzo/FlappySwift
credits 
SKLightNode tutorial 
http://www.ymc.ch/en/playing-with-ios-8-sprite-kit-and-sklightnode 
Assets for Cave version 
http://www.free-pobo.com
Thank you!
Questions?
How to Clone Flappy Bird in Swift

How to Clone Flappy Bird in Swift

  • 1.
    How To Clone FlappyBird in Swift
  • 2.
  • 3.
    Who Am I? @giordanoscalzo https://github.com/gscalzo
  • 4.
    Who Am I? @giordanoscalzo https://github.com/gscalzo A developer
  • 5.
    Who Am I? @giordanoscalzo https://github.com/gscalzo An iOS developer
  • 6.
    Who Am I? @giordanoscalzo https://github.com/gscalzo A Swift beginner
  • 9.
    How to implementHello World in Swift?
  • 10.
  • 11.
  • 14.
    far to beperfect
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
    ./setup 1 ./setup2 ./setup 3 ...
  • 21.
  • 23.
    class GameScene: SKScene{ private var screenNode: SKSpriteNode! override func didMoveToView(view: SKView) { // ... }
  • 24.
    override func didMoveToView(view:SKView) { screenNode = SKSpriteNode(color: UIColor.clearColor(), size: self.size) addChild(screenNode) let backgroundNode = SKSpriteNode(imageNamed: "background") backgroundNode.anchorPoint = CGPointZero backgroundNode.position = CGPointZero screenNode.addChild(backgroundNode) let groundNode = SKSpriteNode(imageNamed: "ground") groundNode.anchorPoint = CGPointZero groundNode.position = CGPointZero screenNode.addChild(groundNode) }
  • 26.
  • 27.
    override func didMoveToView(view:SKView) { //... Background(textureNamed: "background").addTo(screenNode).start() Ground(textureNamed: "ground").addTo(screenNode).start() }
  • 28.
    protocol Startable { func start() -> Startable func stop() -> Startable }
  • 29.
    class Background { private var parallaxNode: ParallaxNode! private let textureName: String init(textureNamed textureName: String) { } func addTo(parentNode: SKSpriteNode!) -> Background { return self } }
  • 30.
    init(textureNamed textureName: String){ self.textureName = textureName } func addTo(parentNode: SKSpriteNode!) -> Background { let width = parentNode.size.width let height = parentNode.size.height parallaxNode = ParallaxNode(width: width, height: height, textureNamed: textureName).addTo(parentNode) return self }
  • 31.
    extension Background :Startable { func start() -> Startable { parallaxNode.start(duration: 20.0) return self } func stop() -> Startable { parallaxNode.stop() return self } }
  • 32.
    class Ground { private var parallaxNode: ParallaxNode! private let textureName: String init(textureNamed textureName: String) { } func addTo(parentNode: SKSpriteNode!) -> Ground { return self } }
  • 33.
    init(textureNamed textureName: String){ self.textureName = textureName } func addTo(parentNode: SKSpriteNode!) -> Ground { let width = parentNode.size.width let height = CGFloat(60.0) parallaxNode = ParallaxNode(width: width, height: height, textureNamed: textureName).zPosition(5).addTo(parentNode) return self }
  • 34.
    extension Ground :Startable { func start() -> Startable { parallaxNode.start(duration: 5.0) return self } func stop() -> Startable { parallaxNode.stop() return self } }
  • 35.
    How to implementParallaxNode?
  • 39.
    class ParallaxNode { private let node: SKSpriteNode! init(width: CGFloat, height: CGFloat, textureNamed: String) { } private func createNode(textureNamed: String, x: CGFloat) -> SKNode { } func zPosition(zPosition: CGFloat) -> ParallaxNode { } func addTo(parentNode: SKSpriteNode) -> ParallaxNode { } func start(#duration: NSTimeInterval) { } func stop() { } }
  • 40.
    init(width: CGFloat, height:CGFloat, textureNamed: String) { let size = CGSizeMake(2*width, height) node = SKSpriteNode(color: UIColor.whiteColor(), size: size) node.anchorPoint = CGPointZero node.position = CGPointZero node.addChild(createNode(textureNamed, x: 0)) node.addChild(createNode(textureNamed, x: width)) }
  • 41.
    private func createNode(textureNamed:String, x: CGFloat) -> SKNode { let node = SKSpriteNode(imageNamed: textureNamed, normalMapped: true) node.anchorPoint = CGPointZero node.position = CGPoint(x: x, y: 0) return node }
  • 42.
    func zPosition(zPosition: CGFloat)-> ParallaxNode { node.zPosition = zPosition return self } func addTo(parentNode: SKSpriteNode) -> ParallaxNode { parentNode.addChild(node) return self } func stop() { node.removeAllActions() }
  • 43.
    func start(#duration: NSTimeInterval){ node.runAction(SKAction.repeatActionForever(SKAction.sequence( [ SKAction.moveToX(-node.size.width/2.0, duration: duration), SKAction.moveToX(0, duration: 0) ] ))) }
  • 45.
  • 46.
    override func didMoveToView(view:SKView) { physicsWorld.gravity = CGVector(dx: 0, dy: -3) //... bird = Bird(textureNames: ["bird1", "bird2"]).addTo(screenNode) bird.position = CGPointMake(30.0, 400.0) bird.start() }
  • 47.
    class Bird { private let node: SKSpriteNode! private let textureNames: [String] var position : CGPoint { set { node.position = newValue } get { return node.position } } init(textureNames: [String]) { self.textureNames = textureNames node = createNode() } func addTo(scene: SKSpriteNode) -> Bird{ scene.addChild(node) return self } }
  • 48.
    extension Bird { private func createNode() -> SKSpriteNode { let birdNode = SKSpriteNode(imageNamed: textureNames.first!) birdNode.setScale(1.8) birdNode.zPosition = 2.0 birdNode.physicsBody = SKPhysicsBody(rectangleOfSize: birdNode.size) birdNode.physicsBody!.dynamic = true return birdNode } }
  • 49.
    extension Bird :Startable { func start() -> Startable { animate() return self } func stop() -> Startable { node.physicsBody!.dynamic = false node.removeAllActions() return self } }
  • 50.
    private func animate(){ let animationFrames = textureNames.map { texName in SKTexture(imageNamed: texName) } node.runAction( SKAction.repeatActionForever( SKAction.animateWithTextures(animationFrames, timePerFrame: 0.1) )) }
  • 51.
    func update() { switch node.physicsBody!.velocity.dy { case let dy where dy > 30.0: node.zRotation = (3.14/6.0) case let dy where dy < -100.0: node.zRotation = -1*(3.14/4.0) default: node.zRotation = 0.0 } }
  • 52.
  • 54.
  • 56.
    func flap() { node.physicsBody!.velocity = CGVector(dx: 0, dy: 0) node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 6)) }
  • 58.
  • 59.
  • 61.
    class GameScene: SKScene{ override func didMoveToView(view: SKView) { //... Pipes(textureNames: ["pipeTop.png", "pipeBottom.png"]).addTo(screenNode).start() }
  • 62.
    class Pipes { // class let createActionKey = "createActionKey" // class variables not yet supported private class var createActionKey : String { get {return "createActionKey"} } private var parentNode: SKSpriteNode! private let textureNames: [String] init(textureNames: [String]) { self.textureNames = textureNames } func addTo(parentNode: SKSpriteNode) -> Pipes { self.parentNode = parentNode return self } }
  • 63.
    func start() ->Startable { let createAction = SKAction.repeatActionForever( SKAction.sequence( [ SKAction.runBlock { self.createNewPipe() }, SKAction.waitForDuration(3) ] ) ) parentNode.runAction(createAction, withKey: Pipes.createActionKey) return self }
  • 64.
    func stop() ->Startable { parentNode.removeActionForKey(Pipes.createActionKey) let pipeNodes = parentNode.children.filter { (node: AnyObject?) -> Bool in (node as SKNode).name == PipePair.kind } for pipe in pipeNodes { pipe.removeAllActions() } return self }
  • 65.
    private func createNewPipe(){ PipePair(textures: textureNames, centerY: centerPipes()).addTo(parentNode).start() } private func centerPipes() -> CGFloat { return parentNode.size.height/2 - 100 + 20 * CGFloat(arc4random_uniform(10)) }
  • 66.
    class PipePair { // class let kind = "PIPES" // class variables not yet supported class var kind : String { get {return "PIPES"} } private let gapSize: CGFloat = 50 private var pipesNode: SKNode! private var finalOffset: CGFloat! private var startingOffset: CGFloat!
  • 67.
    init(textures: [String], centerY:CGFloat){ pipesNode = SKNode() pipesNode.name = PipePair.kind let pipeTop = createPipe(imageNamed: textures[0]) let pipeTopPosition = CGPoint(x: 0, y: centerY + pipeTop.size.height/2 + gapSize) pipeTop.position = pipeTopPosition pipesNode.addChild(pipeTop) let pipeBottom = createPipe(imageNamed: textures[1]) let pipeBottomPosition = CGPoint(x: 0 , y: centerY - pipeBottom.size.height/2 - gapSize) pipeBottom.position = pipeBottomPosition pipesNode.addChild(pipeBottom) let gapNode = createGap(size: CGSize( width: pipeBottom.size.width, height: gapSize*2)) gapNode.position = CGPoint(x: 0, y: centerY) pipesNode.addChild(gapNode) finalOffset = -pipeBottom.size.width/2 startingOffset = -finalOffset }
  • 68.
    func start() { pipesNode.runAction(SKAction.sequence( [ SKAction.moveToX(finalOffset, duration: 6.0), SKAction.removeFromParent() ] )) }
  • 69.
    private func createPipe(#imageNamed:String) -> SKSpriteNode { let pipeNode = SKSpriteNode(imageNamed: imageNamed) return pipeNode } private func createGap(#size: CGSize) -> SKSpriteNode { let gapNode = SKSpriteNode(color: UIColor.clearColor(), size: size) return gapNode }
  • 71.
  • 72.
    enum BodyType :UInt32 { case bird = 1 // (1 << 0) case ground = 2 // (1 << 1) case pipe = 4 // (1 << 2) case gap = 8 // (1 << 3) } class GameScene: SKScene {
  • 73.
    extension Bird { private func createNode() -> SKSpriteNode { //... birdNode.physicsBody = SKPhysicsBody(rectangleOfSize: birdNode.size) birdNode.physicsBody?.dynamic = true birdNode.physicsBody?.categoryBitMask = BodyType.bird.toRaw() birdNode.physicsBody?.collisionBitMask = BodyType.bird.toRaw() birdNode.physicsBody?.contactTestBitMask = BodyType.world.toRaw() | BodyType.pipe.toRaw() | BodyType.gap.toRaw()
  • 74.
    Or better, usinga builder closure... extension Bird { private func createNode() -> SKSpriteNode { //... birdNode.physicsBody = SKPhysicsBody.rectSize(birdNode.size) { body in body.dynamic = true body.categoryBitMask = BodyType.bird.toRaw() body.collisionBitMask = BodyType.bird.toRaw() body.contactTestBitMask = BodyType.world.toRaw() | BodyType.pipe.toRaw() | BodyType.gap.toRaw() }
  • 75.
    Handy builder closureextension extension SKPhysicsBody { typealias BodyBuilderClosure = (SKPhysicsBody) -> () class func rectSize(size: CGSize, builderClosure: BodyBuilderClosure) -> SKPhysicsBody { let body = SKPhysicsBody(rectangleOfSize: size) builderClosure(body) return body } }
  • 76.
    class Ground { func addTo(parentNode: SKSpriteNode!) -> Ground { //... groundBody.physicsBody = SKPhysicsBody.rectSize(size) { body in body.dynamic = false body.affectedByGravity = false body.categoryBitMask = BodyType.ground.toRaw() body.collisionBitMask = BodyType.ground.toRaw() }
  • 77.
    extension PipePair { private func createPipe(#imageNamed: String) -> SKSpriteNode { pipeNode.physicsBody = SKPhysicsBody.rectSize(pipeNode.size) { body in body.dynamic = false body.affectedByGravity = false body.categoryBitMask = BodyType.pipe.toRaw() body.collisionBitMask = BodyType.pipe.toRaw() } } private func createGap(#size: CGSize) -> SKSpriteNode { gapNode.physicsBody = SKPhysicsBody.rectSize(size) { body in body.dynamic = false body.affectedByGravity = false body.categoryBitMask = BodyType.gap.toRaw() body.collisionBitMask = BodyType.gap.toRaw() } } }
  • 78.
    extension GameScene: SKPhysicsContactDelegate{ func didBeginContact(contact: SKPhysicsContact!) { } func didEndContact(contact: SKPhysicsContact!) { } }
  • 79.
    func didBeginContact(contact: SKPhysicsContact!){ let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask switch (contactMask) { case BodyType.pipe.toRaw() | BodyType.bird.toRaw(): log("Contact with a pipe") case BodyType.ground.toRaw() | BodyType.bird.toRaw(): log("Contact with ground") default: return } }
  • 80.
    func didEndContact(contact: SKPhysicsContact!){ let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask switch (contactMask) { case BodyType.gap.toRaw() | BodyType.bird.toRaw(): log("Exit from gap") default: return } }
  • 82.
  • 83.
    class GameScene: SKScene{ private var actors: [Startable]! private var score: Score!
  • 84.
    override func didMoveToView(view:SKView) { //... let bg = Background(textureNamed: "background").addTo(screenNode) let gr = Ground(textureNamed: "ground").addTo(screenNode) bird = Bird(textureNames: ["bird1", "bird2"]).addTo(screenNode) bird.position = CGPointMake(30.0, 400.0) let pi = Pipes(textureNames: ["pipeTop.png", "pipeBottom.png"]).addTo(screenNode) actors = [bg, gr, pi, bird] score = Score().addTo(screenNode) for actor in actors { actor.start() } }
  • 85.
    class Score { private var score: SKLabelNode! private var currentScore = 0 func addTo(parentNode: SKSpriteNode) -> Score { score = SKLabelNode(text: "(currentScore)") score.fontName = "MarkerFelt-Wide" score.fontSize = 30 score.position = CGPoint(x: parentNode.size.width/2, y: parentNode.size.height - 40) parentNode.addChild(score) return self } func increase() { currentScore += 1 score.text = "(currentScore)" } }
  • 86.
    func didBeginContact(contact: SKPhysicsContact!){ //... case BodyType.pipe.toRaw() | BodyType.bird.toRaw(): bird.pushDown() case BodyType.ground.toRaw() | BodyType.bird.toRaw(): for actor in actors { actor.stop() } let shakeAction = SKAction.shake(0.1, amplitudeX: 20) screenNode.runAction(shakeAction)
  • 87.
    func didEndContact(contact: SKPhysicsContact!){ //... case BodyType.gap.toRaw() | BodyType.bird.toRaw(): score.increase()
  • 88.
    extension Bird { func flap() { if !dying { node.physicsBody!.velocity = CGVector(dx: 0, dy: 0) node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 6)) } } func pushDown() { dying = true node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: -10)) }
  • 90.
    Into the Cave ./setup 7
  • 91.
    override func didMoveToView(view:SKView) { let textures = Textures.cave() let bg = Background(textureNamed: textures.background).addTo(screenNode) let te = Ground(textureNamed: textures.ground).addTo(screenNode) bird = Bird(textureNames: textures.bird).addTo(screenNode) let pi = Pipes(textureNames: textures.pipes).addTo(screenNode)
  • 92.
    struct Textures { let background: String let ground: String let pipes: [String] let bird: [String] static func classic() -> Textures { return Textures( background: "background.png", ground: "ground.png", pipes: ["pipeTop.png", "pipeBottom.png"], bird: ["bird1", "bird2"]) } static func cave() -> Textures { return Textures( background: "cave_background.png", ground: "cave_ground.png", pipes: ["cave_pipeTop.png", "cave_pipeBottom.png"], bird: ["bird1", "bird2"]) } }
  • 93.
    extension Bird { private func addLightEmitter() { let light = SKLightNode() light.categoryBitMask = BodyType.bird.toRaw() light.falloff = 1 light.ambientColor = UIColor.whiteColor() light.lightColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5) light.shadowColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5) node.addChild(light) } }
  • 94.
    extension PipePair { private func createPipe(#imageNamed: String) -> SKSpriteNode { let pipeNode = SKSpriteNode(imageNamed: imageNamed) pipeNode.shadowCastBitMask = BodyType.bird.toRaw() pipeNode.physicsBody = SKPhysicsBody.rectSize(pipeNode.size) {
  • 95.
    private func createNode(textureNamed:String, x: CGFloat) -> SKNode { let node = SKSpriteNode(imageNamed: textureNamed, normalMapped: true) node.lightingBitMask = BodyType.bird.toRaw()
  • 96.
    What if MichaelBay was the designer? ./setup 8
  • 98.
  • 99.
    override func touchesBegan(touches:NSSet, withEvent event: UIEvent) { switch touches.count { case 1: bird.flap() default: shoot() } }
  • 100.
    extension GameScene { private func shoot(#emitterName: String, finalYPosition: CGFloat) { let fireBoltEmmitter = SKEmitterNode.emitterNodeWithName(emitterName) fireBoltEmmitter.position = bird.position fireBoltEmmitter.physicsBody = SKPhysicsBody.rectSize(CGSize(width: 20, height: 20)) { body in body.dynamic = true body.categoryBitMask = BodyType.bomb.toRaw() body.collisionBitMask = BodyType.bomb.toRaw() body.contactTestBitMask = BodyType.pipe.toRaw() } screenNode.addChild(fireBoltEmmitter) fireBoltEmmitter.runAction(SKAction.sequence( [ SKAction.moveByX(500, y: 100, duration: 1), SKAction.removeFromParent() ])) } private func shoot() { shoot(emitterName: "fireBolt", finalYPosition: 1000) }
  • 102.
  • 103.
  • 104.
  • 105.
    Please: Fork andPRs github.com/gscalzo/FlappySwift
  • 106.
    credits SKLightNode tutorial http://www.ymc.ch/en/playing-with-ios-8-sprite-kit-and-sklightnode Assets for Cave version http://www.free-pobo.com
  • 107.
  • 108.