iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Mobile Development

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

從零開始的8-bit迷宮探險【Level 13】主角總是孤獨的

「呀!呀!」一隻烏鴉飛了過去,因為視線不明,讓移動的黑影更加引人遐想。
在這諾大的森林裡,就只有山姆一個人,登山杖插進土裡的聲音清楚迴盪在空氣中。
山姆調整了一下背包,深深吸了一口氣,再次提起腳步前進。

今日目標

  • 新增角色 (父類別) 及主角 (子類別)
  • 新增一個主角在遊戲畫面中
  • 播放主角的動畫

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


新增角色類別

我們新增一個共用的角色類別,可以讓主角繼承,未來也能讓怪物繼承。
首先新增一個 swift 檔案,將程式碼區開,比較方便管理。

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

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

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

引入 SpriteKit

記得萬事先 import

  • GameCharacter.swift
import SpriteKit

角色種類

先建立一個列舉,加入 NONE 及主角山姆 SAM

  • GameCharacter.swift
enum Role: String {
    case NONE = "none"
    case SAM = "sam"
}

類別的內容

我們希望能記錄角色的一些屬性:

  • role:角色的種類。
  • node:角色的 SKSpriteNode,可以對其設定位置 (position)、定位點 (anchorPoint)、尺寸 (size)、層級 (zPosition) 等等。
  • gridWH:紀錄一格的寬度
  • gridX:記錄當前格子的位置 x
  • gridY:記錄當前格子的位置 y
  • startGridX:紀錄起始格子的位置 x
  • startGridY:紀錄起始格子的位置 y
  • imageName:紀錄角色起始圖片

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

  • gridWH:一格的寬度
  • startGridX:角色初始格子的位置 x
  • startGridY:角色初始格子的位置 y
  • imageName:角色圖片
  • zPosition:角色圖片的層級
  • role:角色種類

在建構子中,儲存帶入的參數值,並且新增一個 SKSpriteNode,將定位點 (anchorPoint)、尺寸 (size)、位置 (position)、層級 (zPosition) 都設定好。

  • GameCharacter.swift
class GameCharacter {
    var role: Role = .NONE
    var node: SKSpriteNode
    var gridWH: Int
    var gridX: Int
    var gridY: Int
    var startGridX: Int
    var startGridY: Int
    var imageName: String
    
    init(gridWH: Int, startGridX: Int, startGridY: Int, imageName: String, zPosition: CGFloat, role: Role) {
        self.role = role
        self.gridWH = gridWH
        self.gridX = startGridX
        self.gridY = startGridY
        self.startGridX = startGridX
        self.startGridY = startGridY
        self.imageName = imageName

        self.node = SKSpriteNode(imageNamed: imageName)
        self.node.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        self.node.size.width = CGFloat(gridWH)
        self.node.size.height = CGFloat(gridWH)
        self.node.position = CGPoint(x: gridWH * startGridX + (gridWH/2), y: -gridWH * startGridY - (gridWH/2))
        self.node.zPosition = zPosition
    }
}

新增 ZPosition 列舉

我們的遊戲畫面就像是個畫布層層堆疊,放在上面的物體都是有層級的,數字越高的在越上方
而 SpriteKit 會幫我們預設定為:0.0
先試想遊戲會出現幾種交疊在一起的物體:收集品、主角、壞天氣怪物、能隱身的樹,分別幫他們先定義好正確的層級。其中一種是隱藏 (-1),當設定為此種類型,則會有消失在畫面中的效果。
請先定義好 ZPosition

  • GameScene.swift
enum ZPosition {
    static let HIDE = -1
    static let COLLECTION = 1
    static let SAM = 2
    static let WEATHER = 3
    static let PURPLE_TREE = 4
}

新增主角類別

新增一個主角類別檔案:Sam.swift,繼承 GameCharacter 類別。

  • imageName 帶入主角的圖檔名稱 sam
    https://imgur.com/HzKZSJh.png

  • zPosition 帶入 ZPosition.SAM

  • role 帶入 .SAM (因為父類別已經有定義 role 的型態為 Role,所以可將 Role.SAM 省略為 .SAM)

  • Sam.swift
