遠方有個一閃一閃的東西,吸引了山姆的目光。
「這顆水晶...特別的大顆!」山姆跑了過去。
碰觸到魔幻水晶的那一剎那,彷彿得到了全宇宙的力量。
這是攻守互換的一刻!山姆的反擊開始了!
PS. 這裡是開發 iOS 手機遊戲的系列文,如果還沒看過之前
劇情文章的朋友,歡迎先點這邊回顧唷!
我們新增魔幻水晶類別,讓它繼承收集物類別
請新增一個 swift 檔案
點選新增檔案
選擇 Swift File -> Next
將檔案命名為 MagicalCrystal,點擊 Create 按鈕,完成 .swift 檔的新增
請先 import SpriteKit
import SpriteKit
這邊做個讓魔幻水晶可以閃爍的效果,我們新增 playAnimation 方法
SKAction.fadeAlpha 來做閃爍效果,to 帶入 alpha 值 (不透明度),duration 帶入持續時間SKAction.sequence 帶入 SKAction 的陣列SKAction.repeatForever 重複播放這個序列動作run 方法播放動畫class MagicalCrystal: Collection {
    func playAnimation() {
        let ani1 = SKAction.fadeAlpha(to: 0, duration: 0.6)
        let ani2 = SKAction.fadeAlpha(to: 1, duration: 0.3)
        let aniAlpha = SKAction.sequence([ani1, ani2])
        let aniRepeat = SKAction.repeatForever(aniAlpha)
        self.node.run(aniRepeat)
    }
}
在先前的章節已經設定過地圖上要畫的圖片代號,想複習的朋友可以點這邊
* 代號代表這個位置要畫上魔幻水晶let mapDraw = [
    "ccccccccpccccccci",
    "   .....e*......b",
    "aam.1ji.s.11.zy.b",
    "  d.3gh....1.wx.b",
    "jcl.....ji.1....b",
    "d*...11.gh...rt.b",
    "d.11.nm....2....b",
    "d..1.kl.22.1.naah",
    "ot...       .b   ",
    "d..1.jcu vci.b   ",
    "d.12.d     b.kccc",
    "d+...gaaaaah.    ",
    "gaam.       .11.n",
    "   d.rt.1#.q....b",
    "   d....21.e.ji.b",
    "cccl.ji....e.gh.b",
    "    .gh.13.s....b",
    "aaam....1....21.b",
    "   d...23.rt.1..b",
    "   d.1.........3b",
    "jccl.1.rft.3.1.1b",
    "d*.............*b",
    "gaaaaaaaaaaaaaaah",
]
請準備好圖片,並且拖拉進專案中

magicCrystals,準備將所有畫面上的魔幻水晶存在這邊drawMap 方法裡,再加上 "*" 的 casemapNode 裡加入魔幻水晶的 node
magicCrystals 陣列中class GameScene: SKScene {
    ...
    var magicCrystals: [Collection] = []
    ...
    func drawMap() {
        for i in 0..<gridYCount {
            let mapRowArr = Array(mapDraw[i]);
            for j in 0..<gridXCount {
                let mapKeys = wallMapping.keys
                switch mapRowArr[j] {
                ...
                case "*":
                    let magicalCrystal = MagicalCrystal(gridWH: self.gridWH, gridX: j, gridY: i, imageName: "magical-crystal")
                    self.mapNode!.addChild(magicalCrystal.node)
                    self.magicCrystals.append(magicalCrystal)
                    magicalCrystal.playAnimation()
                default:
                    break
                }
            }
        }
    } 
}

update 方法裡,再加上主角跟魔幻水晶之間的位置判斷,當主角跟魔幻水晶的格子位置一樣時,並且還沒有被收集時,就將魔幻水晶設定成被收集了 setGotten(isGotten: true)
class GameScene: SKScene {
    override func update(_ currentTime: TimeInterval) {
        ...
        for magicCrystal in self.magicCrystals where !magicCrystal.isGotten && magicCrystal.gridX == sam.gridX && magicCrystal.gridY == sam.gridY {
            magicCrystal.setGotten(isGotten: true)
        }
    }
}

setMode(mode: .ESCAPE)
magicTimer,先判斷如果 magicTimer 不為 nil 時,關閉上一個 magicTimer,這邊的目的是防止 Timer 還沒結束時又再次啟動 TimereacapeToAttackModeAction
class GameScene: SKScene {
    ...
    var magicTimer: Timer? = nil
    ...
    override func update(_ currentTime: TimeInterval) {
        ...
        for magicCrystal in self.magicCrystals where !magicCrystal.isGotten && magicCrystal.gridX == sam.gridX && magicCrystal.gridY == sam.gridY {
            magicCrystal.setGotten(isGotten: true)
            for weather in self.weathers {
                weather.setMode(mode: .ESCAPE)
            }
            if let magicTimer = self.magicTimer {
                magicTimer.invalidate()
            }
            // 20秒後回復
            self.magicTimer = Timer.scheduledTimer(timeInterval: 20, target: self, selector: #selector(eacapeToAttackModeAction), userInfo: nil, repeats: false)
        }
    }
    @objc func eacapeToAttackModeAction() {
        self.magicTimer = nil
        for weather in self.weathers {
            if weather.mode == .ESCAPE {
                weather.setMode(mode: .ATTACK)
            }
        }
    }
}
變成逃逸模式後的怪物,會呈現另一種樣貌,作為區別


getImage 中加入 .ESCAPE 的 case 了,檔名為 cloud
enum Mode {
    case ATTACK
    case ESCAPE
    case PLAY
    case REBIRTH
    
