iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Mobile Development

雙平台APP小遊戲開發實作! Swift & Kotlin 攜手出擊~系列 第 26

[Day26] swift & kotlin 遊戲篇!(8) 小雞BB-遊戲製作-歷史紀錄

Swift 遊戲示意

遊戲 小雞BB

Swift 遊戲紀錄

最後一個功能是遊戲紀錄
修改一下Player.swift


import UIKit

class Player: NSObject {
    var point: Int = 1000
    var history: Array<OrderHistory> = []
    
    func addHistory(_  choose: String, _ is_win: Bool, _ newPoint: Int, _ result: ResultResponse){
        self.history.append(OrderHistory(choose, is_win, point, result, newPoint - point))
    }
}

class OrderHistory: NSObject  {
    let choose: String
    let result: ResultResponse
    let is_win: Bool
    let point: Int
    let winPoint: Int
    
    init(_  choose: String, _ is_win: Bool, _ point: Int, _ result: ResultResponse, _ winPoint: Int) {
        self.choose = choose
        self.is_win = is_win
        self.point = point
        self.result = result
        self.winPoint = winPoint
    }
}

然後回到 ViewController.swift
在播放動畫時呼叫 player.addHistory
示意圖中有提到, 遊戲紀錄要寫在第二個分頁
因此我們來新增遊戲紀錄頁面的類別
首先來到這個畫面
遊戲 小雞BB
請分別產生這兩個檔案
遊戲 小雞BB
遊戲 小雞BB
然後設定到UI的Class中
遊戲 小雞BB
遊戲 小雞BB
到 紀錄的Cell上 設定Id 成 "Cell"
遊戲 小雞BB
程式碼分別如這樣

import UIKit

class HistoryViewControllerTableViewController: UITableViewController {
    var history: Array<OrderHistory> = []
    override func viewDidLoad() {
        super.viewDidLoad()
        // 綁定Cell
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }

    // 回傳要幾個table區域
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    //  總共有幾個項目
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return  self.history.count
    }

    // 設定每一條table的顯示內容  
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 剛剛在 設定的ID "Cell"
        let cellIdentifier = "Cell"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)

        // 設定顯示結果
        var newText = "\(indexPath.row+1).  結果:"

        let currentHistory = self.history[indexPath.row]
        
        if currentHistory.is_win {
            newText += "贏 +\(currentHistory.winPoint)"
        } else {
            newText += "輸 \(currentHistory.winPoint)"
        }

        cell.textLabel?.text = newText
        return cell
    }
    
    // 回傳 table 的 Title
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "遊戲紀錄"
    }

    // 當cell變點擊時, 要做的事情
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 利用Segue開畫面 showDetal 前面設定的跳頁id
        performSegue(withIdentifier: "showDetail", sender: nil)
    }

    // 跳頁前可以做一些事情
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // 判斷如果是去 HistoryDetailViewController
        // 就把資料帶過去
        
        if segue.destination is HistoryDetailViewController {
            let vc = segue.destination as? HistoryDetailViewController
            let selectedRowPath = self.tableView.indexPathForSelectedRow
            if let selectedRow = selectedRowPath?.row {
                let currentHistory = history[selectedRow]
                vc?.history = currentHistory
            }
        }
        
        
    }
    

}
import UIKit

class HistoryDetailViewController: UIViewController {
    var history: OrderHistory?
    
    @IBOutlet weak var winPointView: UILabel!
    @IBOutlet weak var resultView: UILabel!
    @IBOutlet weak var chooseView: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 設定畫面顯示
        if let historyData = self.history {
            print(historyData.choose)
            chooseView.text?.append(" \(self.parseChoose(historyData.choose))")
            resultView.text?.append(" \(historyData.is_win ? "贏" : "輸")")
            winPointView.text?.append(" \(historyData.winPoint)")
            if !historyData.is_win {
                view.backgroundColor = UIColor.gray
            }
        }
    }
    
    func parseChoose(_ chooseKey: String) -> String {
        var choose = ""
        
        switch chooseKey {
            case "left_odd":
                choose = "左藍"
            case "left_even":
                choose = "左紅"
            case "right_odd":
                choose = "左藍"
            case "right_even":
                choose = "左藍"
            default:
                choose = ""
        }
        
        return choose
    }

}

