项目作者: Insfgg99x

项目描述 :
Swift和SpriteKit写的经典小游戏像素鸟Falppy Bird
高级语言: Swift
项目地址: git://github.com/Insfgg99x/FlappyBird.git


FlapyBird

Swift4写的经典小游戏SpriteKit版像素鸟

部分代码预览

  1. import UIKit
  2. import SpriteKit
  3. class SKScrollableNode: SKSpriteNode {
  4. var scrollSpeed:CGFloat = 1.0
  5. convenience init(size:CGSize, imageNamed: String) {
  6. self.init()
  7. let texture = SKTexture.init(imageNamed: imageNamed)
  8. self.texture = texture
  9. self.size = size
  10. var xpos:CGFloat = 0
  11. while xpos < size.width * 2 {
  12. let sub = SKSpriteNode.init(imageNamed: imageNamed)
  13. sub.position = .init(x: xpos, y: 0)
  14. sub.anchorPoint = .zero
  15. sub.size = size
  16. addChild(sub)
  17. xpos += size.width
  18. }
  19. }
  20. func enableScroll() {
  21. let scroll = SKAction.run {
  22. let array = self.children as! [SKSpriteNode]
  23. for sub in array {
  24. sub.position = .init(x: sub.position.x - self.scrollSpeed, y: sub.position.y)
  25. if sub.position.x <= -self.size.width {
  26. sub.position = .init(x: self.size.width * CGFloat(array.count - 1), y: sub.position.y)
  27. }
  28. }
  29. }
  30. let interval:TimeInterval = 1.0/60.0
  31. let wait = SKAction.wait(forDuration: interval)
  32. let sequence = SKAction.sequence([scroll, wait])
  33. let forever = SKAction.repeatForever(sequence)
  34. run(forever, withKey: "forever")
  35. }
  36. func disableScroll() {
  37. removeAction(forKey: "forever")
  38. }
  39. }
  1. private func createPipes() {
  2. let marginx:CGFloat = 65 + size.width
  3. let width:CGFloat = 52.0
  4. let totalHeight = size.height - floor.size.height
  5. let maxPipeHeight = totalHeight - kPipeVoidGap - kMinPipeHeight
  6. let count = pipesInScreen()
  7. for i in 0 ..< count {
  8. let xpos = marginx + (width + kPipeGapx) * CGFloat(i)
  9. let topHeight = CGFloat(arc4random_uniform(UInt32(maxPipeHeight + 1 - kMinPipeHeight))) + kMinPipeHeight
  10. let bottomHeight = totalHeight - kPipeVoidGap - topHeight
  11. let top = Pipe.init(imageNamed: "pipe_top2")
  12. top.type = .top
  13. top.position = .init(x: xpos, y: size.height - topHeight)
  14. top.anchorPoint = .zero
  15. top.physicsBody = SKPhysicsBody.init(edgeLoopFrom: .init(x: 0, y: 0, width: width, height: topHeight))
  16. top.physicsBody?.categoryBitMask = SKSpriteNodeBitMaskPipe
  17. top.physicsBody?.contactTestBitMask = SKSpriteNodeBitMaskBird
  18. addChild(top)
  19. topPipes.append(top)
  20. let bottom = Pipe.init(imageNamed: "pipe_bottom2")
  21. bottom.position = .init(x: xpos, y: bottomHeight - bottom.size.height + floor.size.height)
  22. bottom.anchorPoint = .zero
  23. bottom.type = .bottom
  24. bottom.physicsBody = SKPhysicsBody.init(edgeLoopFrom: .init(x: 0, y: bottom.size.height - bottomHeight, width: width, height: bottomHeight))
  25. bottom.physicsBody?.categoryBitMask = SKSpriteNodeBitMaskPipe
  26. bottom.physicsBody?.contactTestBitMask = SKSpriteNodeBitMaskBird
  27. addChild(bottom)
  28. bottomPipes.append(bottom)
  29. }
  30. }
  1. //MARK: -
  2. //MARK: Update
  3. extension GameScene {
  4. override func update(_ currentTime: TimeInterval) {
  5. guard stae == .playing else {
  6. return
  7. }
  8. updatePipes()
  9. updateScoreIfNeed()
  10. }
  11. private func updatePipes() {
  12. if topPipes.count == 0 || bottomPipes.count == 0 {
  13. return
  14. }
  15. let width:CGFloat = 52.0
  16. let totalHeight = size.height - floor.size.height
  17. let maxPipeHeight = totalHeight - kPipeVoidGap - kMinPipeHeight
  18. let maxXpos = (width + kPipeGapx) * CGFloat(pipesInScreen())
  19. for i in 0..<pipesInScreen() {
  20. let topPipe = topPipes[i]
  21. let bottomPipe = bottomPipes[i]
  22. let topHeight = CGFloat(arc4random_uniform(UInt32(maxPipeHeight + 1 - kMinPipeHeight))) + kMinPipeHeight
  23. topPipe.position = .init(x: topPipe.position.x - kPipeSpeed, y: topPipe.position.y)
  24. if topPipe.position.x <= -size.width {
  25. let xpos = maxXpos + topPipe.position.x
  26. topPipe.position = .init(x: xpos, y: size.height - topHeight)
  27. }
  28. bottomPipe.position = .init(x: bottomPipe.position.x - kPipeSpeed, y: bottomPipe.position.y)
  29. if bottomPipe.position.x <= -size.width {
  30. let xpos = maxXpos + bottomPipe.position.x
  31. let bottomHeight = totalHeight - kPipeVoidGap - topHeight
  32. bottomPipe.position = .init(x: xpos, y: bottomHeight - bottomPipe.size.height + floor.size.height)
  33. }
  34. }
  35. }
  36. private func resetPipePosition() {
  37. if topPipes.count == 0 || bottomPipes.count == 0 {
  38. return
  39. }
  40. let marginx:CGFloat = 65 + size.width
  41. let width:CGFloat = 52.0
  42. let totalHeight = size.height - floor.size.height
  43. let maxPipeHeight = totalHeight - kPipeVoidGap - kMinPipeHeight
  44. for i in 0..<pipesInScreen() {
  45. let top = topPipes[i]
  46. let bottom = bottomPipes[i]
  47. let xpos = marginx + (width + kPipeGapx) * CGFloat(i)
  48. let topHeight = CGFloat(arc4random_uniform(UInt32(maxPipeHeight + 1 - kMinPipeHeight))) + kMinPipeHeight
  49. let bottomHeight = totalHeight - kPipeVoidGap - topHeight
  50. top.position = .init(x: xpos, y: size.height - topHeight)
  51. bottom.position = .init(x: xpos, y: bottomHeight - bottom.size.height + floor.size.height)
  52. }
  53. }
  54. private func updateScoreIfNeed() {
  55. if scoreLb.isHidden {
  56. scoreLb.isHidden = false
  57. }
  58. for p in topPipes {
  59. let reference = p.position.x + p.size.width / 2
  60. let birdx = bird.position.x
  61. if birdx > reference && birdx < (reference + kPipeSpeed) {
  62. score += 1
  63. scoreLb.text = String(score)
  64. run(scoreSound)
  65. }
  66. }
  67. let best = UserDefaults.standard.integer(forKey: "best")
  68. if score > UInt(best) {
  69. UserDefaults.standard.set(Int(score), forKey: "best")
  70. UserDefaults.standard.synchronize()
  71. }
  72. }
  73. private func resetScore() {
  74. score = 0
  75. scoreLb.text = "0"
  76. }
  77. }
  1. //MARK: -
  2. //MARK: Touches Began
  3. extension GameScene {
  4. override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  5. if stae == .playing {
  6. run(jumpSound)
  7. bird.flap()
  8. } else if stae == .gameover {
  9. stae = .ready
  10. lose.removeFromParent()
  11. resetPipePosition()
  12. bird.position = birdStartPosition
  13. addTips()
  14. bird.ready()
  15. scoreLb.isHidden = true
  16. resetScore()
  17. } else {//ready
  18. stae = .playing
  19. removeTips()
  20. bird.go()
  21. createPipes()
  22. }
  23. }
  24. }
  25. //MARK: -
  26. //MARK: SKPhysicsContactDelegate
  27. extension GameScene {
  28. func didBegin(_ contact: SKPhysicsContact) {
  29. if stae == .gameover {
  30. return
  31. }
  32. gameover()
  33. }
  34. }
  35. //MARK: -
  36. //MARK: Game Over
  37. extension GameScene {
  38. private func gameover() {
  39. stae = .gameover
  40. run(collsionSound)
  41. shake()
  42. showScore()
  43. self.bird.die()
  44. isUserInteractionEnabled = false
  45. run(SKAction.wait(forDuration: 2), completion: {
  46. self.bird.physicsBody = nil
  47. self.isUserInteractionEnabled = true
  48. })
  49. }
  50. private func shake() {
  51. AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
  52. let animation = CABasicAnimation.init(keyPath: "position")
  53. animation.duration = 0.06
  54. animation.repeatCount = 4
  55. animation.autoreverses = true
  56. animation.fromValue = CGPoint.init(x: size.width/2 - 4.0, y: size.height/2)
  57. animation.toValue = CGPoint.init(x: size.width/2 + 4.0, y: size.height/2)
  58. view?.layer.add(animation, forKey: "position")
  59. }
  60. private func showScore() {
  61. addChild(lose)
  62. resultLb.text = String(score)
  63. bestLb.text = String(UserDefaults.standard.integer(forKey: "best"))
  64. var img = "bronze"
  65. if score >= 40 {
  66. img = "platinum"
  67. }else if score >= 30 {
  68. img = "gold"
  69. }else if score >= 20 {
  70. img = "silver"
  71. }else if score >= 10 {
  72. img = "bronze"
  73. }else{
  74. img = "bronze"
  75. }
  76. if img.count == 0 {
  77. medal.isHidden = true
  78. }else{
  79. medal.isHidden = false
  80. }
  81. medal.texture = SKTexture.init(imageNamed: img)
  82. }
  83. }
  1. import UIKit
  2. import SpriteKit
  3. class Bird: SKSpriteNode {
  4. private var flapAction:SKAction?
  5. class func bird() -> Bird {
  6. let b = Bird()
  7. b.texture = SKTexture.init(imageNamed: "frog")
  8. b.size = .init(width: 34, height: 28)
  9. return b
  10. }
  11. //wait for start
  12. func ready() {
  13. var gapy:CGFloat = 0.0
  14. var speedy:CGFloat = 1.0
  15. let flashAction = SKAction.run {
  16. if gapy > 4 {
  17. speedy = -1.0
  18. }else if gapy < -4 {
  19. speedy = 1
  20. }
  21. self.position = CGPoint.init(x: self.position.x, y: self.position.y + gapy)
  22. gapy += speedy
  23. }
  24. let interval:TimeInterval = 1.0/60.0
  25. let wait = SKAction.wait(forDuration: interval)
  26. let sequence = SKAction.sequence([flashAction, wait])
  27. let foreverFlash = SKAction.repeatForever(sequence)
  28. run(foreverFlash, withKey: "flash_forever")
  29. }
  30. func die() {
  31. removeAction(forKey: "flapy_forever")
  32. }
  33. //start
  34. func go() {
  35. let body = SKPhysicsBody.init(rectangleOf: size)
  36. body.mass = 0.1
  37. body.categoryBitMask = SKSpriteNodeBitMaskBird
  38. physicsBody = body
  39. removeAction(forKey: "flash_forever")
  40. let rotation = SKAction.run {
  41. self.zRotation = .pi * body.velocity.dy * 0.0005
  42. }
  43. let wait = SKAction.wait(forDuration: 1.0/60.0)
  44. let sequence = SKAction.sequence([wait, rotation])
  45. run(SKAction.repeatForever(sequence), withKey: "rotation")
  46. }
  47. func flap() {
  48. let body = self.physicsBody!
  49. body.velocity = .init(dx: 0, dy: 0)
  50. body.applyImpulse(.init(dx: 0, dy: 40))
  51. }
  52. }
  1. import UIKit
  2. import SpriteKit
  3. enum PipeType {
  4. case top
  5. case bottom
  6. }
  7. class Pipe: SKSpriteNode {
  8. var type:PipeType = .top
  9. }