    func getImage(role: Role) -> String {
        switch self {
        ...
        case .ESCAPE:
            return "cloud"
        case .REBIRTH:
            return "water"
        }
    }
}
updateMode 方法裡,加上 .ESCAPE case
isTrace 值改為 false,讓怪物偵測的路徑是往取得離主角較遠的方向前進moveInterval,將速度調稍快一點class Weather: GameCharacter, Move {
    func updateMode() {
        switch mode {
        case .ATTACK:
            ...
            self.setPathMode(isTrace: true)
        case .PLAY:
            ...
            self.setPathMode(isTrace: true)
        case .ESCAPE:
            self.setTarget(targetX: self.sam!.gridX, targetY: self.sam!.gridY)
            self.setPathMode(isTrace: false)
            self.moveInterval = 0.25
        ...
        }
    }
    func setPathMode(isTrace: Bool) {
        self.isTrace = isTrace
    }
}
可以看到當吃到魔幻水晶後,怪物都變成白雲了,並且往主角的反方向移動。
這時候當怪物與主角碰觸時,怪物就不能攻擊主角了。
主角與怪物接觸 判斷的地方,再加上當前模式為 .ESCAPE 的判斷,讓怪物變成重生的模式 setMode(mode: .REBIRTH)
class GameScene: SKScene {
    ...
    override func update(_ currentTime: TimeInterval) {
        ...
        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()
                }
            } else if weather.mode == .ESCAPE {
                // 讓怪物重生
                weather.setMode(mode: .REBIRTH)
            }
        }
    }
}
變成重生模式後的怪物,會呈現水滴的樣貌


weatherHome
isTrace 為 true
moveInterval,將移動速度調快class Weather: GameCharacter, Move {
    ...
    func updateMode() {
        switch mode {
        ...
        case .REBIRTH:
            self.setTarget(targetX: gridMapping.weatherHome.x, targetY: gridMapping.weatherHome.y)
            self.setPathMode(isTrace: true)
            self.moveInterval = 0.2
        }
    }
}
struct gridMapping {
    ...
    struct weatherHome {
        static let x = 8
        static let y = 10
    }
}
為了讓怪物能更快的回到出生點,我們分別在出生點的左右入口處,以及其上方入口處新增位置設定
struct gridMapping {
    ...
    struct weatherHomeEntryLeft {
        static let x = 4
        static let y = 8
    }
    struct weatherHomeEntryRight {
        static let x = 12
        static let y = 8
    }
    struct weatherHomeEntry {
        static let x = 8
        static let y = 8
    }
}
startMove 方法
.REBIRTH,以及當下的格子位置在 weatherHomeEntryLeft、weatherHomeEntryRight、weatherHomeEntry 則強制讓怪物分別往 .RIGHT、.LEFT、.DOWN 的方向移動,讓怪物順利回到出生點weatherHomeEntry 的位置時,則讓怪物只能左右移動,無法回到出生點class Weather: GameCharacter, Move {
    ...
    func startMove(direction : Direction) {
        ...
        if !isBack || !validDirections.contains(self.direction) {
            self.direction = newDirection
        }
        if self.mode == .REBIRTH {
            if gridX == gridMapping.weatherHomeEntryLeft.x && gridY == gridMapping.weatherHomeEntryLeft.y {
                self.direction = .RIGHT
            }
            if gridX == gridMapping.weatherHomeEntryRight.x && gridY == gridMapping.weatherHomeEntryRight.y {
                self.direction = .LEFT
            }
            if gridX == gridMapping.weatherHomeEntry.x && gridY == gridMapping.weatherHomeEntry.y {
                self.direction = .DOWN
            }
        } else { // 其餘時間不能回家
            if gridX == gridMapping.weatherHomeEntry.x && gridY == gridMapping.weatherHomeEntry.y {
                self.direction = [Direction.LEFT, Direction.RIGHT].randomElement()!
            }
        }
        ...
    }
}
.ATTACK,再次回到攻擊模式class GameScene: SKScene {
    ...
    override func update(_ currentTime: TimeInterval) {
        ...
        for weather in self.weathers where weather.mode == .REBIRTH && weather.gridX == gridMapping.weatherHome.x && weather.gridY == gridMapping.weatherHome.y {
            weather.setMode(mode: .ATTACK)
        }
    }
}
主角可以反擊怪物了!怪物會變成水滴往出生點的位置移動,一但回到出生點,又會再次變回攻擊模式
目前怪物所有的樣貌和模式已經有了一個基本循環
明日會在遊戲中再加上一些特殊道具的設定,增添遊戲性