最後在ViewController.swift 動畫播放完後
執行更新紀錄的方法
完成~

func updateHistoryPage() -> Void {
    if let tableView = self.tabBarController?.viewControllers?[1] as? HistoryViewControllerTableViewController {
        tableView.history = self.player.history
        tableView.tableView.reloadData()
    }
}

經過了那麼多天的努力
最後我們來看看ViewController.swift 完整的樣子

import UIKit

enum EggWapperDirection {
    case Left
    case Right
}
enum HatColor {
    case Red
    case Blue
}


struct KeyFrameOptionItem {
    let startTime: Double // 動畫開始時間
    let translationX: CGFloat // 左右位移
    let translationY: CGFloat // 上下跳動
    let rotated: CGFloat // 上下角度
    let scaledX: CGFloat // 水平翻轉
    
    init(startTime: Double, translationX: CGFloat, translationY: CGFloat, rotated: CGFloat, scaledX: CGFloat)  {
        let oneDegree = CGFloat.pi / 180 // 透過pi轉換rotated角度
        self.startTime = startTime
        self.translationX = translationX
        self.rotated = rotated * oneDegree
        self.translationY = translationY
        self.scaledX = scaledX
    }
}



class ViewController: UIViewController {
    // 按鈕
    @IBOutlet weak var left_blue: UIButton!
    @IBOutlet weak var right_blue: UIButton!
    @IBOutlet weak var left_red: UIButton!
    @IBOutlet weak var right_red: UIButton!
    @IBOutlet weak var pointLabel: UILabel!
    @IBOutlet weak var winIcon: UIImageView!
    
    //小雞 圖片 參照
    @IBOutlet weak var ggImg: UIImageView!
    @IBOutlet weak var eggshell_right: UIImageView!
    @IBOutlet weak var eggshell_left: UIImageView!
    @IBOutlet weak var Cloud: UIImageView!
    
    // 遊戲畫面
    @IBOutlet weak var gameWapper: UIView!
    @IBOutlet weak var lineWapper: UIView!
    @IBOutlet weak var eggWapperRight: UIView!
    @IBOutlet weak var eggWapperLeft: UIView!
    @IBOutlet weak var hat_blue: UIImageView!
    @IBOutlet weak var hat_red: UIImageView!
    
    var lastLineLayer: CAShapeLayer? = nil
    var lastLineInLineLayer: CAShapeLayer? = nil
    
    var lineWapperHeight: CGFloat  = 0
    var lineWapperWidth: CGFloat  = 0
    //小雞 動畫參數
    var chickKeyFrameOptions: Array<KeyFrameOptionItem> = []
    var chooseList: Set<UIButton> = Set<UIButton>()
    
