BLACKPINK小遊戲(創作有梗的 iOS App 遊戲)

1.畫面截圖

2.App操作GIF

簡單說明遊戲規則:

藍鍵:開始/暫停,粉鍵:重置,綠鍵:往左,黃鍵:往右

角色站的階梯會一直往下,吃到金幣會往上,一旦角色碰到天花板或地板的西洋棋,遊戲就結束了,看你能可以撐多久!

3.GitHub連結

https://github.com/00657136/sta

前言:

這次的作業是要創作有梗的小遊戲,像我這樣天份點滿的高手,自然是不屑做一些像撲克牌的老梗遊戲,肯定是寫類似瑪力歐吃金幣的遊戲才符合我的水平。

剛好最近我熱愛的女團blackpink出了新專輯,所以我拿她們作為主題,創作了這個blackpink pixel game!


4.必備功能:

(1) 多個頁面。

多個頁面有什麼難的,輕鬆完成

(2) 至少定義一個跟資料有關的型別(MVC 裡的 M),在型別裡至少定義一個 init。比方定義撲克牌的 Card 型別。

class selectCharacter {
let player :Int
let playerName : String
init(player: Int,playerName: String) {
self.player = player
self.playerName = playerName
}
}
let Lisa = selectCharacter(player: 0, playerName: "Lisa")
let Jennie = selectCharacter(player: 1, playerName: "Jennie")
let Jisoo = selectCharacter(player: 2, playerName: "Jisoo")
let Rose = selectCharacter(player: 3, playerName: "Rose")

由於角色總共有4位成員,所以我定義了selectCharacter這個型別,替四位成員附上代碼,方便切換角色。


(3) 利用 function prepare 傳資料。

我使用兩次傳資料

第一次是為了把選擇角色的結果傳到遊戲中

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! ViewController
controller.pickCharacter = pickNumber
}

第二次是為了把遊戲紀錄傳到計分板上

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! recordViewController
controller.M = minute
controller.S = second
controller.bestMinute = Bminute
controller.bestSecond = Bsecond
}

(4) 使用 UIAlertController。

這個功能我拿來提醒玩家沒有選擇角色

@IBAction func sendbuttom(_ sender: Any) {
if pickNumber != 0 && pickNumber != 1 && pickNumber != 2 && pickNumber != 3{
let controller = UIAlertController(title: "怎麼可以忘了!", message: "你忘了選一個blackpink成員,不選就不能玩喔!快回去選吧~", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
controller.addAction(okAction)
present(controller, animated: true, completion: nil)
}

功能操作GIF

(5) 利用 Timer 實現計時或倒數功能。

我Timer用非常多,像是重複執行動畫、遊戲中角色和階梯降落,還有計時功能,這一項在下方的程式解說會再說明

(6)利用 UIViewPropertyAnimator 製作動畫效果。

在第二頁中,四個角色一直跑步的動畫是用UIViewPropertyAnimator搭配Timer做出來的

timer2 = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (_) in
if self.JisooImage.frame.origin.x == 500 {
self.JisooImage.frame.origin = CGPoint(x:0,y: self.JisooImage.frame.origin.y)
}
if self.LisaImage.frame.origin.x == 500 {
self.LisaImage.frame.origin = CGPoint(x:0,y: self.LisaImage.frame.origin.y)
}
if self.JennieImage.frame.origin.x == 500 {
self.JennieImage.frame.origin = CGPoint(x:0,y: self.JennieImage.frame.origin.y)
}
if self.RoseImage.frame.origin.x == 500 {
self.RoseImage.frame.origin = CGPoint(x:0,y: self.RoseImage.frame.origin.y)
}
else{
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1,delay: 0,options:[.repeat],animations:{self.JisooImage.frame.origin = CGPoint(x:500,y: self.JisooImage.frame.origin.y)})
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1,delay: 0,options:[.repeat],animations:{self.LisaImage.frame.origin = CGPoint(x:500,y: self.LisaImage.frame.origin.y)})
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1,delay: 0,options:[.repeat],animations:{self.JennieImage.frame.origin = CGPoint(x:500,y: self.JennieImage.frame.origin.y)})
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1,delay: 0,options:[.repeat],animations:{self.RoseImage.frame.origin = CGPoint(x:500,y: self.RoseImage.frame.origin.y)})
}
})

if self._.frame.origin.x == 500判斷是否動畫執行完成,如果完成就讓角色回到原本的位置,再跟著Timer重複執行


(7) 透過 present 顯示以下連結提到的某一種 controller。(不包含 UIAlertController)

我選擇做SFSafariViewController,連到我的Medium

@IBAction func Link(_ sender: Any) {
if let url = URL(string: "https://medium.com/@jeff.797886") {
let safari = SFSafariViewController(url: url)
safari.delegate = self
present(safari,animated: true,completion: nil)
}
}

