• (三)宇宙大战 Space Battle -- 场景SCENE切换、UserDefaults统计分数、Particle粒子效果


    此《宇宙大战 Space Battle》SpirteKit手机游戏教程共分为三系列:

    (一)宇宙大战 Space Battle -- 新建场景Scene、精灵节点、Particle粒子及背景音乐
    (二)宇宙大战 Space Battle -- 无限循环背景Endless、SpriteKit物理碰撞、CoreMotion加速计
    (三)宇宙大战 Space Battle — 场景SCENE切换、UserDefaults统计分数、Particle粒子效果(你正在此处进行学习)

    一、如何进行各个场景之间的切换

     
    场景SCENE切换

    如上图所示,共分为三个场景:
    1、MainScene.sks -- 用户打开APP时一开始看到的画面,等待用户点击"Play"按钮;
    2、GameScene.sks -- 游戏进行中的场景画面,用于创建无限循环背景Endless、监测SpriteKit物理碰撞、应用CoreMotion加速计,判断游戏的业务逻辑;
    3、LoseScene.sks -- 游戏结束时的场景画面,记录当届分数,记录最高分并应用UserDefaults储存分数在手机沙盒当中,点击"Tap to play"按钮回到GameScene游戏场景画面;

     
    目录文件在工程项目中的Scenes文件夹中

    我们依据第一节所学到的知识,新建一个文件,在Scenes文件夹中,Mouse右建 -> New File -> 选择 iOS -> SpriteKit Scene -> Next 命名一个新的场景为 MainScene.sks

     
    新建一个文件
     
    SpriteKit Scene
     
    分别拖动三个ColorSprite到MainScene.sks场景中
     
    三个精灵Spirte节点的Zposition为1,位于背景的上方

    另新建一个文件,也是在Scenes文件夹中,Mouse右建 -> New File -> 选择 iOS -> Swift File -> Next 命名为 MainScene.swfit ,关联 MainScene.sks的 Custom Class 为 MainScene.swift

     
    关联 MainScene.sks的 Custom Class 为 MainScene.swift

    在Game ViewController设置开始场景为 MainScene.sks

     if let scene = MainScene(fileNamed: "MainScene") {
                    scene.size = CGSize( 1536, height: 2048)
                    scene.scaleMode = .aspectFill
                    view.presentScene(scene)
                }
    

    设置好启动场景后,我们再来MainScene.swfit编写代码:

    在didMove(to view: SKView)里,

    override func didMove(to view: SKView) {
            /// Play为场景命名的节点名称
            play = childNode(withName: "Play") as! SKSpriteNode
            learnTemp = childNode(withName: "learnTemp") as! SKSpriteNode
            // 背景音乐
            let bgMusic = SKAudioNode(fileNamed: "spaceBattle.mp3")
            bgMusic.autoplayLooped = true
            addChild(bgMusic)
        }
    

    接下来,我们就要来判断用户的触摸位置,是不是按到 Play 按钮
    在 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 函数里:

    /// 判断用户是否有点击  
    guard  let touch = touches.first else {
                return  
      }
      let touchLocation = touch.location(in: self) /// 获得点击的位置
      if  play.contains(touchLocation) {
              /// 表示触摸点击在play按钮当中
            }
    

    把切换进入GameScene.sks的代码写在 play.contains(){}函数里

    let reveal = SKTransition.doorsOpenVertical(withDuration: TimeInterval(0.5))
               ///场景切换
                let mainScene = GameScene(fileNamed: "GameScene")
                mainScene?.size = self.size
                mainScene?.scaleMode = .aspectFill
                self.view?.presentScene(mainScene!, transition: reveal)
    

    这样子,我们就完成了场景之间的切换了!

    同样,GameScene切换到LoseScene、LoseScene切换为GameScene 也是应用 self.view?.presentScene的方法,具体代码如下:

    在外星人Alien撞击到飞船,游戏结束,GameScene切换到LoseScene:

    // MARK: 外星人Alien撞击到飞船,游戏结束
        func alienHitSpaceShip(nodeA:SKSpriteNode,nodeB:SKSpriteNode){
     // 切换游戏结束场景
                        let reveal = SKTransition.doorsOpenHorizontal(withDuration: TimeInterval(0.5))
                        let loseScene = LoseScene(fileNamed: "LoseScene")
                        loseScene?.size = self.size
                        loseScene?.scaleMode = .aspectFill
                        self.view?.presentScene(loseScene!, transition: reveal)
    }
    

    二、应用UserDefaults储存游戏分数和最高分

    我们在GameScene.swift里

     private var currentScore:SKLabelNode! // 当前分数节点
     private var cScore:Int = 0  /// Int 存当前分数
     private var highScore:SKLabelNode!    // 最高分数
     private var hScore:Int = 0 /// Int 存最高分数
    

    在子弹击中外星人时记录分数
    func bulletHitAlien(nodeA:SKSpriteNode,nodeB:SKSpriteNode){}

    func bulletHitAlien(nodeA:SKSpriteNode,nodeB:SKSpriteNode){
    
      // 分数统计
            cScore += 1
            currentScore.text = "SCORE:(cScore)"
            // 保存当前分数
            UserDefaults.standard.set(cScore, forKey: "CURRENTSCORE")
            
            if cScore > hScore {
                hScore = cScore
                highScore.text = "High:(hScore)"
                // 保存最高分数
                UserDefaults.standard.set(cScore, forKey: "HIGHSCORE")
            }
    }
    

    我们应用UserDefaults.standard.set方法,分别储存当前分数和最高分数对应的键值forKey:CURRENTSCORE和HIGHSCORE,然后,在游戏结束的场景LoseScene.swift通过UserDefaults.standard.integer(forKey: "CURRENTSCORE")取出存在手机沙盒里的值;

    currentScore.text = "SCORE:(UserDefaults.standard.integer(forKey: "CURRENTSCORE"))"   // 取出当前分数
     highScore.text    = "HIGH SCORE:(UserDefaults.standard.integer(forKey: "HIGHSCORE"))" // 取出沙盒中的最高分数
    
     
    取出沙盒中的分数,并分别把当前分数、最高分数显示在LoseScene场景当中

    代码如下:

     private var currentScore:SKLabelNode! // 当局分数
     private var highScore:SKLabelNode!    // 最高分数
        
        override func didMove(to view: SKView) {
            // 找到 名称为Play的节点
            play = childNode(withName: "Play") as! SKSpriteNode
            currentScore = childNode(withName: "currentScore") as! SKLabelNode
            highScore    = childNode(withName: "highScore")    as! SKLabelNode
            currentScore.text = "SCORE:(UserDefaults.standard.integer(forKey: "CURRENTSCORE"))"   // 取出当前分数
            highScore.text    = "HIGH SCORE:(UserDefaults.standard.integer(forKey: "HIGHSCORE"))" // 取出沙盒中的最高分数
            
        }
    

    我们补充一下有关Swift数据储存方式的相关知识,数据储存是存在iOS沙盒的当中,沙盒,顾名思义,即各个app之间是无法互相访问数据的,其目录结构为:

     
    每个应用程序都有对应的私有目录,其根目录为Home目录。该目录下又三个文件夹:Documents、Library、tmp
     
    UserDefaults的存放位置

    每个iOS应用都有自己的应用沙盒(应用沙盒就是文件系统目录),与其他文件系统隔离。应用必须待在自己的沙盒里,其他应用不能访问该沙盒。沙盒下的目录如下:

    Documents: 保存应⽤运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录。

    tmp: 保存应⽤运行时所需的临时数据,使⽤完毕后再将相应的文件从该目录删除。应用 没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。

    Library/Caches: 保存应用运行时⽣成的需要持久化的数据,iTunes同步设备时不会备份 该目录。⼀一般存储体积大、不需要备份的非重要数据,比如网络数据缓存存储到Caches下。

    Library/Preference: 保存应用的所有偏好设置,如iOS的Settings(设置) 应⽤会在该目录中查找应⽤的设置信息。iTunes同步设备时会备份该目录。

    UserDefaults可以存储的数据类型:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary,如果把有null的value放入userDefaults,程序会崩。

    //存储基础类型,以Int为例。
    UserDefaults.standard.set(15, forKey:"theKey")
    
    //读取基础类型,以Int为例。
    let num = UserDefaults.standard.integer(forKey: "theKey")
    

    注意:不要用UserDefaults储存用户的密码。

    三、SpirteKit Particle粒子效果

    我们选中Explosion.sks来查看Particle粒子效果,右侧为粒子效果的属性面板,我们可以修改属性中的相关值,来观察爆炸的变化。

     
    Particle粒子效果

    在代码中引入,外星人Alien撞击到飞船引入粒子特效。

    // 击中粒子效果 Particle
    func alienHitSpaceShip(nodeA:SKSpriteNode,nodeB:SKSpriteNode){
    if (nodeA.physicsBody?.categoryBitMask == PhysicsCategory.Alien  || nodeB.physicsBody?.categoryBitMask == PhysicsCategory.Alien) && (nodeA.physicsBody?.categoryBitMask == PhysicsCategory.SpaceShip || nodeB.physicsBody?.categoryBitMask == PhysicsCategory.SpaceShip) {
    
                let explosion = SKEmitterNode(fileNamed: "Explosion")!
                explosion.position = nodeA.position
                self.addChild(explosion)
       }
    }
    

    子弹击中外星飞船的粒子特效

     // MARK: 子弹vs外星人
        func bulletHitAlien(nodeA:SKSpriteNode,nodeB:SKSpriteNode){
            
            // 击中粒子效果 Particle
            let explosion = SKEmitterNode(fileNamed: "ExplosionBlue")!
            explosion.position = nodeA.position
            self.addChild(explosion)
    
           explosion.run(SKAction.sequence([
                SKAction.wait(forDuration: 0.3),
                SKAction.run {
                    explosion.removeFromParent()
                }]))
    }
    

    子弹引入粒子特效,在func spawnBulletAndFire(){}里

    /*
             * 粒子效果
             * 1.新建一个SKNode => trailNode
             * 2.新建粒子效果SKEmitterNode,设置tragetNode = trailNode
             * 3.子弹加上emitterNode
             */
            let trailNode = SKNode()
            trailNode.zPosition = 1
            trailNode.name = "trail"
            addChild(trailNode)
            
            let emitterNode = SKEmitterNode(fileNamed: "ShootTrailBlue")! // particles文件夹存放粒子效果
            emitterNode.targetNode = trailNode  // 设置粒子效果的目标为trailNode => 跟随新建的trailNode
            bulletNode.addChild(emitterNode)    // 在子弹节点Node加上粒子效果;
    

    如果去除 emitterNode.targetNode = trailNode,则子弹没有拖影特效

    其中特别要注意的是要判断哪个是子弹精灵节点,并移除所有子弹精灵的子节点的特效。

    nodeA.removeAllChildren() // 移除所有子效果 emitter

     // 判断哪个是子弹节点bulletNode,碰撞didBegin没有比较大小时,则会相互切换,也就是A和B互相切换;
            if nodeA.physicsBody?.categoryBitMask == PhysicsCategory.BulletBlue {
                nodeA.removeAllChildren() // 移除所有子效果 emitter
                nodeA.isHidden = true     // 子弹隐藏
                nodeA.physicsBody?.categoryBitMask = 0 // 设置子弹不会再发生碰撞
                nodeB.removeFromParent()  // 移除外星人
            }else if nodeB.physicsBody?.categoryBitMask == PhysicsCategory.BulletBlue {
                nodeA.removeFromParent()  // 移除外星人
                nodeB.removeAllChildren()
                nodeB.isHidden =  true
                nodeB.physicsBody?.categoryBitMask = 0
            }
    

    至此,我们就完成了Space Battle宇宙大战SpirteKit游戏的所有章节了。

    很感谢大家的阅读,如果有疑问可以在文章下方的评论栏提问,也请大家多多指出文中的不足之处,一起努力,一起从开发游戏当中获得满满的乐趣,收获满满的自豪感。

    游戏源码传送门:https://github.com/apiapia/SpaceBattleSpriteKitGame
    更多游戏教程:http://www.iFIERO.com

  • 相关阅读:
    20159320《网络攻防实践》第7周教材总结
    20159320《网络攻防实践》第7周视频总结
    20159320《网络攻防实践》第7周学习总结
    软件中反跟踪技术和软件调试
    20159320《网络攻防实践》第6周学习总结
    20159320《网络攻防实践》第6周教材总结
    20159320《网络攻防实践》第6周视频总结
    20159318 《网络攻防实践》第6周学习总结
    20159318 《网络攻击与防范》第5周学习总结
    20159318《网络攻防技术与实践》第4周学习总结
  • 原文地址:https://www.cnblogs.com/apiapia/p/9278015.html
Copyright © 2020-2023  润新知