    var player = Player()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setChickKeyFrameOption()
        self.drawGameLine()
       
    }
    
    override func viewDidAppear(_ animated: Bool) {
        print("viewDidDisappear")
        self.reSetAni()
        
    }
    
    @IBAction func choose(_ sender: UIButton) {
        self.enableAllButton(false)
        
        let session = URLSession(configuration: .default)
        var request = URLRequest(url: URL(string: "http://pinyi.ami-shake.com/gg_order.php")!)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        let data = ["choose": self.getChoose(sender), "balance": String(self.player.point)]
      
        do{
            request.httpBody = try JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions())
        }catch let error{
            print("passer data error")
            print(error)
        }
        
        session.dataTask(with: request) { data, response, error in
            if let data = data {
                do {
                   let res = try JSONDecoder().decode(OrderResponse.self, from: data)
                    DispatchQueue.main.async {
                        self.player.addHistory(self.getChoose(sender), res.info.is_win, Int(res.info.balance)  ?? 0, res.info.result)
                        self.playResultFromResponse(res)
                        self.updateHistoryPage()
                    }
                } catch let error {
                   print("error")
                   print(error)
                }
                
            }
        }.resume()
    }
    
    func updateHistoryPage() -> Void {
        if let tableView = self.tabBarController?.viewControllers?[1] as? HistoryViewControllerTableViewController {
            tableView.history = self.player.history
            tableView.tableView.reloadData()
        }
    }
    
    
    func playResultFromResponse(_ res: OrderResponse) -> Void {
        if(res.info.result.start == "left" && res.info.result.end == "odd" ){
            self.playResult(EggWapperDirection.Left, HatColor.Blue, res.info.is_win, res.info.balance)
        }
        if(res.info.result.start == "left" && res.info.result.end == "even" ){
            self.playResult(EggWapperDirection.Left, HatColor.Red, res.info.is_win, res.info.balance)
        }
        if(res.info.result.start == "right"  && res.info.result.end == "odd" ){
            self.playResult(EggWapperDirection.Right, HatColor.Blue, res.info.is_win, res.info.balance)
        }
        if(res.info.result.start == "right"  && res.info.result.end == "even" ){
            self.playResult(EggWapperDirection.Right, HatColor.Red, res.info.is_win, res.info.balance)
        }
    }
    
    func enableAllButton(_ isEnable: Bool) -> Void {
        let buttonList = [self.left_red, self.left_blue , self.right_red, self.right_blue]
        let disableAlpha: CGFloat = 0.5
        
        for button in buttonList {
            button?.isEnabled = isEnable
            button?.alpha = isEnable ? 1 : disableAlpha
        }
    }
    
    func reSetAni() -> Void {
        self.eggshell_left.transform = .identity
        self.eggshell_left.alpha = 1
        self.eggshell_right.transform = .identity
        self.eggshell_right.alpha = 1
        self.eggWapperLeft.transform = .identity
        self.eggWapperRight.transform = .identity
       
        self.displayCloud(true).startAnimation()
        self.displayLastLine(true)
        // 重新設定初始動畫
        self.setChickAnimation()
        self.enableAllButton(true)
    }
    
    func playResult(_ eggWapperDirection: EggWapperDirection, _ hatColor: HatColor, _ isWin: Bool, _ newPoint: String)-> Void {
        var hasLastLine = true
        
        if(
            (eggWapperDirection == EggWapperDirection.Right && hatColor == HatColor.Blue) ||
            (eggWapperDirection == EggWapperDirection.Left && hatColor == HatColor.Red)
        ){
            hasLastLine = false
        }
        
        let eggshellAni =  self.openEggAni(eggWapperDirection)
        let cloudAni = self.displayCloud(false)
        let playEggAni = self.playEggAniOnLine(eggWapperDirection, hasLastLine)
        
        playEggAni.addCompletion({ _ in
            self.updatePoint(isWin, newPoint)
            self.reSetAni()
        })
        
        cloudAni.addCompletion({ _ in
            playEggAni.startAnimation()
        })
        
        displayLastLine(hasLastLine)
        
        eggshellAni.startAnimation()
        cloudAni.startAnimation()
        
    }
    
    func updatePoint(_ isWin: Bool, _ newPoint: String)-> Void {
        
        self.winIcon.isHidden = false
        if isWin {
            // AudioServicesPlaySystemSound(1325)
            self.winIcon.image = UIImage(systemName: "hands.sparkles.fill")
        } else {
            // ㄆAudioServicesPlaySystemSound(1324)
            self.winIcon.image = UIImage(systemName: "hand.thumbsdown")
        }
        
        self.updatePointAndDisplayInUI(Int(newPoint) ?? 0)
        
        self.checkIsGameOver()
    }
    
    func checkIsGameOver() -> Void {
        if self.player.point > 0 {
            return
        }
        
        self.alertMessage("遊戲結束!", "輸了! 遊戲即將重啟")
        self.updatePointAndDisplayInUI(1000)
    }
    
    func updatePointAndDisplayInUI(_ newPoint: Int) {
        self.player.point = newPoint
        self.pointLabel.text = "Point: \(self.player.point)"
    }
    
    func alertMessage(_ title: String,_ msg: String) -> Void {
        let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
        let okBtn = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(okBtn)
        self.present(alert, animated: true, completion: nil)
        
    }
    
    func openEggAni(_ eggWapperDirection: EggWapperDirection) -> UIViewPropertyAnimator {
        return UIViewPropertyAnimator(duration: 0.5, curve: .linear, animations: {
            let egg: UIView! = eggWapperDirection == EggWapperDirection.Right ? self.eggshell_right : self.eggshell_left
            egg.transform = CGAffineTransform(translationX: 30, y: -30).rotated(by: 30 *  CGFloat.pi / 180 )
            egg.alpha = 0
        })
    }
    
    func playEggAniOnLine(_ eggWapperDirection: EggWapperDirection, _ hasLastLine: Bool) -> UIViewPropertyAnimator {
      
        let eggWapperAni = UIViewPropertyAnimator(duration: 3, curve: .linear)
        eggWapperAni.addAnimations {
            UIView.animateKeyframes(withDuration: 0, delay: 0, animations: {
                let eggRunLineKeyFrameOptions = self.getEggRunLineKeyFrameOptions(hasLastLine);
                for option in eggRunLineKeyFrameOptions {
                    UIView.addKeyframe(
                        withRelativeStartTime: option.startTime,
                        relativeDuration: 0.1,
                        animations: {
                            if(eggWapperDirection == EggWapperDirection.Left){
                                self.eggWapperLeft.transform = CGAffineTransform(translationX: option.translationX, y: option.translationY)
                            } else {
                                self.eggWapperRight.transform = CGAffineTransform(translationX: -option.translationX, y: option.translationY)
                            }
                            
                        })
                }
            })
        }
        
        return eggWapperAni
    }
    
    func setChickAnimation() {
        print("setChickAnimation")
        UIView.animateKeyframes(
            withDuration: 4,
            delay: 0.0,
            options: [.repeat, .calculationModeLinear],
            animations: {
                for option in self.chickKeyFrameOptions {
                    UIView.addKeyframe(
                        withRelativeStartTime: option.startTime,
                        relativeDuration: 0.1,
                        animations: {
                            self.ggImg.transform = CGAffineTransform(translationX: option.translationX, y: option.translationY)
                                .rotated(by: option.rotated)
                                .scaledBy(x: option.scaledX, y: 1.0)
                            self.eggshell_right.transform = CGAffineTransform(translationX: 0, y: option.translationY < 0 ? -10 : 0)
                            self.eggshell_left.transform = CGAffineTransform(translationX: 0, y: option.translationY == 0 ? -10 : 0)
                        }
                    )
                }
            },
            completion: nil
        )
    }
    
    func drawGameLine() {
        // 如果已經初始化過 就不再重繪
        if(self.lineWapperHeight > 0){
            return
        }
        // 高度用帽子最小的Y - 雞蛋最大的Y
        self.lineWapperHeight = hat_blue.frame.minY - eggWapperLeft.frame.maxY
        // 寬度用兩個雞蛋中間的X計算
        self.lineWapperWidth = eggWapperRight.frame.midX - eggWapperLeft.frame.midX
        
        // 左邊柱子
        drawLineWithBoderInLineWapper(
            startPoint: CGPoint(x: eggWapperLeft.frame.midX, y:  (eggWapperLeft.frame.maxY - 20)),
            endPoint: CGPoint(x: hat_blue.frame.midX, y:  (hat_blue.frame.minY - 10)),
            lineWidth: 20,
            rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
            boderWidth: 8,
            boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
        )
        // 右邊柱子
        drawLineWithBoderInLineWapper(
            startPoint: CGPoint(x: eggWapperRight.frame.midX, y:  (eggWapperRight.frame.maxY - 20)),
            endPoint: CGPoint(x: hat_red.frame.midX, y:  (hat_red.frame.minY - 10)),
            lineWidth: 20,
            rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
            boderWidth: 8,
            boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
        )
        
        // 中間橫線
        for yPoint in [0.2, 0.4, 0.6, 0.8] as Array<CGFloat> {
            drawLineWithBoderInLineWapper(
                startPoint: CGPoint(x: eggWapperLeft.frame.midX, y:  (eggWapperLeft.frame.maxY + self.lineWapperHeight * yPoint)),
                endPoint: CGPoint(x: eggWapperRight.frame.midX, y:  (eggWapperRight.frame.maxY + self.lineWapperHeight * yPoint)),
                lineWidth: 20,
                rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
                boderWidth: 8,
                boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
            )
        }
    }
    
    func drawLineWithBoderInLineWapper(startPoint: CGPoint, endPoint: CGPoint, lineWidth: CGFloat, rectColor: UIColor, boderWidth: CGFloat, boderColor: UIColor) {
        let chickmarkLayer = CAShapeLayer()
        
        if(startPoint.y == endPoint.y){
            // 畫橫線
            chickmarkLayer.path = UIBezierPath(rect: CGRect(
                                                        x: startPoint.x + lineWidth/2,
                                                        y: startPoint.y - lineWidth/2,
                                                        width: endPoint.x - startPoint.x - lineWidth,
                                                        height: lineWidth
                                                        )).cgPath
            
        }else {
            // 畫直線
            chickmarkLayer.path = UIBezierPath(roundedRect: CGRect(
                                                        x: startPoint.x - lineWidth/2,
                                                        y: startPoint.y,
                                                        width: lineWidth,
                                                        height: endPoint.y - startPoint.y
                                                        ), cornerRadius: lineWidth/2).cgPath
        }
        chickmarkLayer.fillColor = boderColor.cgColor
        
        
        let chickmarkLayer_inLine = CAShapeLayer()
        let boderInLineWidth = lineWidth - boderWidth
        if(startPoint.y == endPoint.y){
            // 畫橫線
            chickmarkLayer_inLine.path = UIBezierPath(rect: CGRect(
                                                        x: startPoint.x ,
                                                        y: startPoint.y - boderInLineWidth/2,
                                                        width: endPoint.x - startPoint.x + boderWidth / 2,
                                                        height: boderInLineWidth
                                                        )).cgPath
        } else {
            // 畫直線
            chickmarkLayer_inLine.path = UIBezierPath(roundedRect: CGRect(
                                                        x: startPoint.x - boderInLineWidth/2,
                                                        y: startPoint.y + boderWidth/2  ,
                                                        width: boderInLineWidth,
                                                        height: (endPoint.y - startPoint.y - boderWidth)
                                                        ), cornerRadius: boderInLineWidth/2).cgPath
            
        }
        
        chickmarkLayer_inLine.fillColor = rectColor.cgColor
       
        lineWapper.layer.addSublayer(chickmarkLayer)
        lineWapper.layer.addSublayer(chickmarkLayer_inLine)
        
        let lastLineStartPoint = CGPoint(x: eggWapperLeft.frame.midX, y:  (eggWapperLeft.frame.maxY + self.lineWapperHeight * 0.8))
        if(lastLineStartPoint.equalTo(startPoint)){
            self.lastLineLayer = chickmarkLayer
            self.lastLineInLineLayer = chickmarkLayer_inLine
        }
    }
    
    func displayLastLine(_ isShow: Bool) {
        self.lastLineLayer?.isHidden = !isShow
        self.lastLineInLineLayer?.isHidden = !isShow
    }
    
    func displayCloud(_ isShow: Bool) -> UIViewPropertyAnimator{
        let cloudAni = UIViewPropertyAnimator(duration: isShow ? 0 : 1,curve: .linear, animations: {
            self.cloud.alpha = isShow ? 1 : 0
        })
        
        return cloudAni
    }

    fileprivate func getEggRunLineKeyFrameOptions(_ hasLastLine: Bool) -> Array<KeyFrameOptionItem>{
        var keyFrameOptions: Array<KeyFrameOptionItem> = []
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.0, translationX: 0, translationY: self.lineWapperHeight*0.2 + 50, rotated: 0, scaledX: 1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.1, translationX: self.lineWapperWidth, translationY:  self.lineWapperHeight*0.2 + 50, rotated: 0, scaledX: 1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.2, translationX: self.lineWapperWidth, translationY:  self.lineWapperHeight*0.4 + 50, rotated: 0, scaledX: 1.0))

        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.3, translationX: 0, translationY:  self.lineWapperHeight*0.4 + 50, rotated: 0, scaledX:  1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.4, translationX: 0, translationY: self.lineWapperHeight*0.6 + 50, rotated: 0, scaledX:  1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.5, translationX: self.lineWapperWidth,  translationY: self.lineWapperHeight*0.6 + 50, rotated: 0, scaledX: 1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.6, translationX: self.lineWapperWidth, translationY:  self.lineWapperHeight*0.8 + 50, rotated: 0, scaledX:  1.0))
        if(hasLastLine){
            // 走第四條線
            keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: 0, translationY:  self.lineWapperHeight*0.8 + 50, rotated: 0, scaledX: 1.0))
            keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.8, translationX: 0, translationY:  self.lineWapperHeight*1 + 10, rotated: 0, scaledX:  1.0))
        } else {
            
            keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: self.lineWapperWidth, translationY:  self.lineWapperHeight*1 + 10, rotated: 0, scaledX:  1.0))
        }
        
        return keyFrameOptions
    }
    
    fileprivate func setChickKeyFrameOption() {
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.0, translationX: -17.0, translationY: 0.0, rotated: 10, scaledX: 1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.1, translationX: -33.0, translationY:  -10.0, rotated: -10, scaledX: 1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.2, translationX: -50.0, translationY:  0, rotated: 10, scaledX: 1.0))
        // 以上向左邊跳到底後轉身往回走
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.3, translationX: -33.0, translationY:  -10.0, rotated: -10, scaledX:  -1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.4, translationX: -27.0, translationY: 0.0, rotated: 10, scaledX:  -1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.5, translationX: 0.0,  translationY: -10.0, rotated: -10, scaledX: -1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.6, translationX: 30.0, translationY:  0.0, rotated: 10, scaledX:  -1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: 50.0, translationY:  -10.0, rotated: -10, scaledX: -1.0))
        //跳到最右邊後 轉身往原點走
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.8, translationX: 20.0, translationY:  0.0, rotated: 10, scaledX:  1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.9, translationX: 0.0, translationY:  -10.0, rotated: -10, scaledX: 1.0))
    }
    
    fileprivate func getChoose(_ sender: UIButton) -> String {
        if(sender == self.left_red){
            return "left_even"
        }
        if(sender == self.left_blue){
            return "left_odd"
        }
        if(sender == self.right_blue){
            return "right_odd"
        }
        if(sender == self.right_red){
            return "right_even"
        }
        
        return ""
    }
}

