More Related Content Similar to How to Clone Flappy Bird in Swift Similar to How to Clone Flappy Bird in Swift(20) More from Giordano Scalzo More from Giordano Scalzo(10) How to Clone Flappy Bird in Swift3. 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
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)
}
27. override func didMoveToView(view: SKView) {
//...
Background(textureNamed: "background").addTo(screenNode).start()
Ground(textureNamed: "ground").addTo(screenNode).start()
}
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
}
}
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)
]
)))
}
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
}
}
56. func flap() {
node.physicsBody!.velocity = CGVector(dx: 0, dy: 0)
node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 6))
}
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
}
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, 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()
}
75. 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
}
}
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()
}
}
}
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
}
}
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)
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))
}
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()
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)
}
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