寫一個猜燈謎的小遊戲
剛要練習這個主題,立馬想到知識王LIVE,記得以前很愛跟朋友玩知識王LIVE,但我們不是一起跟人PK,是一起回答同一個問題去打敵人(☝ ՞ਊ ՞)☝嘿嘿,但又看到他出了另一個遊戲APP,表情符號王,立馬下載,原本要做作業,結果自己玩了1個小時…超好玩
所以!決定自己來做看看~Let’s Go!
學習目標
- for 迴圈
- if else
- array
- 亂數
- 自訂資料型別
- MVC分類檔案
- 專案資料
- computed property
- property observer
- 頁面之間傳遞資料
最終成果
遊戲參考資料:
📍MVC分類檔案:
在這次專案中我將資料分為Models及View Controllers進行管理。
- Models: 我就會放自己定義的資料型別
- View Controllers: 我就會放每個畫面的程式碼
📍創建型別:
這個遊戲需要很多題目,於是在models裡建立一個Question的型別,之後好用來生成一個題庫的陣列。在這個類別中需要三個參數分別為:題目,答案,魚目混珠的文字。
在這裡面我希望生成10個隨機的中文字,因此我需要先用一個generateRandomChineseCharacters的方法隨機創建10個文字
func generateRandomChineseCharacters(count: Int) -> [String] {
var result: [String] = []
for _ in 0..<count {
// 使用 arc4random_uniform 函數生成一個介於 0x3400 和 0x4E00 - 1 之間的隨機數。這些數值範圍包含了中文字符的 Unicode 範圍。
let randomUnicodeValue = arc4random_uniform(UInt32(0x4E00 - 0x3400)) + UInt32(0x3400)
if let scalar = Unicode.Scalar(randomUnicodeValue) {
let character = Character(scalar)
result.append(String(character))
}
}
return result
}
創建完10個隨機文字後,需要將真正的答案加入隨機文字陣列中,但只加在後面當然不行,因此需要使用shuffle()來讓陣列裡面的元素隨機排列。
func getRandomChineseCharacters(anser: [String]) -> [String] {
// 加入真正的答案,兩個變數的類型都為[String]因此可以直接用加號
var allCharacters = generateRandomChineseCharacters(count: 10) + anser
// 陣列裡面的元素隨機排列
allCharacters.shuffle()
return allCharacters
}
在GameViewController使用:
var gameDatas: [Question] = [
Question(topic: ["🐯", "🐯", "🐯", "🐯"], ans: ["虎", "頭", "虎", "腦"]),
Question(topic: ["👣", "📈", "💨", "🤷♂️"], ans: ["趾", "高", "氣", "揚"]),
Question(topic: ["😈", "💬", "💔", "👤"], ans: ["惡", "語", "傷", "人"]),
Question(topic: ["1️⃣", "🎵", "🤯", "👤"], ans: ["一", "鳴", "驚", "人"]),
// ...省略其他
]
📍關卡更新:
在初始時就會生成第一關所以當一開始所有gameDatas都準備好的時候,每次都會進到updateLevel()拿新的一關,並且移除掉gameDatas已經玩過的題目,直到gameDatas沒有資料,遊戲結束。
func updateLevel() {
// 判斷還有沒有題目
if !gameDatas.isEmpty {
// 用過的題目就移除
levelData = gameDatas.removeFirst()
if let question = levelData {
// 更新題目標籤
for (index, label) in topicLabel.enumerated() {
if index < question.topic.count {
label.text = question.topic[index]
}
}
// 更新按鈕文字
for (index, button) in wordsButton.enumerated() {
if index < question.others.count {
button.setTitle(question.others[index], for: .normal)
}
}
}
} else {
gameOverView.isHidden = false
}
}
📍點擊文字按鈕:
因為我要做動畫,所以必須記錄按鈕要飛到哪。可以打開stoybroad查看答案方框的位置在哪。
點擊第一次位置是1點;擊點第二次位置是2以此類推,找出每個位置,寫switch做判斷。
let targetY: CGFloat = 425
var targetX: CGFloat
clickAnsCount += 1
switch clickAnsCount {
case 1:
targetX = 94
case 2:
targetX = 155
case 3:
targetX = 215
case 4:
targetX = 276
default:
targetX = 94
}
設定好每次需要移動到的位置後,可以用animate來設定需要移動到的CGPoint,並且移動後的按鈕都不能再點擊。
// 移動按鈕位置並禁用
UIView.animate(withDuration: 0.5) {
sender.frame.origin = CGPoint(x: targetX, y: targetY)
sender.isEnabled = false
sender.backgroundColor = .white
sender.layer.cornerRadius = 5
sender.setTitleColor(.darkGray, for: .disabled)
}
另外因為動畫會需要時間跑所以如果沒有延遲更新下一關的話,第四顆鈕就會跑不出來,直接到下一關,我們必須設定類似setTimeout的佇列才能避免這個狀況。
let delayInSeconds: Double = 1.0 // 延遲 1 秒
// 延遲一段時間後執行的程式碼
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { [self] in
// 在這裡放置延遲執行的程式碼
if self.isGameOver {
self.gameOverView.isHidden = false
} else {
self.initUI()
self.updateLevel()
}
}
應該有發現在下一關的時候需要讓按鈕都乖乖回去,所以必須在viewDidLoad()的時候就記下所有按鈕的位置,在更新關卡的時候才能讓按鈕乖乖回去。
override func viewDidLoad() {
super.viewDidLoad()
setOriginalButtonPosition()
}
// 設定按鈕的初始位置
func setOriginalButtonPosition() {
for word in wordsButton {
originalButtonPosition.append(word.frame.origin)
}
}
// 初始化界面元素
func initUI() {
for (index, word) in wordsButton.enumerated() {
// 更新關卡讓按鈕各自歸位,並且恢復可以點擊
UIView.animate(withDuration: 0.5) {
word.frame.origin = self.originalButtonPosition[index]
word.isEnabled = true
}
}
}
📍computed property:
// 判斷遊戲是否結束的計算屬性
var isGameOver: Bool {
return heartsImageView.isEmpty
}
📍property observer:
// 紀錄玩家獲勝次數,屬性觀察器會自動更新界面
var winAcount = 0 {
didSet {
winAcountLabel.text = String(winAcount)
}
}
📍頁面間的溝通:
需要使用到@IBSegueAction,產生這個方法的方式跟產生@IBAction一樣,只需要找到Segue然後按著control即可。特別注意:在結果頁,因為class裡面的參數是需要初始值,不然就是必須定義為optional,因此我確定我一定會傳資料過去,所以給“!”告訴他,我一定要取資料
// 在結果頁接收資料
class resultViewController: UIViewController {
// 我一定要取資料,強制取資料,但沒有的話就壞掉,特別注意
var heartsAcount: Int!
@IBOutlet weak var resultImageView: UIImageView!
@IBOutlet weak var resultLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
showResult()
}
func showResult() {
if heartsAcount > 0 {
resultImageView.image = UIImage(named: "win")
resultLabel.text = "LEVEL UP"
} else {
resultImageView.image = UIImage(named: "lose")
resultLabel.text = "You LOSE!"
}
}
}
附上github