整個遊戲撰寫完成!

Kotlin 遊戲示意

遊戲 小雞BB

Kotlin 遊戲紀錄

在Kotlin這邊遊戲紀錄頁面要如何取得紀錄呢?
我們回到HistoryFragment.kt
新增 TextView 與 Rcycler View

遊戲 小雞BB
遊戲 小雞BB

  1. textView(TextView)
    屬性 對齊 設定
    layout_width match_parent
    layout_height wrap_content
    background @color/gray_400
    paddingStart 5dp
    paddingTop 10dp
    paddingEnd 10dp
    paddingBottom 5dp
    text 遊戲紀錄
    textColor #424242
    textSize 20dp
    Start -> StartOf parent 0dp
    End -> EndOf parent 0dp
    Top -> TopOf parent 0dp
  2. recycler_view(RcyclerView)
    屬性 對齊 設定
    layout_width 0d
    layout_height 0dp
    paddingStart 10dp
    paddingEnd 10dp
    scrollbars vertical
    layoutManager LinearLayoutManager
    Start -> StartOf parent 0dp
    End -> EndOf parent 0dp
    Top -> BottomOf textView 0dp
    Bottom -> BottomOf parent 0dp

設定好畫面後, 就給RcyclerView設定一個資料來源
RcyclerView會接受一個Adapter
來處理每一個Cell資料顯示的內容
而每個Cell也是一個 layout
所以我們先在res/layout底下新增一個 Layout Resource File
命名為 list_item.xml
內容這樣

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/editTextBackground">

    <TextView
        android:id="@+id/item_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackground"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:textColor="@color/black"
        android:textSize="20sp" />
