這是第二款
// // 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