昨天我們介紹了使用 SpriteKit 來製作打磚塊遊戲,利用了物理引擎的特性來創造逼真的遊戲體驗。不過,不是所有遊戲都需要如此複雜的物理引擎,許多遊戲只需使用 UIKit 的動畫特效就能提供良好的遊戲效果。今天,我們將探討如何利用 UIKit 製作一個簡單但有趣的快艇骰子 Yahtzee 遊戲。
Yahtzee 快艇骰子是一個將骰子遊戲與撲克牌常見的計分規則相結合的遊戲,讓運氣和策略同時挑戰玩家的技巧。這篇文章將帶領你構建一個基本的 Yahtzee 遊戲應用,並介紹如何運用 Swift 的集合 (Set) 與陣列轉換 (map) 來簡化計算邏輯。
這次的介面設計比較簡潔,使用了 Storyboard 將基本的 UI 元素排布好,以藍白色為主調,畫面風格稍顯陽春,但我們專注於遊戲功能的實現。整個遊戲包含骰子顯示區、計分板以及簡單的操作按鍵。
截圖:展示了遊戲開始後的畫面,骰子與計分板的佈局。
Yahtzee 的計分規則分為上半部和下半部。讓我們簡單回顧一下這些規則。
在上半部,玩家需要計算五個骰子中相同數字的總和。例如,如果擲出了 1、2、3、3、6,且玩家選擇計算「3」,則得分為 6(兩個 3)。
加分條件:如果 1-6 的總分大於等於 63 分,玩家可獲得額外 35 分。
這部分則根據撲克牌的組合計分:
Three of a Kind:三個骰子相同,計算所有骰子的總和。
Four of a Kind:四個骰子相同,計算所有骰子的總和。
Full House:三個相同 + 兩個相同,固定得 25 分。
Small Straight:四個連續的數字,固定得 30 分。
Large Straight:五個連續的數字,固定得 40 分。
Yahtzee:五個骰子相同,得 50 分。
Chance:任意組合,計算所有骰子的總和。
目前這個版本只設計了單人玩法,未來可以加入挑戰分數的機制,即玩家需超過某個預設分數才算勝利。另外,我們還可以進一步創造一個「快艇」概念:分數即為燃料,得分越高,快艇的燃料越多,最終能跑得更遠。
這次的實作重點是運用 Swift 的集合 (Set) 與陣列轉換 (map) 來簡化邏輯運算。
class Die {
var value: Int
var isHeld: Bool
init() {
value = Int.random(in: 1...6)
isHeld = false
}
func roll() {
if !isHeld {
value = Int.random(in: 1...6)
}
}
}
使用 map 方法將骰子的數值轉換為一個陣列,並用 Set 來過濾重複數字:
var dice: [Die]
let uniqueValues = Set(dice.map { $0.value })
當我們調用 map 方法時,我們正在對陣列中的每個元素應用一個轉換,並返回一個包含轉換後結果的新陣列。在這種情況下,dice 是一個 Die 對象的陣列,$0.value 是一個閉包,它返回 Die 對象的 value 屬性的值。所以 dice.map { $0.value } 這行代碼將返回一個包含了所有 Die 對象的 value 屬性值的陣列。
接著,我們將這個陣列傳遞給 Set 初始化器,它將這個陣列中的所有元素作為其內容來創建一個新的 Set 實例。Set 是一種集合類型,它包含一組唯一的元素,這意味著在 uniqueValues 中將不會包含重複的元素,每個元素只會出現一次。
private func calculateThreeOfAKindScore() -> Int {
let diceValues = dice.map { $0.value }
let counts = Set(diceValues).map { value in diceValues.filter { $0 == value }.count }
if counts.contains(where: { $0 >= 3 }) {
return diceValues.reduce(0, +)
} else {
return 0
}
}
private func calculateFourOfAKindScore() -> Int {
let diceValues = dice.map { $0.value }
let counts = Set(diceValues).map { value in diceValues.filter { $0 == value }.count }
if counts.contains(where: { $0 >= 4 }) {
return diceValues.reduce(0, +)
} else {
return 0
}
}
private func calculateFullHouseScore() -> Int {
let diceValues = dice.map { $0.value }
let counts = Set(diceValues).map { value in diceValues.filter { $0 == value }.count }
if counts.contains(2) && counts.contains(3) {
return 25
} else {
return 0
}
}
首先,我們透過一個簡單的旋轉動畫來模擬搖動骰子的效果。當使用者點擊「搖骰」按鈕時,所有未被選中的骰子將進行短暫的旋轉,讓骰子看起來像是在搖動。這段動畫的實現關鍵是使用 UIView.animate 函數來執行動畫,並且將旋轉角度設為 π / 1.1。
// 旋轉骰子動畫
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut], animations: {
self.emptyNotSelectedDiceImage()
for button in self.dieButtons {
if !button.isSelected {
button.transform = CGAffineTransform(rotationAngle: .pi / 1.1)
}
}
當使用者點擊「計分」按鈕時,我們會將所有骰子按鈕移動到按鈕的中心位置,並進行縮放,模擬骰子消失的效果。隨後,骰子會回到原本位置,並恢復大小與透明度。這段動畫提升了整體的互動體驗,讓遊戲變得更加動感。
@IBAction func scoreButtonPressed(_ sender: UIButton) {
let targetCenter = sender.superview?.convert(sender.center, to: nil) ?? CGPoint.zero
UIView.animate(withDuration: 1.0, animations: {
for button in self.dieButtons {
button.center = targetCenter
button.transform = CGAffineTransform(scaleX: 0.2, y: 0.2)
button.alpha = 0
}
})
透過這些 Swift 內建的強大工具,程式碼不僅更簡潔,也變得更具可擴展性和可維護性。
透過 Yahtzee 快艇骰子遊戲 App,使用 UIKit 的動畫效果如旋轉、移動及縮放等,就能使遊戲的視覺表現更加生動,這對於未來開發更具吸引力的遊戲非常有幫助。此外,透過集合 (Set) 和陣列轉換 (map),我們學會了如何以簡潔且高效的方式處理資料,這在處理遊戲邏輯如計分等方面,尤其能顯著提升程式效率和可讀性。這些技巧也可以應用於更多類似的遊戲邏輯設計中,幫助你在未來的開發中更快速地解決問題。