</LinearLayout>

然後在 com.test.chickbb 專案資料夾 底下新增一個package adapter
裡面新增一個 kotlin class 叫 ItemAdapter

package com.test.chickbb.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.test.chickbb.R
import com.test.chickbb.player.OrderHistory

class ItemAdapter(private val dataset: List<OrderHistory>): RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
    private lateinit var mListener: onItemClickListener

    class ItemViewHolder(private val view: View, listener: onItemClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
        // 取得 view
        val textView: TextView = view.findViewById(R.id.item_title)

        init {
            view.setOnClickListener {
                listener.onItemClick(adapterPosition)
            }
        }

        override fun onClick(v: View?) {
            TODO("Not yet implemented")
        }

    }
    // 定義點擊事件 用 interface 由使用者實作
    interface onItemClickListener {
        fun onItemClick(position: Int)
    }

    fun setOnItemClickListener(listener: onItemClickListener){
        mListener = listener
    }

    // 設定使用的Layout
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout, mListener)
    }

    // 設定顯示樣式
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = "${position+1}. 結果${if (item.is_win) "贏 +" else "輸"}${item.winPoint}"
    }

    // 設定有幾個cell
    override fun getItemCount() = dataset.size
}


接下來我們預計點擊Cell
會把遊戲結果帶到 ShowDetailFragment

