「萬事俱備,只欠東風了」Rain 對著 Storm 說。
「居然有人敢入侵我們的家園!」Lightning 說到激動處還不時冒出電流。
「我看這傢伙走路有點慢,我們快去把他趕走吧!」Snow 回憶著撞見山姆時的情境。
午後的黑森林,要變天了。
PS. 這裡是開發 iOS 手機遊戲的系列文,如果還沒看過之前
劇情文章的朋友,歡迎先點這邊回顧唷!
先複習一下,目前我們的主角-山姆,已經可以在迷宮中移動了,想更詳細了解,可以點擊:【Level 14】讓主角奔跑吧!Running Sam
我們已經在共用的角色類別 GameCharacter 寫好可以幫助製作移動跟動畫的有:
Direction
getValidDirection()
isWall(dir: Direction)
Move
playAnimation(imageName: String, num: Int, repeatAni: Bool = true)
回到我們的壞天氣怪物類別 (Weather),讓它遵循 Move protocol,並且實作方法
Move,不過移動方向由方法內決定,可以忽略帶進來的 direction 參數getValidDirection() 取得當前可以前進的方向 validDirections
randomElement() 取得任一個隨機的可行方向,因得到的結果是 Optional 型別,所以這邊運用了 guard 的功能,當沒有取得時直接 returnself.direction
isMoving 是否正在移動的值SKAction.moveBy 播放移動動畫,x 及 y 帶入移動的向量,duration 帶入移動間隔秒數。最後透過 .run 執行動畫,當動畫完成時,執行 endMove 方法startMove 方法,會讓移動動畫持續進行class Weather: GameCharacter, Move {
    ...
    func startMove(direction : Direction) {
        // 取得可行方向
        let validDirections: [Direction] = self.getValidDirection()
        
        // 隨機取得一個可行方向
        guard let randomDirection = validDirections.randomElement() else {
            return
        }
        self.direction = randomDirection
        
        // 是否正在移動中
        self.isMoving = self.direction != Direction.NONE
        
        // 播放移動動畫
        let animation = SKAction.moveBy(x: self.moveX[self.direction]!, y: self.moveY[self.direction]!, duration: self.moveInterval)
        self.node.run(animation, completion: endMove)
        
        // 設定格子
        self.setGridXY(direction: self.direction)
    }
    func endMove() {
        self.startMove(direction: self.direction)
    }
}
在遊戲場景的 didMove,建立好怪物之後,讓每一個怪物都呼叫移動方法 startMove
就可以看到怪物在地圖內自動移動了
class GameScene: SKScene {
    ...
    override func didMove(to view: SKView) {
        ...
        for weather in self.weathers {
            weather.startMove(direction: .NONE)
        }
    }
}

目前怪物可以隨機移動了,可是看起來有一直來回走動的感覺
為了修正這個問題,我們希望能讓怪物一直往前走,或者是轉個彎再走,避開回頭走的方向
所以在取得方向的時候,稍微修正一下:
newDirection,先暫時將隨機取得的方向存進變數中newDirection 相反self.direction
class Weather: GameCharacter, Move {
    ...
    func startMove(direction : Direction) {
        var newDirection: Direction = .NONE
        
        // 取得可行方向
        let validDirections: [Direction] = self.getValidDirection()
        
        // 隨機取得一個可行方向
        guard let randomDirection = validDirections.randomElement() else {
            return
        }
        newDirection = randomDirection
        
        // 判斷是否回頭走
        let isBack = self.direction == .LEFT && newDirection == .RIGHT ||
            self.direction == .RIGHT && newDirection == .LEFT ||
            self.direction == .UP && newDirection == .DOWN ||
            self.direction == .DOWN && newDirection == .UP
            
        // 不是回頭路 或 原方向撞牆,才更新方向
        if !isBack || !validDirections.contains(self.direction) {
            self.direction = newDirection
        }
        
        ...
    }
}

最後,讓怪物也能透過左右的通道穿梭地圖吧!
這邊的方式跟製作主角移動時一樣
在播放移動動畫之前,先判斷走到特地位置時,就將怪物瞬間移動到地圖另一邊
class Weather: GameCharacter, Move {
    ...
    func startMove(direction : Direction) {
        ...
        // 左右穿梭
        if ((gridX == gridMapping.leftPass1.x && gridY == gridMapping.leftPass1.y && direction == .LEFT) || (gridX == gridMapping.leftPass2.x && gridY == gridMapping.leftPass2.y && direction == .LEFT) || (gridX == gridMapping.rightPass1.x && gridY == gridMapping.rightPass1.y && direction == .RIGHT)) {
            self.gridX = direction == .LEFT ? gridMapping.rightPass1.x + 1 : gridMapping.leftPass1.x - 1
            self.gridY = direction == .LEFT ? gridMapping.rightPass1.y : [gridMapping.leftPass1.y, gridMapping.leftPass2.y].randomElement()!
            self.node.position = CGPoint(x: (gridX * gridWH) + (gridWH/2), y: -gridY * gridWH - (gridWH/2))
        }
        
        // 播放移動動畫
        let animation = SKAction.moveBy(x: self.moveX[self.direction]!, y: self.moveY[self.direction]!, duration: self.moveInterval)
        self.node.run(animation, completion: endMove)
        
        ...
    }
}

目前的怪物只有正面一個方向的序列圖,我們將他優化,把各個方向都做出來吧!








因為怪物有不同種類的移動模式,我們預計在不同模式會顯示不同的樣貌
因此在加各方向的圖片時,我們事先規劃,在 Mode 加上 getImage 來取得對應移動模式之下,對應怪物的圖片名稱
目前我們先以預設 .ATTACK 模式來製作,其他模式未來會再說明
enum Mode {
    case ATTACK // 追擊主角
    case ESCAPE // 逃離主角
    case PLAY // 聚集到湖邊玩耍
    case REBIRTH // 回到出生點重生
    
    func getImage(role: Role) -> String {
        switch self {
        case .ATTACK, .PLAY:
            switch role {
            case .RAIN:
                return "rain"
            case .STORM:
                return "storm"
            case .LIGHTNING:
                return "lightning"
            case .SNOW:
                return "snow"
            default:
                return ""
            }
        case .ESCAPE:
            return "cloud"
        case .REBIRTH:
            return "water"
        }
    }
}
祕技:這邊比較特別的是運用了在列舉 (enum) 裡使用方法 (function) 的技巧,可以結合 switch case,分別讓各個 case 回傳不同的結果,只需要加上
switch self,就可以方便地對自身的 case 做條件判斷並回傳對應的值。
最後,來寫播放動畫的程式碼吧!
startMove 方法後,就先將當下的移動方向暫存到 tempDirection
playAnimation
imageName 用其屬性來組成:
self.mode.getImage(role: self.role),取得怪物種類名稱self.direction.rawValue,取得當前方向rain_left
num:2張序列圖class Weather: GameCharacter, Move {
    ...
    func startMove(direction : Direction) {
        let tempDirection: Direction = self.direction
        ...
        self.isMoving = self.direction != Direction.NONE
        
        // 改變怪物圖片
        if tempDirection != self.direction {
            self.playAnimation(imageName: "\(self.mode.getImage(role: self.role))_\(self.direction.rawValue)", num: 2)
        }
        
        // 左右穿梭
        ...
    }
}

目前怪物已經可以自動偵測可以前進的方向,並且隨機移動囉!
但這樣還不夠聰明~
明天將會介紹如何讓怪物朝著主角的位置移動!