Contenu connexe Similaire à How to Clone Flappy Bird in Swift Similaire à How to Clone Flappy Bird in Swift (20) Plus de 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