iT邦幫忙

2021 iThome 鐵人賽

DAY 15
3
Mobile Development

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

從零開始的8-bit迷宮探險【Level 15】迷人的反派角色-製作怪物

正當山姆思考結界問題的同時,啪嗒!啪嗒!雨落了下來。
「下雨了!」山姆趕緊找尋遮蔽物,跑到了一棵大樹下。
「呼,好險。」山姆喘了一口氣,還好背包沒有濕掉。
山姆蹲下繫緊因奔跑而鬆脫的鞋帶,抬起頭時,出現了一雙眼睛緊盯著。
四目交對之下,山姆快速掃遍腦中的資料庫...
「那是...雪人?這個季節裡居然有雪人!?」

今日目標

  • 新增怪物類別,繼承角色類別 (GameCharacter)
  • 怪物的動畫
  • 新增四個怪物在遊戲畫面中

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


怪物角色設計

依照我們的遊戲設計規劃,怪物共有四種:

  • 壞天氣1號:下雨 (Rain)
  • 壞天氣2號:暴風雨 (Storm)
  • 壞天氣3號:閃電 (Lightning)
  • 壞天氣4號:雪人 (Snow)

以下先設計好怪物的樣子,每種怪物都先分別設計兩張動畫序列圖,我們先製作一種方向 (往下走的正面),並將圖片拖移進專案中

  • 下雨 (Rain):

    • 檔名 rain_down_1、rain_down_2
      https://imgur.com/NpvXjs8.pnghttps://imgur.com/B4k4twc.png
  • 暴風雨 (Storm):

    • 檔名 storm_down_1、storm_down_2
      https://imgur.com/q7LQt5A.pnghttps://imgur.com/AofEeDM.png
  • 閃電 (Lightning):

    • 檔名 lightning_down_1、lightning_down_2
      https://imgur.com/G9GOBYt.pnghttps://imgur.com/9hBissO.png
  • 雪人 (Snow):

    • 檔名 snow_down_1、snow_down_2
      https://imgur.com/4Ljooro.pnghttps://imgur.com/nc6HXBS.png

怪物的設定

先前已經定義過主角的一些設定值,我們先將怪物的設定值也加進專案中

怪物種類

在之前主角篇新增的 Role 列舉,再多加入四種怪物名稱 RAINSTORMLIGHTNINGSNOW

  • GameCharacter.swift
enum Role: String {
    case NONE = "none"
    case SAM = "sam"
    case RAIN = "rain"
    case STORM = "storm"
    case LIGHTNING = "lightning"
    case SNOW = "snow"
}

怪物起始位置

將怪物在迷宮中的起始位置定義在之前宣告的 gridMapping 結構 (struct) 中,方便一起管理

  • GameScene.swift
struct gridMapping {
    ...
    struct rainStart {
        static let x = 7
        static let y = 8
    }
    struct stormStart {
        static let x = 8
        static let y = 10
    }
    struct lightningStart {
        static let x = 6
        static let y = 10
    }
    struct snowStart {
        static let x = 9
        static let y = 8
    }
}

新增怪物類別

接著,請新增一個 swift 檔案,命名為 Weather.swift

  • 點選新增檔案
    https://imgur.com/DyRgPfA.png

  • 選擇 Swift File -> Next
    https://imgur.com/IJukGWI.png

  • 將檔案命名為 Weather,點擊 Create 按鈕,完成 .swift 檔的新增

引入 SpriteKit

請先 import SpriteKit

  • Weather.swift
import SpriteKit

怪物的移動模式種類

在這邊我們先定義好怪物將會進行的移動模式種類,將會在未來的章節實做移動的方式

  • ATTACK:追擊主角
  • ESCAPE:逃離主角
  • PLAY:聚集到湖邊玩耍
  • REBIRTH:回到出生點重生
  • Weather.swift
enum Mode {
    case ATTACK
    case ESCAPE
    case PLAY
    case REBIRTH
}

類別的內容