首先到 fragment_show_detail.xml
設定layout 這邊很單純
就三個textView 而已
我直接用Code表示

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frameLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:attr/colorPressedHighlight"
    tools:context=".ShowDetailFragment">

    <TextView
        android:id="@+id/detail_choose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:text="選擇:"
        android:textColor="#5D4037"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/detail_isWin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="10dp"
        android:text="結果:"
        android:textColor="#5D4037"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/detail_choose" />

    <TextView
        android:id="@+id/detail_winPoint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="10dp"
        android:text="得分:"
        android:textColor="#5D4037"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/detail_isWin" />

</androidx.constraintlayout.widget.ConstraintLayout>

再來我們到ShowDetailFragment.kt
設定會傳入三個參數 用來顯示詳細結果

package com.test.chickbb

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.test.chickbb.databinding.FragmentShowDetailBinding

private const val ARG_PARAM1 = "choose"
private const val ARG_PARAM2 = "isWin"
private const val ARG_PARAM3 = "winPoint"

class ShowDetailFragment : Fragment() {
    private var _binding: FragmentShowDetailBinding? = null
    private val binding get() = _binding!!

    private var choose: String? = null
    private var isWin: Boolean? = null
    private var winPoint: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            choose = it.getString(ARG_PARAM1)
            isWin = it.getBoolean(ARG_PARAM2)
            winPoint = it.getString(ARG_PARAM3)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentShowDetailBinding.inflate(inflater, container, false)
        binding.detailChoose.text =  binding.detailChoose.text.toString() + parseChoose(choose!!)
        binding.detailIsWin.text = binding.detailIsWin.text.toString() + if (isWin!!) "贏" else "輸"
        binding.detailWinPoint.text = binding.detailWinPoint.text.toString() + winPoint
        return binding.root
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String, param2: Boolean, param3: String) =
            ShowDetailFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putBoolean(ARG_PARAM2, param2)
                    putString(ARG_PARAM3, param3)
                }
            }
    }

    fun parseChoose(chooseKey: String):String {
        var choose = ""

        when (chooseKey) {
            "left_odd" -> choose = "左藍"
            "left_even" -> choose = "左紅"
            "right_odd" -> choose = "左藍"
            "right_even" -> choose = "左藍"
        }

        return choose
    }
}

