iT邦幫忙

2021 iThome 鐵人賽

DAY 25
0
Mobile Development

從零開始的8-bit迷宮探險!Swift SpriteKit 遊戲開發實戰系列 第 25

從零開始的8-bit迷宮探險【Level 25】今天又是嶄新的一天,回到原點

山姆再次勇闖黑森林,但是這次他大意了!
在旁埋伏的 Storm 跟 Lightning 趁山姆一不注意,使出了閃電暴風雨。
淋濕的山姆倒在地上。
幸好,這裡是從零開始的遊戲世界,山姆不只一條命。
於是山姆又重新站了起來,再次從起點出發。

今日目標

  • 新增主角的生命值,並顯示在遊戲畫面上
  • 主角被怪物攻擊後,生命值減 1,主角與怪物都回到原點
  • 建立遊戲的流程,讓遊戲可以不斷的重新開始
  • 讓遊戲開始時有個緩衝時間,再開始遊戲

PS. 這裡是開發 iOS 手機遊戲的系列文,如果還沒看過之前 劇情 文章的朋友,歡迎先點這邊回顧唷!


加入生命值機制

目前主角一被怪物攻擊後,就會直接遊戲結束,但是這樣的節奏太快了,因此我們為主角新增生命值的機制,讓主角除了一開始的生命外,還有額外三條命 (共 4 條命)。

  • 新增 samLife 紀錄還有幾條生命
  • 新增 lifeNodelifeIconNodelifeLabel 節點來顯示主角的圖示及剩下的生命值
    • lifeNode:外層的 node,設定與螢幕同寬,高 30,加到遊戲場景中
    • lifeIconNode:圖示的 node,這邊放上主角的圖片,設定寬高及位置,加到 lifeNode
    • lifeLabel:顯示主角剩餘生命值,文字設定為 "X \(self.samLife)",設定文字的顏色、尺寸、字體、位置,讓他垂直置中,水平置左,並加到 lifeNode
  • 調整 applySafeArea 方法,校正 position,讓生命值的外層節點 lifeNode 的位置在地圖節點下方
  • GameScene.swift
class GameScene: SKScene {
    ...
    var samLife: Int = 3
    var lifeNode: SKSpriteNode?
    var lifeIconNode: SKSpriteNode?
    var lifeLabel: SKLabelNode?
    
    override func didMove(to view: SKView) {
        ...
        self.lifeNode = SKSpriteNode(color: .clear, size: CGSize(width: CGFloat(self.size.width), height: CGFloat(30)))
        self.lifeIconNode = SKSpriteNode(imageNamed: "sam_down_1")
        self.lifeLabel = SKLabelNode(text: "X \(self.samLife)")
        if let lifeNode = self.lifeNode, let lifeIconNode = self.lifeIconNode, let lifeLabel = self.lifeLabel {
            lifeNode.anchorPoint = CGPoint(x: 0, y: 0.5)
            self.addChild(lifeNode)
            
            lifeIconNode.anchorPoint = CGPoint(x: 0, y: 0.5)
            lifeIconNode.size.width = CGFloat(25)
            lifeIconNode.size.height = CGFloat(25)
            lifeIconNode.position = CGPoint(x: 5, y: 0)
            lifeNode.addChild(lifeIconNode)

            lifeLabel.fontColor = UIColor.white
            lifeLabel.fontSize = CGFloat(22)
            lifeLabel.fontName = "Copperplate"
            lifeLabel.position = CGPoint(x: 35, y: 0)
            lifeLabel.verticalAlignmentMode = .center
            lifeLabel.horizontalAlignmentMode = .left
            lifeNode.addChild(lifeLabel)
        }
    }
    
    func applySafeArea() {
        ...
        if let mapNode = self.mapNode, let scoreNode = self.scoreNode, let lifeNode = self.lifeNode {
            mapNode.position = CGPoint(x: 0, y: -self.topSafeArea - scoreNode.size.height)
            scoreNode.position =  CGPoint(x: 0 ,y: -self.topSafeArea - scoreNode.size.height/2)
            lifeNode.position = CGPoint(x: 0, y: -self.topSafeArea - scoreNode.size.height - mapNode.size.height - 15)
        }
    }
}

執行結果

遊戲中已經出現顯示生命值的畫面囉!
https://imgur.com/xpDana5.gif


遊戲重新開始流程

有了計算剩餘生命值的變數後,可以依照剩餘的生命數量,讓遊戲重新開始或是真的遊戲結束。今天我們會先帶大家實作遊戲重新開始的部分,遊戲結束明天會再說明
在這邊我們可以建立遊戲重新開始的流程,需要做的事情有:

  • 重設設定
    • 主角減一條生命、跌倒狀態回到預設
    • 主角及怪物都設定回原本的位置
  • 遊戲開始
    • 主角及怪物狀態設定為可以移動
    • 怪物開始移動
    • 設定隨機啟動玩耍模式的 Timer