別忘了要import喔

import SafariServices

功能操作GIF


(8) 儲存個人的分數或勝敗記錄,顯示在成績頁面上。

這個在上面的function prepare有稍微提過,這邊就只附上判斷最佳紀錄和計分板的code

if minute*60+second >= Bminute*60+Bsecond{
Bminute = minute
Bsecond = second
}

記分板

import UIKit
class recordViewController: UIViewController {
var bestMinute:Int=0
var bestSecond:Int=0
var M:Int = 0
var S:Int = 0
@IBOutlet weak var Record: UILabel!
@IBOutlet weak var BestRecord: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if S < 10{
Record.text = "\(M):0\(S)"
}
else if S >= 10{
Record.text = "\(M):\(S)"
}
if bestSecond < 10{
BestRecord.text = "\(bestMinute):0\(bestSecond)"
}
else if bestSecond >= 10{
BestRecord.text = "\(bestMinute):\(bestSecond)"
}
if bestSecond < 10{
BestRecord.text = "\(bestMinute):0\(bestSecond)"
}
else if bestSecond >= 10{
BestRecord.text = "\(bestMinute):\(bestSecond)"
}
// Do any additional setup after loading the view.
}

(9 )加入通知提醒功能,比方每天晚上九點通知打開 App 玩遊戲。(上課沒教的自學功能)

我寫在第二頁的HINT按鈕,按了後會有通知,告訴玩家這款小遊戲該怎麼玩