接下來就要設定導航了
回到 nav_graph
對 showDetailFragment 設定這三個參數

我們回到HistoryFragment.kt

package com.test.chickbb

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import com.test.chickbb.adapter.ItemAdapter
import com.test.chickbb.databinding.FragmentHistoryBinding
import com.test.chickbb.player.OrderHistory
import com.test.chickbb.player.PlayerViewModel

class HistoryFragment : Fragment() {
    private var _binding: FragmentHistoryBinding? = null
    private val binding get() = _binding!!
    // lateinit 為延後初始化
    private lateinit var player: PlayerViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentHistoryBinding.inflate(inflater, container, false)
        val recyclerView = binding.recyclerView
        // 從Activity取得共用的ViewModel
        player = ViewModelProvider(requireActivity()).get(PlayerViewModel::class.java)

        // 先確認裡面有資料
        if(!player.history.isNullOrEmpty()) {
            // 轉成 history List
            val listHistorys = player.history?.toList()!!
            // 產生出 Adapter 實體
            val adapter = ItemAdapter(listHistorys)
            // 將 Adapter 設定給 RecyclerView
            recyclerView.adapter = adapter

            // 實作 點擊時要做的事
            adapter.setOnItemClickListener(object : ItemAdapter.onItemClickListener{
                override fun onItemClick(position: Int) {
                    // 被點到的對象
                    val currentOrder = listHistorys[position]
                    // 使用 Directions跳轉頁面
                    val action = HistoryFragmentDirections.actionHistoryFragmentToShowDetailFragment(currentOrder.choose, currentOrder.is_win, currentOrder.winPoint.toString())
                    // 跳頁摟
                    binding.root.findNavController().navigate(action)
                }

            })

            recyclerView.setHasFixedSize(true)
        }


        return binding.root
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

好了~所有功能完成
呼~

差異

在Swift內 頁面與頁面間互傳資料
語法上比較簡單

但對於程式設計來說 用 as? 某個頁面Class這種方式
感覺比較隨意 個人不太喜歡

而Kotlin這邊採用 ViewModelProvider 可以取得單一實例的Service
這樣方法個人更喜歡一點
而Kotlin 這邊採用的MVVM架構

我也覺得很好用喔

小碎嘴時間 ヽ(゚´Д`)ノ゚

嗯嗯...程式碼好多 頭好炸
本來也想把Kotlin的完整程式貼出來

但仔細看了看好像不是很必要
頭腦炸裂的程式開發結束了!

明天開始來準備上架摟~


上一篇
[Day25] swift & kotlin 遊戲篇!(7) 小雞BB-遊戲製作-API與遊戲動畫
下一篇
[Day27] swift & kotlin 上架篇!(1) 小雞BB-遊戲上架流程-swift
系列文
雙平台APP小遊戲開發實作! Swift & Kotlin 攜手出擊~30

尚未有邦友留言

立即登入留言