遠方有個一閃一閃的東西,吸引了山姆的目光。
「這顆水晶...特別的大顆!」山姆跑了過去。
碰觸到魔幻水晶的那一剎那,彷彿得到了全宇宙的力量。
這是攻守互換的一刻!山姆的反擊開始了!
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)
}
}
}
主角可以反擊怪物了!怪物會變成水滴往出生點的位置移動,一但回到出生點,又會再次變回攻擊模式
目前怪物所有的樣貌和模式已經有了一個基本循環
明日會在遊戲中再加上一些特殊道具的設定,增添遊戲性