import SpriteKit

class Sam: GameCharacter {
    init(gridWH: Int, startGridX: Int, startGridY: Int) {
        super.init(gridWH: gridWH, startGridX: startGridX, startGridY: startGridY, imageName: "sam", zPosition: CGFloat(ZPosition.SAM), role: .SAM)
    }
}

將主角加到遊戲畫面中

考慮到未來會有不同種類的角色,分別會有不同的起始點 (這邊提的起始點是指位於迷宮格子中的位置),可以先新增一個結構 (struct),定義不同種類位置的 (x, y) 常數值,方便未來管理。請先新增一個 samStart 的 x 及 y 值。

  • GameScene.swift
struct gridMapping {
    struct samStart {
        static let x = 1
        static let y = 1
    }
}

在 didMove 中,創建一個 Sam 類別的實體,並將起始位置帶入。
接著將實體的 node 添加到 mapNode 中。

  • GameScene.swift
class GameScene: SKScene {
    ...
    var sam: Sam?
    
    override func didMove(to view: SKView) {
        ...
        self.sam = Sam(gridWH: self.gridWH, startGridX: gridMapping.samStart.x, startGridY: gridMapping.samStart.y)
        self.mapNode!.addChild(self.sam!.node)
    }
}

執行成果

主角出現在畫面中了

https://imgur.com/pl4ngnz.png


主角的動畫

如果只有一張貼圖,遊戲會比較不生動,加入動畫可以提升精緻度。
我們試著製作兩張主角手部舉起登山杖的連續圖,面朝下的方向,分別取名為:

  • sam_down_1
  • sam_down_2

https://imgur.com/HzKZSJh.pnghttps://imgur.com/CUkapCL.png

回到角色類別,找到設定圖片的地方,將原本建立 SKSpriteNode 的地方做調整。

  • 使用 SKTexture 新增兩張序列圖
  • 透過 SKAction.animate 建立圖片序列動畫,timePerFrame 代表每張圖片的持續時間(秒)
  • 由於這個動畫只會執行一次,因此透過 SKAction.repeatForever,帶入原本的圖片序列動畫,讓它重複播放
  • 接著建立 SKSpriteNode,並且透過 .run,讓它播放動畫
  • GameCharacter.swift
// self.node = SKSpriteNode(imageNamed: imageName)

let sequence1 = SKTexture(imageNamed: imageName + "_1")
let sequence2 = SKTexture(imageNamed: imageName + "_2")
let ani = SKAction.animate(with: [sequence1, sequence2], timePerFrame: 0.4)
let aniRepeat = SKAction.repeatForever(ani)
self.node = SKSpriteNode(texture: sequence1)
self.node.run(aniRepeat, withKey: "sequence")

將圖檔名稱改為 sam_down

  • Sam.swift
super.init(gridWH: gridWH, startGridX: startGridX, startGridY: startGridY, imageName: "sam_down", zPosition: CGFloat(ZPosition.SAM), role: .SAM)

執行後就可以看到主角有動畫了!

微調圖片大小

我們發現主角在地圖中看起來似乎有點小,想要將角色圖片調整大一點。
因為角色的 anchorPoint 是定在 (0.5, 0.5),因此我們可以直接將 node 的 size 調大,不會影響到定位。
將寬高各加上 13 微調

  • GameCharacter.swift
self.node.size.width = CGFloat(gridWH + 13)
self.node.size.height = CGFloat(gridWH + 13)

執行結果

主角在畫面上能顯示序列動畫囉!

https://imgur.com/CyaguQ6.gif

明日來控制主角移動吧!


參考來源:
SpriteKit zPosition
SpriteKit SKAction animate


上一篇
從零開始的8-bit迷宮探險【Level 12】把迷宮塗上喜歡的顏色
下一篇
從零開始的8-bit迷宮探險【Level 14】讓主角奔跑吧!Running Sam
系列文
從零開始的8-bit迷宮探險!Swift SpriteKit 遊戲開發實戰30

尚未有邦友留言

立即登入留言