標籤:
上篇地址:swift實戰入門之手把手教你編寫2048(一)
github地址:https://github.com/scarlettbai/2048.git。
上篇文章已經中已經把2048的遊戲區塊畫好了,這篇來加入計分板以及往遊戲面板中插入數字塊
計分板同樣作為一個view,我們建立一個ScoreView.swift檔案,代碼如下:
import UIKit//這裡協議的作用是方便別的類中調用計分板的scoreChanged方法protocol ScoreProtocol{ func scoreChanged(newScore s : Int)}class ScoreView : UIView , ScoreProtocol{ //計分板本身是個lable,作用是顯示分數 var lable : UILabel //分數 var score : Int = 0{ didSet{ lable.text = "SCORE:\(score)" } } let defaultFrame = CGRectMake(0, 0, 140, 40) init(backgroundColor bgColor : UIColor, textColor tColor : UIColor , font : UIFont){ lable = UILabel(frame : defaultFrame) lable.textAlignment = NSTextAlignment.Center super.init(frame : defaultFrame) backgroundColor = bgColor lable.textColor = tColor lable.font = font lable.layer.cornerRadius = 6 self.addSubview(lable) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func scoreChanged(newScore s : Int){ score = s }}
其中引入了swift中的Protocol這個概念,即協議,其作用類似於Java中的介面,就是方便別的地方調用其中的對外暴露的方法。
加入了ScoreView之後,我們再在主控制器NumbertailGameController中初始化一個計分板即可,代碼如下:
func setupGame(){ //...此處省略之前代碼 //初始化一個ScoreView let scoreView = ScoreView( backgroundColor: UIColor(red : 0xA2/255, green : 0x94/255, blue : 0x5E/255, alpha : 1), textColor: UIColor(red : 0xF3/255, green : 0xF1/255, blue : 0x1A/255, alpha : 0.5), font: UIFont(name: "HelveticaNeue-Bold", size: 16.0) ?? UIFont.systemFontOfSize(16.0) ) let views = [scoreView , gamebord] //定位其在主面板中左上方的絕對位置 var f = scoreView.frame f.origin.x = xposition2Center(view: scoreView) f.origin.y = yposition2Center(0, views: views) scoreView.frame = f //調用其自身方法來初始化一個分數 scoreView.scoreChanged(newScore: 13631488)}
運行結果如下:
可以看到計分板已經出現在遊戲中了,接下來我們往遊戲中加入數字塊,一個數字塊其實也就是一個view,所以同樣我們建立一個TileView.swift檔案,代碼如下:
import UIKitclass TileView : UIView{ //數字塊中的值 var value : Int = 0 { didSet{ backgroundColor = delegate.tileColor(value) lable.textColor = delegate.numberColor(value) lable.text = "\(value)" } } //提供顏色選擇 unowned let delegate : AppearanceProviderProtocol //一個數字塊也就是一個lable var lable : UILabel init(position : CGPoint, width : CGFloat, value : Int, delegate d: AppearanceProviderProtocol){ delegate = d lable = UILabel(frame : CGRectMake(0 , 0 , width , width)) lable.textAlignment = NSTextAlignment.Center lable.minimumScaleFactor = 0.5 lable.font = UIFont(name: "HelveticaNeue-Bold", size: 15) ?? UIFont.systemFontOfSize(15) super.init(frame: CGRectMake(position.x, position.y, width, width)) addSubview(lable) lable.layer.cornerRadius = 6 self.value = value backgroundColor = delegate.tileColor(value) lable.textColor = delegate.numberColor(value) lable.text = "\(value)" } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }}
這裡的AppearanceProviderProtocol其實就是裡面定義了一些顏色,可以根據當前的數字值來取不同的顏色,內容很簡單,為了不佔篇幅,此處就省略了,大家可以在github上下載源碼來看下。
同樣的,加了視圖後,我們需要將其初始化出來,這裡由於數字塊是在遊戲面板中的,所以我們將初始化方法放入GamebordView類中,在GamebordView.swift中新增如下方法:
func insertTile(pos : (Int , Int) , value : Int) { assert(positionIsValied(pos)) let (row , col) = pos //取出當前數字塊的左上方座標(相對於遊戲區塊) let x = tilePadding + CGFloat(row)*(tilePadding + tileWidth) let y = tilePadding + CGFloat(col)*(tilePadding + tileWidth) let tileView = TileView(position : CGPointMake(x, y), width: tileWidth, value: value, delegate: provider) addSubview(tileView) bringSubviewToFront(tileView) tiles[NSIndexPath(forRow : row , inSection: col)] = tileView //這裡就是一些動畫效果,如果有興趣可以研究下,不影響功能 UIView.animateWithDuration(tileExpandTime, delay: tilePopDelay, options: UIViewAnimationOptions.TransitionNone, animations: { tileView.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale, self.tilePopMaxScale)) }, completion: { finished in UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in tileView.layer.setAffineTransform(CGAffineTransformIdentity) }) })}func positionIsValied(position : (Int , Int)) -> Bool{ let (x , y) = position return x >= 0 && x < dimension && y >= 0 && y < dimension}
上面代碼很簡單,就是取出數字塊相對於遊戲區塊的座標,然後初始化出數字塊,添加到遊戲面板中,且將其置於上層。
接下來我們在主控制器NumbertailGameController中調用此方法,就可以看到效果了,代碼如下:
var bord : GamebordView?func setupGame(){ //...此處省略之前代碼 gamebord.insertTile((3,1) , value : 2) gamebord.insertTile((1,3) , value : 2)}func insertTile(pos : (Int , Int) , value : Int){ assert(bord != nil) let b = bord! b.insertTile(pos, value: value)}
運行看效果如下:
可以看到遊戲區塊中已經有數字塊了,接下來會面臨一個問題,就是我們的2048遊戲,是在每次滑動之後,隨機向空餘的地方插入一個數字塊,那麼,這裡我們需要提供一個隨機向空餘地方插入數字塊的方法,這裡選用一個我們選用一個數組結構來儲存已經添加進遊戲的數字塊,數組中儲存當前塊的值,我們通過當前值是否為空白來判斷這個位置是否空閑,首先我們建立一個BaseModle.swift檔案,代碼如下:
import Foundation//數組中存放的枚舉,要麼空要麼一個帶值的Tileenum TileEnum { case Empty case Tile(Int)}struct SequenceGamebord<T> { var demision : Int //存放實際值的數組 var tileArray : [T] init(demision d : Int , initValue : T ){ self.demision = d tileArray = [T](count : d*d , repeatedValue : initValue) } //通過當前的x,y座標來計算儲存和取出的位置 subscript(row : Int , col : Int) -> T { get{ assert(row >= 0 && row < demision && col >= 0 && col < demision) return tileArray[demision*row + col] } set{ assert(row >= 0 && row < demision && col >= 0 && col < demision) tileArray[demision*row + col] = newValue } } //初始化時使用 mutating func setAll(value : T){ for i in 0..<demision { for j in 0..<demision { self[i , j] = value } } }}
上段代碼涉及到兩個關鍵字,其中subscript就是給結構體定義下標訪問方式,mutating是結構體在修改自身屬性時必須要加的。
結構體定義好了,我們知道現在要存放整個數字塊狀態的結構體就是一個SequenceGamebord<TileEnum>,接下來,我們需要建立一個GameModle.swift充當我們的遊戲空間的modle層,來記錄當前遊戲的狀態以及提供一些遊戲自身的操作等(這裡大家可以注意下,這個項目中命名規則我都是視圖層以View結尾,控制層以Controller結尾,模型層以Modle結尾,不太理解這些層意義的建議去Google下MVC,此處就不多講了)。代碼如下:
import UIKitclass GameModle : NSObject { let dimension : Int let threshold : Int //存放數字塊狀態資訊 var gamebord : SequenceGamebord<TileEnum> unowned let delegate : GameModelProtocol //當前分數,改變後回調用分數視圖渲染分數 var score : Int = 0{ didSet{ delegate.changeScore(score) } } //初始化一個都存的Empty的SequenceGamebord<TileEnum> init(dimension : Int , threshold : Int , delegate : GameModelProtocol) { self.dimension = dimension self.threshold = threshold self.delegate = delegate gamebord = SequenceGamebord(demision: dimension , initValue: TileEnum.Empty) super.init() }}
上面代碼很簡單,下面我們來新加方法取出遊戲區中空置的塊:
func getEmptyPosition() -> [(Int , Int)] { var emptyArrys : [(Int , Int)] = [] for i in 0..<dimension { for j in 0..<dimension { if case .Empty = gamebord[i , j] { emptyArrys.append((i , j)) } } } return emptyArrys}
代碼很簡單,就是通過遍曆SequenceGamebord<TileEnum>,將不為空白的位置群組成一個(Int , Int)的字典數組返回。
接下來寫隨機插入的方法:
func insertRandomPositoinTile(value : Int) { let emptyArrays = getEmptyPosition() if emptyArrays.isEmpty { return } let randomPos = Int(arc4random_uniform(UInt32(emptyArrays.count - 1))) let (x , y) = emptyArrays[randomPos] gamebord[(x , y)] = TileEnum.Tile(value) delegate.insertTile((x , y), value: value)}
這個方法也很簡單,就是取出當前所有的空的位置數組,在隨機一個數組中的位置,之後賦值給gamebord以及調用遊戲視圖層渲染出新的遊戲區塊
接下來我們在主控制器NumbertailGameController中加入如下代碼看下效果:
var gameModle : GameModle?init(dimension d : Int , threshold t : Int) { //...此處省略之前代碼 gameModle = GameModle(dimension: dimension , threshold: threshold , delegate: self )}func setupGame(){ //...此處省略之前代碼 assert(gameModle != nil) let modle = gameModle! modle.insertRandomPositoinTile(2) modle.insertRandomPositoinTile(2) modle.insertRandomPositoinTile(2)}
運行看下效果:
可以看到,在隨機位置插入了三個數字為2的數字塊。
今天就先介紹到這裡,下期來將數字塊的移動。
我的部落格:blog.scarlettbai.com
歡迎關注個人公眾號:讀書健身編程
[swift實戰入門]手把手教你編寫2048(二)