重設設定

  • 找到之前寫好的 gameStop 方法 (在主角被怪物攻擊時會觸發),加上以下程式碼:
    • samLife生命值減 1
    • 判斷生命值
      • 若以無剩餘生命,則遊戲結束,我們先印出 log,後續再加上動作
      • 若還有剩餘生命,則設定 3 秒後將遊戲重設 gameReset
  • 遊戲重新設定 gameReset
    • 更新畫面上角色的生命值
    • 呼叫 resetPosition 方法,將主角及怪物設定回到遊戲初始時的位置
    • 將怪物設定回攻擊模式 .ATTACK
    • 將主角跌倒狀態重置 self.isSamFall = false
  • GameScene.swift
class GameScene: SKScene {
    func gameStop() {
        ...
        self.samLife -= 1
        if self.samLife < 0 {
            print("遊戲結束")
        } else {
            Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(gameReset), userInfo: nil, repeats: false)
        }
    }
    @objc func gameReset() {
        self.lifeLabel?.text = "X \(self.samLife)"
        for weather in self.weathers {
            weather.resetPosition()
            weather.setMode(mode: .ATTACK)
        }
        if let sam = self.sam {
            sam.resetPosition()
        }
        self.isSamFall = false
    }
}

角色重設位置

在共用的角色類別中新增 resetPosition 方法

  • gridXgridY 設定回初始格子點
  • 更新 position
  • 播放對應的角色動畫
  • GameCharacter.swift
class GameCharacter {
    ...
    func resetPosition() {
        self.gridX = startGridX
        self.gridY = startGridY
        self.node.position = CGPoint(x: self.gridWH * self.startGridX + (self.gridWH/2), y: -self.gridWH * self.startGridY - (self.gridWH/2))
        self.playAnimation(imageName: self.imageName, num: 2)
    }
}

執行結果

主角被攻擊 3 秒後,角色們都回到原本的位置,主角生命值減 1
https://imgur.com/rzlKp6Z.gif

遊戲開始

考量到遊戲的流程是會不斷循環的,我們可以新增一個共用的遊戲開始方法,在一進遊戲時、以及在主角減一條生命後,都可以呼叫

  • 新增 gameStart 方法,讓遊戲開始後有一段時間的緩衝,讓玩家可以看一下地圖,才遊戲開始。請設定 Timer,5 秒後執行遊戲開始動作 gameStartAction
  • 將原本寫在 didMove 的開始遊戲後的動作移到 gameStartAction,包含設定角色們可以移動、讓怪物開始移動,以及產生隨機時間後變成玩耍模式的 Timer
  • GameScene.swift
class GameScene: SKScene {
    ...
    func gameStart() {
        Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(gameStartAction), userInfo: nil, repeats: false)
    }
    @objc func gameStartAction() {
        for weather in self.weathers {
            weather.setCanMove(isCanMove: true)
            weather.startMove(direction: .NONE)
        }
        if let sam = self.sam {
            sam.setCanMove(isCanMove: true)
        }
        let randomTime = Int.random(in: 10...50)
        self.randomTimer = Timer.scheduledTimer(timeInterval: TimeInterval(randomTime), target: self, selector: #selector(attackToPlayModeAction), userInfo: nil, repeats: false)
    }
}

呼叫遊戲開始方法

  • 在進入遊戲後,呼叫 gameStart 方法
  • 角色被攻擊後,在 gameRestart 裡呼叫 gameStart
  • GameScene.swift
class GameScene: SKScene {
    ...
    override func didMove(to view: SKView) {
        ...
        self.gameStart()
    }
    @objc func gameRestart() {
        ...
        self.gameStart()
    }
}

執行結果

遊戲成功加上生命值機制了!在生命值沒有歸零前,可以不斷重新開始遊戲,之前收集的水晶跟分數也都還會保留
https://imgur.com/hqjtipx.gif


今日小結

目前已經加上重新開始遊戲的流程了,明日會繼續將遊戲結束後的流程加上
我們預計在遊戲結束時,會切換到另外一個場景,並且顯示玩家這場的遊戲得分,再加上重新開始遊戲的按鈕
我們明日見囉~/images/emoticon/emoticon13.gif


上一篇
從零開始的8-bit迷宮探險【Level 24】誰才是高玩?紀錄本機最高得分
下一篇
從零開始的8-bit迷宮探險【Level 26】這遊戲沒有華佗,不能補血啊!Game Over 場景切換
系列文
從零開始的8-bit迷宮探險!Swift SpriteKit 遊戲開發實戰30

尚未有邦友留言

立即登入留言