iFIERO -- SkyNinja天豬之城 SpriteKit iOS遊戲源碼

這是第二款










// //  GameScene.swift //  SkyNinja /*  *  *** 遊戲元素使用條款及注意事項 ***  *  *  遊戲中的所有元素全部由iFIERO所原創(除註明引用之外),包括人物、音樂、場景等;  *  創作的初衷就是讓更多的遊戲愛好者可以在開發遊戲中獲得自豪感 -- 讓手機遊戲開發變得簡單;  *  秉着開源分享的原則,iFIERO發佈的遊戲都儘可能的易懂實用,並開放所有源碼;  *  任何使用者都可以使用遊戲中的代碼塊,也可以進行拷貝、修改、更新、升級,無須再經過iFIERO的同意;  *  但這並不表示可以任意複製、拆分其中的遊戲元素:  *  用於[商業目的]而不註明出處;  *  用於[任何教學]而不註明出處;  *  用於[遊戲上架]而不註明出處;  *  另外,iFIERO有商用授權遊戲元素,獲得iFIERO官方授權後,即無任何限制;  *  請尊重幫助過你的iFIERO的知識產權,非常感謝;  *  *  Created by VANGO楊 && ANDREW陳  *  Copyright © 2018 iFiero. All rights reserved.  *  www.iFIERO.com  *  iFIERO -- 讓手機遊戲開發變得簡單  *  *  SkyNinja 天豬之城 在此遊戲中您將獲得如下技能:  *  *  1、LaunchScreen       學習如何設置遊戲啓動畫面  *  2、Scene              學習如何切換遊戲的遊戲場景  *  3、Scene Edit         學習直接使用可見即所得操作編輯遊戲場景  *  4、Random             利用可複用的隨機函數生成Enemy  *  5、SpriteNode class   學習建立獨立的class精靈並引入場景scene  *  6、Collision          學習有節點與節點之間的碰撞的原理及處理方法  *  7、Animation&Atlas    學習如何導入動畫幀及何爲Atlas  *  8、Camera             使用Camera實現endless背景滾動  *  9、Grarity            學習如何點擊屏幕時反轉重力  *  10、StateMachine      GameplayKit 運用之場景切換;(**** 中級技能)  *  11、Partilces         學習如何做特效及把特效發生碰撞時移出場景;(**** 中級技能)  *  */ ``` import SpriteKit import GameplayKit class GameScene: SKScene ,SKPhysicsContactDelegate{          var moveAllowed = false /// 場景是否可以移動了;          //MARK: - StateMachine 場景中各個舞臺State     lazy var stateMachine:GKStateMachine = GKStateMachine(states: [         WaitingState(scene: self), //self 爲 GameScene ,把GameScene專入State         PlayState(scene: self),         GameOverState(scene: self)         ])          //MARK: - 場景中的所有SpriteNode     /*      * 1.調用 Elements Class 的節點,須在GameScene的把節點的Custom Class設爲PlayerNode      * 2.Module 設爲項目名稱 SkyNinja      * 3.爲何要設置獨立的class精靈,可以爲GameScene減少代碼,並有利於代碼的複用;      */     var playerNode:PlayerNodeClass!     var coinTempNode:SKSpriteNode!     var bombTempNode:SKSpriteNode!     var mainCamera:SKCameraNode!     var groundNode:SKSpriteNode!    /// 地面     var skyNode:SKSpriteNode!       /// 天空     var spawnElements = SpawnElements() /// 生成節點工具 特別注意,這裏非 spawnElements = SpawnElements!          private var dt:TimeInterval = 0  /// 每一frame的時間差     private var lastUpdateTimeInterval:TimeInterval = 0          override func didMove(to view: SKView) {         super.didMove(to: view)         self.physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)  /// 物理世界的重力         self.physicsWorld.contactDelegate = self               /// 碰撞代理                  initCamera()   /// Camera         initBgMusic()  /// 背景音樂         initPlayer()   /// 初始化玩家         initCoinBomb() /// 臨時的Coin+Bomb         initSkyGroundLine() // 建立物理天空+地面         stateMachine.enter(WaitingState.self) /// 初始化以上的各個精靈SpriteNode後,再進入WaitingState 場景舞臺State     }     //MARK: - 加入Camera     func initCamera(){         mainCamera = childNode(withName: "MainCamera") as! SKCameraNode     }     //MARK: - 移動Camera     func moveCamera(){         self.mainCamera.position.x += CAMERA_MOVE_XPOS ///向右移動     }     //MARK: - 停止Camera     func stopCamera(){         self.mainCamera.removeAllActions()     }     // MARK:-初始化玩家     func initPlayer(){                  playerNode = childNode(withName: "Player") as! PlayerNodeClass         playerNode.physicsBody?.affectedByGravity = true         playerNode.initPlayer()     }     // MARK:-背景音樂     func initBgMusic(){                  let bgMusic = SKAudioNode(fileNamed: "background.mp3")         bgMusic.autoplayLooped = true         addChild(bgMusic)     }          func initCoinBomb(){         coinTempNode = childNode(withName: "CoinTemp") as! SKSpriteNode         bombTempNode = childNode(withName: "BombTemp") as! SKSpriteNode     }     //MARK: - 物理線     func initSkyGroundLine(){                  skyNode = childNode(withName: "Sky") as! SKSpriteNode         let sykLine = LineNode()  /// 生成新的節點 比如 let newNode = SKNode()         sykLine.initSkyLine(size: size, yPos: skyNode.position.y + 10)         addChild(sykLine)                           groundNode = childNode(withName: "Ground") as! SKSpriteNode         let groundLine = LineNode()         groundLine.initGroundLine(size: size, yPos: groundNode.position.y + groundNode.size.height - 10)         addChild(groundLine)              }          // MARK: - 反轉物理世界;     func reverseGravity(){         physicsWorld.gravity *= -1     }          // MARK: - 根據 camera.position.x 移動所有頁面元素;     ///因爲節點anchorPoint爲(0,0),且相機的初始位置爲 1024,所以要把相機的位置扣除1024 即(camera.position.x - self.size.width / 2)     func moveSprites(camera:SKCameraNode){         /// 所有的天空精靈         enumerateChildNodes(withName: "Sky") { (node, error) in             if  node.position.x + self.size.width < (camera.position.x - self.size.width / 2) {                 node.position.x += self.size.width * SCENE_NUMBERS             }         }         /// 所有的地面精靈;         enumerateChildNodes(withName: "Ground") { (node, error) in             if  node.position.x + self.size.width < (camera.position.x - self.size.width / 2 ) {                 node.position.x += self.size.width * SCENE_NUMBERS             }             /// print("所有的地面精靈",node.position.x,(camera.position.x - self.size.width / 2 ))         }         /// 所有線和Camera同步         enumerateChildNodes(withName: "Line") { (node, error) in             // let node = node as! SKNode             node.position.x += CAMERA_MOVE_XPOS             if  node.position.x < -self.size.width {                 node.position.x += self.size.width * SCENE_NUMBERS             }         }         /// 所有樹         /// 樹爲何不:(camera.position.x - self.size.width / 2 ),請注意樹的 anchorPoint(0.5,0.5)         enumerateChildNodes(withName: "Tree") { (node, error) in             if  node.position.x + self.size.width < (camera.position.x ) {                 node.position.x += self.size.width * SCENE_NUMBERS             }         }         /// 所有背景         enumerateChildNodes(withName: "Bg") { (node, error) in                          if  node.position.x + self.size.width < (camera.position.x - self.size.width / 2 ) {                 node.position.x += self.size.width * SCENE_NUMBERS             }         }     }     // MARK: - 生成節點工具 class     @objc func spawnCoins(){         /// print(spawnElements.spawnCoin(camera: mainCamera))         if moveAllowed {             self.addChild(spawnElements.spawnCoin(camera: mainCamera)) /// 傳入主相機位置         }              }          @objc func spawnBombs(){         if moveAllowed {             addChild(spawnElements.spawnBomb(camera: mainCamera,scene: self)) /// 傳入主相機位置         }     }          @objc func removeCoins(){         enumerateChildNodes(withName: "coin") { (node, error) in             if node.position.x < self.mainCamera.position.x - self.size.width {                 /// print("移除coin")                 node.removeFromParent()             }         }     }     // MARK: - 不再生成了;     func stopSpawning(){                  playerNode.removeAction(forKey: "jogging")  /// 移除人物的運動;                  enumerateChildNodes(withName: "coin") { (node, error) in             node.removeAllActions()         }         enumerateChildNodes(withName: "bomb") { (node, error) in             node.removeAllActions()         }     }          //MARK: - 重新開始遊戲;     func restartGame(){                  let newScene = GameScene(fileNamed: "GameScene")!         newScene.size = CGSize(width: SCENE_WIDTH, height: SCENE_HEIGHT)         newScene.anchorPoint = CGPoint(x: 0, y: 0)         newScene.scaleMode   = .aspectFill         let transition = SKTransition.flipHorizontal(withDuration: 0.5)         view?.presentScene(newScene, transition:transition)     }          // MARK: - 監測屏幕點擊事件     override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {         guard let touch = touches.first else {             return         }         let touchLocation = touch.location(in: self) ///獲得點擊的位置         /// 判斷目前的GameScene場景舞臺是哪個state         switch stateMachine.currentState {         case is WaitingState:             /// 獲得按鈕的點擊位置             guard let body = physicsWorld.body(at: touchLocation) else {                 return             }             /// 判斷是否是點擊了PlayButton             guard  let playButton = body.node?.childNode(withName: "PlayButton") as? SKSpriteNode else {                 return             }             /// 如果點擊位置是在PlayButton             if (playButton.contains(touchLocation)){                 playButton.isHidden = true                 stateMachine.enter(PlayState.self) /// 進入開始遊戲;             }                      case is PlayState:             reverseGravity() /// 反轉物理世界;                      case is GameOverState:                          guard let body = physicsWorld.body(at: touchLocation) else {                 return             }             // TapToPlay按鈕;             if let tapToPlay  = body.node?.childNode(withName: "tapToPlay"){                                  if tapToPlay.contains(touchLocation){                     print("重新開始遊戲!")                     restartGame()                 }             }                      default:             break;                                   }     }     // MARK: - 監測碰撞     func didBegin(_ contact: SKPhysicsContact) {                  let bodyA:SKPhysicsBody         let bodyB:SKPhysicsBody         if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {             bodyA = contact.bodyA             bodyB = contact.bodyB         }else{             bodyA = contact.bodyB             bodyB = contact.bodyA         }         ///檢測碰到中間線         if bodyA.categoryBitMask == PhysicsCategory.Player && bodyB.categoryBitMask == PhysicsCategory.MiddleLine {             /// print("碰到屏幕線人物反轉")             playerNode.reversePlayer()          }                  ///檢測碰到coin         if bodyA.categoryBitMask == PhysicsCategory.Player && bodyB.categoryBitMask == PhysicsCategory.Coin {             /// print("碰到屏幕線人物反轉")             let coinAction = SKAction.playSoundFileNamed("coin.wav", waitForCompletion: false)             run(coinAction)             bodyB.node?.removeFromParent()         }                  ///檢測碰到Bomb         if bodyA.categoryBitMask == PhysicsCategory.Player && bodyB.categoryBitMask == PhysicsCategory.Bomb {             /// 播放音樂             let bombAction = SKAction.playSoundFileNamed("ninjaHit.wav", waitForCompletion: false)             run(bombAction)             /// 移除BOMB             /// bodyB.node?.removeFromParent()             stateMachine.enter(GameOverState.self)         }              }     // MARK: - 時時更新update     override func update(_ currentTime: TimeInterval) {                  /// 獲取時間差         if lastUpdateTimeInterval == 0 {             lastUpdateTimeInterval = currentTime         }         dt = currentTime - lastUpdateTimeInterval         lastUpdateTimeInterval = currentTime                  stateMachine.update(deltaTime: dt)  /// 把update傳進各個State裏;     } } ``` 源碼傳送門:https://github.com/apiapia/SkyNinjaGameSpriteKitTutorial 更多手機遊戲教程:http://www.iFIERO.com