iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0
Mobile Development

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

從零開始的8-bit迷宮探險【Level 19】這個相遇我等了一輩子了-偵測主角與怪物接觸

山姆回想著剛剛看到的雪人怪,還心有餘悸。
轉了個彎,繼續找尋水晶。
「碰!」山姆迎頭撞上了白色的物體,帶有冰涼的感覺。
「糟了!得趕快逃跑才行。」

今日目標

  • 偵測主角與怪物的接觸
  • 播放接觸時的動畫

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


偵測距離

在 SKScene 類別中的 update 方法,會在遊戲的每幀 (per-frame) 被呼叫,我們可以覆寫 update 方法,並且將一些想要判斷的邏輯放在裡面,當符合邏輯時,就可以執行對應的事情

  • 判斷主角與所有怪物之間的距離,做出類似接觸的感覺
    • 使用 for-in 遍歷所有的怪物 (self.weathers)
    • 使用 where 分別寫上當兩者的 gridXgridY 相等時,另一個軸向的距離相減小於一個格子的寬度 (這邊因為圖片在之前的章節實作時有放大,所以我們加上了一點數值 6 微調)
    • abs:可以取兩者相減後的絕對值
    • 當怪物的模式為 .ATTACK.PLAY 時,且 isSamFallfalse 才能讓怪物攻擊主角,並且執行 gameStop() 方法。這邊設置 isSamFall 的用意是避免連續進入這個邏輯判斷,導致重複執行
    • gameStop():將所有 Timer 關閉,並讓主角執行跌倒的動畫,這邊 num 帶入 3,repeatAni 帶入 false
  • GameScene.swift
class GameScene: SKScene {
    ...
    var isSamFall: Bool = false
    ...
    override func update(_ currentTime: TimeInterval) {
        guard let sam = self.sam else {
            return
        }
        for weather in self.weathers where weather.gridX == sam.gridX && abs(weather.node.position.y - sam.node.position.y) <= CGFloat(self.gridWH + 6) || weather.gridY == sam.gridY && abs(weather.node.position.x - sam.node.position.x) <= CGFloat(self.gridWH + 6) {
            if weather.mode == .ATTACK || weather.mode == .PLAY {
                if !self.isSamFall {
                    self.isSamFall = true
                    self.gameStop()
                }
            }
        }
    }
    func gameStop() {
        // 停止 timer
        self.stopTimer()
        
        // 跌倒動畫
        if let sam = self.sam {
            sam.playAnimation(imageName: "sam_fall", num: 3, repeatAni: false)
        }
    }
}

接觸時的動畫序列圖

準備三張序列圖片,將它們命名為:

  • sam_fall_1
  • sam_fall_2
  • sam_fall_3

https://imgur.com/lVvj6im.png

來看看成果吧

https://imgur.com/WZHP4fj.gif

我們的主角很帥氣的滑壘跌倒了!
因為角色們都還是處在移動的狀態,接著來調整這個問題

停止移動

角色們都需要有停止的狀態,所以我們回到角色 GameCharacter 類別,加上一個參數 isCanMove,預設為 false,用來記錄目前是否可以移動
在 Move 協定中,加上設定移動的方法 setCanMove(isCanMove: Bool)

  • GameCharacter.swift
class GameCharacter {
    ...
    var isCanMove: Bool = false
    ...
}
protocol Move {
    func startMove(direction: Direction)
    func endMove()
    func setCanMove(isCanMove: Bool)
}

調整主角 Sam 類別

  • startMove 方法一開始時,先判斷目前是否可以移動,如果不行則設定 isMovingfalse ,並且 return,不繼續執行後續的移動
  • 實作 setCanMove 方法,改變 isCanMove 的值
  • Sam.swift
class Sam: GameCharacter, Move {
    func startMove(direction: Direction) {
        if !self.isCanMove {
            self.isMoving = false
            return
        }
        ...
    }
    func setCanMove(isCanMove: Bool) {
        self.isCanMove = isCanMove
    }
}

調整怪物 Weather 類別

  • 與主角類別一樣,加上可否移動的判斷及實作方法
  • Weather.swift
    func startMove(direction : Direction) {
        if !self.isCanMove {
            self.isMoving = false
            return
        }
        ...
    }
    func setCanMove(isCanMove: Bool) {
        self.isCanMove = isCanMove
    }

設定及判斷可否移動

  • 在進入遊戲時 (didMove)
    • 先將所有怪物設定為可以移動 setCanMove(isCanMove: true),才執行開始移動 startMove(direction: .NONE)
    • 將主角設定為可以移動 setCanMove(isCanMove: true)
  • 遊戲停止時 (gameStop)
    • 將主角設定為不能移動,才執行跌倒動畫
    • 將所有怪物設定為不能移動
  • 偵測方向按鈕 (touchesBegan)
    • 在一開始的地方,先判斷主角可否移動,若不行則直接 return
  • GameScene.swift
class GameScene: SKScene {
    override func didMove(to view: SKView) {
        ...
        for weather in self.weathers {
            weather.setCanMove(isCanMove: true)
            weather.startMove(direction: .NONE)
        }
        if let sam = self.sam {
            sam.setCanMove(isCanMove: true)
        }
    }
    func gameStop() {
        ...
        // 停止移動主角 & 跌倒動畫
        if let sam = self.sam {
            sam.setCanMove(isCanMove: false)
            sam.playAnimation(imageName: "sam_fall", num: 3, repeatAni: false)
        }
        
        // 停止移動怪物
        for weather in weathers {
            weather.setCanMove(isCanMove: false)
        }
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let sam = self.sam, sam.isCanMove == true else {
            return
        }
        ...
    }
}

看一下最後成果

主角及怪物接觸時,所有角色都成功停止移動了

https://imgur.com/j7KUCtz.gif


今日小結

目前我們的遊戲畫面上,已經有主角及怪物了,但是還少了個重要的東西,就是主角的遊戲目的:收集水晶。
明日就會帶大家在遊戲中加上水晶囉!/images/emoticon/emoticon12.gif


參考來源:
update(_:)
abs


上一篇
從零開始的8-bit迷宮探險【Level 18】為什麼他們開始亂跑?捉摸不定的怪物移動模式
下一篇
從零開始的8-bit迷宮探險【Level 20】搜集水晶可以召喚神龍嗎?
系列文
從零開始的8-bit迷宮探險!Swift SpriteKit 遊戲開發實戰30

尚未有邦友留言

立即登入留言