iFIERO -- SkyNinja天猪之城 SpriteKit iOS游戏源码

SkyNinja.gif
Snip20180612_2.png
Snip20180612_9.png
Snip20180612_3.png

//
// 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343