@IBAction func readme(_ sender: Any) {
let content = UNMutableNotificationContent()
content.title = "遊戲說明"
content.subtitle = "選擇一位blackpink成員成為你的夥伴"
content.body = "藍鍵:開始/暫停,粉鍵:重置,綠鍵:往左,黃鍵:往右,吃到金幣會往上,一旦角色碰到天花板或地板,遊戲就結束了,看你能可以撐多久!"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
let request = UNNotificationRequest(identifier: "notification1", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}

5.程式說明

洋洋灑灑看了整篇文章,還是不知道遊戲在幹嘛?沒事沒事,這段會把我所有程式碼貼上來,並且一一詳解

第一頁

import UIKit
class pageViewController: UIViewController {
@IBOutlet weak var press: UIButton!
@IBOutlet weak var Rose: UIImageView!
@IBOutlet weak var Jisoo: UIImageView!
@IBOutlet weak var Jennie: UIImageView!
@IBOutlet weak var Lisa: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { (_) in
if self.i == 1{
self.i -= 1
self.Jisoo.image =  imageLiteral(resourceName: "jisoo2")
self.Lisa.image =  imageLiteral(resourceName: "Lisa2")
self.Jennie.image =  imageLiteral(resourceName: "jennie2")
self.Rose.image =  imageLiteral(resourceName: "rose2")
self.press.isHidden = true
}
else{
self.i += 1
self.Jisoo.image =  imageLiteral(resourceName: "jisoo1")
self.Lisa.image =  imageLiteral(resourceName: "Lisa1")
self.Jennie.image =  imageLiteral(resourceName: "jennie1")
self.Rose.image =  imageLiteral(resourceName: "rose1")
self.press.isHidden = false
}
}
// Do any additional setup after loading the view.
}
var i:Int = 0
var timer : Timer?

Timer重複跑,讓角色有動起來的感覺,buttom也會一閃一閃得動


第二頁

import UIKit
import SafariServices
import UserNotifications
class FirstViewController: UIViewController,SFSafariViewControllerDelegate {
@IBOutlet weak var JisooImage: UIImageView!
@IBOutlet weak var LisaImage: UIImageView!
@IBOutlet weak var JennieImage: UIImageView!
@IBOutlet weak var RoseImage: UIImageView!
var i:Int=0
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { (_) in
if self.i == 1{
self.i -= 1
self.JisooImage.image =  imageLiteral(resourceName: "jisoo2")
self.LisaImage.image =  imageLiteral(resourceName: "Lisa2")
self.JennieImage.image =  imageLiteral(resourceName: "jennie2")
self.RoseImage.image =  imageLiteral(resourceName: "rose2")
}
else{
self.i += 1
self.JisooImage.image =  imageLiteral(resourceName: "jisoo1")
self.LisaImage.image =  imageLiteral(resourceName: "Lisa1")
self.JennieImage.image =  imageLiteral(resourceName: "jennie1")
self.RoseImage.image =  imageLiteral(resourceName: "rose1")
}
}
timer2 = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (_) in
if self.JisooImage.frame.origin.x == 500 {
self.JisooImage.frame.origin = CGPoint(x:0,y: self.JisooImage.frame.origin.y)
}
if self.LisaImage.frame.origin.x == 500 {
self.LisaImage.frame.origin = CGPoint(x:0,y: self.LisaImage.frame.origin.y)
}
if self.JennieImage.frame.origin.x == 500 {
self.JennieImage.frame.origin = CGPoint(x:0,y: self.JennieImage.frame.origin.y)
}
if self.RoseImage.frame.origin.x == 500 {
self.RoseImage.frame.origin = CGPoint(x:0,y: self.RoseImage.frame.origin.y)
}
else{
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1,delay: 0,options:[.repeat],animations:{self.JisooImage.frame.origin = CGPoint(x:500,y: self.JisooImage.frame.origin.y)})
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1,delay: 0,options:[.repeat],animations:{self.LisaImage.frame.origin = CGPoint(x:500,y: self.LisaImage.frame.origin.y)})
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1,delay: 0,options:[.repeat],animations:{self.JennieImage.frame.origin = CGPoint(x:500,y: self.JennieImage.frame.origin.y)})
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1,delay: 0,options:[.repeat],animations:{self.RoseImage.frame.origin = CGPoint(x:500,y: self.RoseImage.frame.origin.y)})
}
})
// Do any additional setup after loading the view.
}
var timer:Timer?
var timer2:Timer?
@IBAction func Link(_ sender: Any) {
if let url = URL(string: "https://medium.com/@jeff.797886") {
let safari = SFSafariViewController(url: url)
safari.delegate = self
present(safari,animated: true,completion: nil)
}
}
@IBAction func readme(_ sender: Any) {
let content = UNMutableNotificationContent()
content.title = "遊戲說明"
content.subtitle = "選擇一位blackpink成員成為你的夥伴"
content.body = "藍鍵:開始/暫停,粉鍵:重置,綠鍵:往左,黃鍵:往右,吃到金幣會往上,一旦角色碰到天花板或地板,遊戲就結束了,看你能可以撐多久!"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
let request = UNNotificationRequest(identifier: "notification1", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}

這一頁在上面的必備功能幾乎講完了,讀者請你慢慢對照吧


第三頁

import UIKit
class pickCharacterViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
var pickNumber:Int?
@IBAction func selectLisa(_ sender: Any) {
pickNumber = 0
}
@IBAction func selectJennie(_ sender: Any) {
pickNumber = 1
}
@IBAction func selectJisoo(_ sender: Any) {
pickNumber = 2
}
@IBAction func selectRose(_ sender: Any) {
pickNumber = 3
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! ViewController
controller.pickCharacter = pickNumber
}
@IBAction func sendbuttom(_ sender: Any) {
if pickNumber != 0 && pickNumber != 1 && pickNumber != 2 && pickNumber != 3{
let controller = UIAlertController(title: "怎麼可以忘了!", message: "你忘了選一個blackpink成員,不選就不能玩喔!快回去選吧~", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
controller.addAction(okAction)
present(controller, animated: true, completion: nil)
}
}

按了按鈕就可以讓pickNumber有了數字,讓prepare把選擇角色的結果傳到下一頁


第四頁

這一頁就是遊戲本身,所以不貼上所有程式碼,而是分段解釋

@IBAction func start(_ sender: Any) {
if minute*60+second >= Bminute*60+Bsecond{
Bminute = minute
Bsecond = second
}
if pickCharacter == Rose.player || pickCharacter == Lisa.player || pickCharacter == Jennie.player || pickCharacter == Jisoo.player{
startORpause += 1
}

按下藍鍵開始後,開始算最佳紀錄的時間,同時判斷玩家有沒有選角色,有的話,startORpause會+1,遊戲正式開始


timer2 = Timer.scheduledTimer(withTimeInterval: 2.2, repeats: true){(_)in
self.stairApear = Int.random(in: -2...7)
self.coin4.isHidden = true
self.coin3.isHidden = true
self.coin2.isHidden = true
self.coin1.isHidden = true
self.coin0.isHidden = true
self.coin11.isHidden = true
self.coin22.isHidden = true
}

用亂數搭配Timer讓金幣隨機跳出來,金幣範圍照理來說是-2~4之間,調到7是為了有金幣都沒有出現的情況發生


timer=Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (_) in
self.second += 1
if self.second == 60 {
self.minute += 1
self.second = 0
}

背景Timer運行

self.characterImageView.frame.origin.y += 5
self.chess += 1
self.stair4.frame.origin.y += 5
self.stair3.frame.origin.y += 5
self.stair2.frame.origin.y += 5
self.stair1.frame.origin.y += 5
self.stair0.frame.origin.y += 5
self.stair11.frame.origin.y += 5
self.stair22.frame.origin.y += 5
self.coin4.frame.origin.y += 5
self.coin3.frame.origin.y += 5
self.coin2.frame.origin.y += 5
self.coin1.frame.origin.y += 5
self.coin0.frame.origin.y += 5
self.coin11.frame.origin.y += 5
self.coin22.frame.origin.y += 5

角色、金幣都會隨著階梯往下掉,chess的話是我用來計算與天花板和地板的西洋棋之距離


if self.stairApear == 4{
self.coin4.isHidden = false
if self.wall == 4{
self.characterImageView.frame.origin.y -= 30
self.chess -= 6
self.stair4.frame.origin.y -= 30
self.stair3.frame.origin.y -= 30
self.stair2.frame.origin.y -= 30
self.stair1.frame.origin.y -= 30
self.stair0.frame.origin.y -= 30
self.stair11.frame.origin.y -= 30
self.stair22.frame.origin.y -= 30
self.coin4.frame.origin.y -= 30
self.coin3.frame.origin.y -= 30
self.coin2.frame.origin.y -= 30
self.coin1.frame.origin.y -= 30
self.coin0.frame.origin.y -= 30
self.coin11.frame.origin.y -= 30
self.coin22.frame.origin.y -= 30
self.coin4.isHidden = true
}
}

亂數跳到4號金幣出現時,若角色剛好也在,則判定吃到金幣,階梯往上走,其他金幣也是同樣道理,wall的話是我用來算角色x軸的位置,等等在綠鍵黃鍵會說明


if self.chess >= 45 || self.chess <= -15{
if self.timer != nil{
self.timer?.invalidate()
}
if self.timer2 != nil{
self.timer2?.invalidate()
}

chess是指角色與天花板和地板的西洋棋之距離,碰到後Timer停止

self.startORpause = 0

遊戲也會結束

再按一次藍鍵遊戲會暫停,兩個timer都讓他們停下來

else {
if timer != nil{
timer?.invalidate()
}
if timer2 != nil{
timer2?.invalidate()
}
startORpause = 0
}

@IBAction func turnLeftbuttom(_ sender: Any) {
if startORpause == 1{
if steps == 1{
if(pickCharacter==Rose.player){
characterImageView.image =  imageLiteral(resourceName: "rose2-2")
}
else if(pickCharacter==Lisa.player){
characterImageView.image =  imageLiteral(resourceName: "Lisa2-2")
}
else if(pickCharacter==Jennie.player){
characterImageView.image =  imageLiteral(resourceName: "jennie2-2")
}
else if(pickCharacter==Jisoo.player){
characterImageView.image =  imageLiteral(resourceName: "jisoo2-2")
}
steps += 1
wall -= 1
characterImageView.frame.origin.x -= 50

判斷遊戲是否執行後,按下綠鍵往左後,characterImageView.frame.origin.x -= 50讓角色往左走,wall-1,記錄角色x軸的位置,steps的話是我用來算步伐的數,才可以切換角色的圖片,使角色看起來會動

if wall == -2{
if(pickCharacter==Rose.player){
characterImageView.image =  imageLiteral(resourceName: "rose1-2")
}

當角色碰到牆壁後,角色無法繼續往左,並停在站著的圖片


黃鍵跟綠鍵大同小異,只是往右而已

直接來介紹粉鍵-重置

characterImageView.frame.origin = CGPoint(x: 97, y: 258)
stair4.frame.origin = CGPoint(x: 363, y: 376)
stair3.frame.origin = CGPoint(x: 303, y: 376)
stair2.frame.origin = CGPoint(x: 244, y: 376)
stair1.frame.origin = CGPoint(x: 184, y: 376)
stair0.frame.origin = CGPoint(x: 123, y: 376)
stair11.frame.origin = CGPoint(x: 61, y: 376)
stair22.frame.origin = CGPoint(x: 0, y: 376)
coin4.frame.origin = CGPoint(x: 368,y: 340)
coin3.frame.origin = CGPoint(x: 313,y: 340)
coin2.frame.origin = CGPoint(x: 253,y: 340)
coin1.frame.origin = CGPoint(x: 192,y: 340)
coin0.frame.origin = CGPoint(x: 133,y: 340)
coin11.frame.origin = CGPoint(x: 69,y: 340)
coin22.frame.origin = CGPoint(x: 10,y: 340)
coin4.isHidden = true
coin3.isHidden = true
coin2.isHidden = true
coin1.isHidden = true
coin0.isHidden = true
coin11.isHidden = true
coin22.isHidden = true
steps=0
wall=0
second = 0
minute = 0
chess=0
startORpause = 0
stairReturn = 0
if timer != nil{
timer?.invalidate()
}

所有東西都回到原本的位置,背景timer也停止

第五頁計分板,在必備功能那段貼過了,所有程式碼說明完成


6.心得

原本我想做小朋友下樓梯,但發現問題重重,就退而求其之,做這個吃金幣的遊戲,這遊戲花了我不少心思,從文章篇幅就看得出來,而且素材幾乎都是我自己畫出來的。

真要我說有什麼感想,那大概是我怎麼天份怎麼好