Episode 32 — 模擬Google小遊戲:圈圈叉叉、數字輪盤

Shien
彼得潘的 Swift iOS / Flutter App 開發教室
12 min readJan 6, 2022

現在才發現 Google 除了搜尋功能還有自製的小遊戲呢
這次要來模仿其中兩個小遊戲,圈圈叉叉跟輪盤
看似簡單,但做起來又是另外一回事
來看看是怎麼回事吧

遊戲一:圈圈叉叉

我做的是雙人遊戲,google版本是單人版。我利用前一張的骰子遊戲另外建立一個 View Controller 連到舊的 Navigation Controller,就可以提供更多遊戲選擇。

玩家可以利用上面兩個Button選擇O或X,按一下圈就會先從圈開始,按叉就從叉開始。

玩家選擇完格子就會自動換到下一家,圈按完自動變叉,叉選完自動變圈。且會顯示輪到的是圈還是叉字樣。

玩家贏的時候會顯示誰是贏家即為贏家那方加分數,平手的話則都不加

玩家可以隨時按下面的one more round 按鈕來新的一局

玩家可以按最下面的 restart按鈕來重來整個遊戲

遊戲二:數字輪盤

一樣連結前一章節的骰子遊戲,將輪盤遊戲的 View Controller 連到舊的Navigation Controller 提供第四個遊戲。

玩家可以利用上面的 segmented control 選擇輪盤的格子數量

玩家可以按下面的 spin 按鈕來轉動輪盤

繪製一個圓餅圖

我會選擇輪盤是因為剛好可以複習之前的圓餅圖,果然真的需要複習因為忘得差不多了。以下有製作圓形圖表的文章。

繪製一個圓不難,但繪製一個隨著數值改變的圓有點挑戰性
我研究出來的寫法如下

//宣告一個繪製圓餅圖的方法,導入一個被切成多少 part 的參數
func makeAWheel(parts: Int){

//儲存圓形一度
let perDegree = CGFloat.pi / 180

//圓的半徑
let radius:CGFloat = 200

//設定輪盤中心
let wheelViewCenter = CGPoint(x: wheelView.bounds.width/2, y: wheelView.bounds.height/2)

//開始角度為 270 度
var startAngle = perDegree * 270

//結束角度 0 度
var endAngle:CGFloat = perDegree * 0

//當輪盤被切成雙數,顏色為綠跟紅
let colorForEvenNum = [UIColor.green, UIColor.red]

//當輪盤被切成單數,顏色為綠、紅跟藍
let colorForOddNum = [UIColor.green, UIColor.red, UIColor.cyan]

//選擇顏色時會用到的指數
var index = 0

//從第一 part 到最後一 part 每次要做的事
for part in 1...parts {

// 結束角度為每一次開始角度加上 360 除以指定等份
endAngle = startAngle + (perDegree * (360/CGFloat(parts)))

//繪製圓圈
let circle = UIBezierPath()

circle.move(to: wheelViewCenter)

circle.addArc(withCenter: wheelViewCenter, radius: 200, startAngle: startAngle , endAngle: endAngle, clockwise: true)

let circleLayer = CAShapeLayer()
circleLayer.path = circle.cgPath
circleLayer.strokeColor = UIColor.black.cgColor

//當圓餅被切成雙數等份,顏色要用雙數的顏色。單數等份則為單數顏色
if parts % 2 == 0 {

circleLayer.fillColor = colorForEvenNum[index].cgColor

index = (index + 1) % colorForEvenNum.count

} else {

//當圓餅被切成 7 份且輪到最後一份時,顏色指數要多加1才不會跟第一份顏色重復
if parts == 7 && part == 7 {

index = (index + 2) % colorForOddNum.count

} else {

index = (index + 1) % colorForOddNum.count
}

circleLayer.fillColor = colorForOddNum[index].cgColor

}


circleLayer.lineWidth = 5

wheelView.layer.addSublayer(circleLayer)

//製作每一等份上的數字 label
let numLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))

numLabel.text = "\(part)"

numLabel.textColor = .black

numLabel.font = UIFont.systemFont(ofSize: 20)

numLabel.sizeToFit()

//改變每一個數字的角度讓他們朝向圓心
numLabel.transform = CGAffineTransform(rotationAngle: (startAngle-270*perDegree) + (360/CGFloat(parts)/2*perDegree) )

//繪製數字 label 的路徑
let labelPath = UIBezierPath()

labelPath.addArc(withCenter: wheelViewCenter, radius: radius-50, startAngle: startAngle, endAngle: endAngle-(perDegree*360/CGFloat(parts)/2), clockwise: true)

let labelLayer = CAShapeLayer()

labelLayer.path = labelPath.cgPath

labelLayer.fillColor = UIColor.clear.cgColor

numLabel.center = labelPath.currentPoint

wheelView.addSubview(numLabel)
wheelView.layer.addSublayer(labelLayer)

//每一次繪製完一份前,要將結束角度給開始角度
startAngle = endAngle
}

}

轉圈動畫

其實呢,我也是不知道怎麼轉圈,但幸好有學長姐在,我才可以拷貝答案。

但當我拷貝後發現一件不妙的事情,他們是用圖片下去旋轉,而我是用UIBezier繪製的。所以我把繪製的 layer 加到另外新增的 view上,這樣就可以旋轉同個 view 就好,也能實現多種圓餅圖的旋轉。

wheelView.layer.addSublayer(circleLayer)//圓形的 path
.
.
.
.
.
wheelView.addSubview(numLabel) //數字 label

另外,我是把旋轉的功能存在另外一個 class 裡(wheelView)。而真正顯示輪盤的地方在 viewController4 裡。

在 wheelView 裡面的 class 長這樣

import UIKitclass WheelView: UIView {var currentValue: Double = 0*Double.pi

func rotateGradually() {

//宣告一個轉圈的方法
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")

//開始轉動
CATransaction.begin()

//從目前的角度(值)開始轉
rotateAnimation.fromValue = currentValue

// 產生1~2pi隨機的Double數字,也就是1~360度
let randomDouble = Double.random(in: Double.pi/180...Double.pi*2)

//開始到結束,總共轉了10圈加上randomDouble度,從到目前角度
currentValue += randomDouble + (10*Double.pi*2)

//轉到假度為更新的目前角度
rotateAnimation.toValue = currentValue

//動畫結束後仍保在結束狀態,讓轉盤不會在動畫結束時回到最初狀態。以便再次轉動
rotateAnimation.isRemovedOnCompletion = false


rotateAnimation.fillMode = .forwards

rotateAnimation.duration = 5 //動畫持續時間

rotateAnimation.repeatCount = 1 // 重複幾次

//用cubic Bezier curve決定動畫速率曲線
rotateAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0, 0.9, 0.4, 1.00)

self.layer.add(rotateAnimation, forKey: nil)
CATransaction.commit()
}
}

我在我的 viewController4 新增被轉動的 view。這個 view要繼承剛剛另外創造的 WheelView 的 class

@IBOutlet weak var wheelView: WheelView!

建立一個 Button 讓輪盤轉動,在 viewController4 新增一個按鈕 action,來讓view 執行 WheelView class 裡面的旋轉功能。

@IBAction func chooseNum(_ sender: Any) {wheelView.rotateGradually()}

學長姐還有用到 Alert 來顯示轉到的結果為多少,但我把這個部份省略掉只做旋轉的部分而已。紅色框框為省略部分。

影片回顧

--

--