將怪物類別 (Weather) 繼承角色類別 (GameCharacter)
接著加上怪物專有的一些屬性:

  • mode:怪物的移動模式,預設給 .ATTACK
  • isTrace:紀錄目前正在追蹤目標或是遠離目標,預設給 true
  • targetGridX:目標物的格子點 x
  • targetGridY:目標物的格子點 y
  • sam:主角,追擊時需要主角的位置資料

接著在建構子 (init) 寫上初始化時需帶入的參數:

  • gridWH:一格的寬度
  • startGridX:角色初始格子的位置 x
  • startGridY:角色初始格子的位置 y
  • role:角色種類
  • sam:主角

在 super.init 的時候帶入的參數:

  • imageName 帶入 "\(role.rawValue)_down",對應到前面加入的圖片檔名,例如 rain_down。在 GameCharacter 類別中,先前已經在 init 寫好播放動畫序列圖的程式碼,帶入圖檔名就可以產生序列動畫了
  • zPosition 帶入 ZPosition.WEATHER,在先前的章節已經加好的怪物層級

並且將 sam 主角的資料存起來

  • Weather.swift
class Weather: GameCharacter {
    var mode: Mode = .ATTACK
    var isTrace: Bool = true
    var targetGridX = 0
    var targetGridY = 0
    var sam: Sam?
    
    init(gridWH: Int, startGridX: Int, startGridY: Int, role: Role, sam: Sam) {
        super.init(gridWH: gridWH, startGridX: startGridX, startGridY: startGridY, imageName: "\(role.rawValue)_down", zPosition: CGFloat(ZPosition.WEATHER), role: role)
        self.sam = sam
    }
}

新增怪物到遊戲中

回到 GameScene,我們新增怪物的實體,將怪物新增到遊戲中

  • 在這邊先宣告陣列 weathers,準備儲存所有的怪物
  • didMove 裡分別新增四個怪物:rainstormlightningsnow,並將它們的 node 加到 mapNode 裡面,以及將實體加進 weathers 陣列中
  • GameScene.swift
class GameScene: SKScene {
    ...
    var weathers: [Weather] = []
    
    override func didMove(to view: SKView) {
        ...
        let rain = Weather(gridWH: self.gridWH, startGridX: gridMapping.rainStart.x, startGridY: gridMapping.rainStart.y, role: .RAIN, sam: self.sam!)
        self.mapNode!.addChild(rain.node)
        self.weathers.append(rain)
        
        let storm = Weather(gridWH: self.gridWH, startGridX: gridMapping.snowStart.x, startGridY: gridMapping.snowStart.y, role: .SNOW, sam: self.sam!)
        self.mapNode!.addChild(storm.node)
        self.weathers.append(storm)

        let lightning = Weather(gridWH: self.gridWH, startGridX: gridMapping.lightningStart.x, startGridY: gridMapping.lightningStart.y, role: .LIGHTNING, sam: self.sam!)
        self.mapNode!.addChild(lightning.node)
        self.weathers.append(lightning)

        let snow = Weather(gridWH: self.gridWH, startGridX: gridMapping.stormStart.x, startGridY: gridMapping.stormStart.y, role: .STORM, sam: self.sam!)
        self.mapNode!.addChild(snow.node)
        self.weathers.append(snow)
    }
}

執行成果

來看一下執行的成果吧!
https://imgur.com/uudL2ZW.gif


今日小結

目前我們有主角 (Sam) 及怪物 (Weather) 兩種類別了,同樣都繼承角色 (GameCharacter) 父類別,而透過繼承類別的好處是省略了一些重複的程式碼,像是初始化的地方,已經可以不用再多寫一次播放序列動畫的程式碼,並且也能依照特殊需求,再多加入特別的參數,使我們能更快速建立不同種類的角色。
聰明的各位應該也猜到了,接下來一樣可以遵循前面建立好的移動協定 Move,明日就來帶著大家一起實作讓怪物動起來吧!


上一篇
從零開始的8-bit迷宮探險【Level 14】讓主角奔跑吧!Running Sam
下一篇
從零開始的8-bit迷宮探險【Level 16】丞相,起風了!遠方飄來烏雲怪物了
系列文
從零開始的8-bit迷宮探險!Swift SpriteKit 遊戲開發實戰30

尚未有邦友留言

立即登入留言