# SkyNinjaGameSpriteKitTutorial **Repository Path**: iFIERO/SkyNinjaGameSpriteKitTutorial ## Basic Information - **Project Name**: SkyNinjaGameSpriteKitTutorial - **Description**: iFIERO -- SkyNinja天猪之城 SpriteKit iOS游戏源码 - **Primary Language**: Swift - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2018-06-25 - **Last Updated**: 2024-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README SKY NINJA SpriteKit Game Tutorial GIF动画演示:https://upload-images.jianshu.io/upload_images/3896436-8488f7fa265bdeb1.gif?imageMogr2/auto-orient/strip // // 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, 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
百度源码备份:https://pan.baidu.com/s/1_1rt4fZR2OuDSg-lFDAUQw
更多手机游戏教程:http://www.iFIERO.com