在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
上周在图书馆借了一本Swift语言实战入门,入个门玩一玩^_^正好这本书的后面有一个2048小游戏的实例,笔者跟着实战了一把。 差不多一周的时间,到今天,游戏的基本功能已基本实现,细节我已不打算继续完善,就这么整理一下过程中一些值得记录的点吧。 用的Swift版本是2.0,原书中的Swift版本会低一些,所以实践起来有些地方语法并不一样。 一、开始页面
在程序的第一张页面(Main.storyboard)上,只放了一个“开始游戏”按钮,点击按钮,弹出一个提示对话框,确认后,进入游戏页面。 1 @IBAction func startGame(sender: UIButton) { 2 let alerController = UIAlertController(title: "开始", message: "游戏就要开始,你准备好了吗?", preferredStyle: UIAlertControllerStyle.Alert) 3 alerController.addAction(UIAlertAction(title: "Ready Go!", style: UIAlertActionStyle.Default, handler: { 4 action in 5 self.presentViewController(MainTabViewController(), animated: true, completion: nil) 6 })) 7 self.presentViewController(alerController, animated: true, completion: nil) 8 9 } 二、游戏页面用一个TabViewController来实现,控制游戏页面(MainViewController)、设置页面(SettingViewController)和主题页面(KKColorListViewController)等三个页面的切换。 1 import UIKit 2 3 class MainTabViewController: UITabBarController,KKColorListViewControllerDelegate { 4 var viewMain = MainViewController() 5 var viewColor = KKColorListViewController(schemeType:KKColorsSchemeType.Crayola) 6 override func viewDidLoad() { 7 super.viewDidLoad() 8 9 // Do any additional setup after loading the view. 10 11 viewMain.title = "2048" 12 let user=UserModel.sharedInstance().user 13 if let red = user?.red{ 14 let uicolor=UIColor(red: red, green: (user?.green)!, blue: (user?.blue)!, alpha: (user?.alpha)!) 15 viewMain.view.backgroundColor=uicolor 16 } 17 18 let viewSetting = SettingViewController() 19 viewSetting.title = "设置" 20 21 viewColor.title="颜色" 22 viewColor.headerTitle="选择背景色" 23 viewColor.delegate=self 24 25 let main = UINavigationController(rootViewController: viewMain) 26 let setting = UINavigationController(rootViewController: viewSetting) 27 let color = UINavigationController(rootViewController: viewColor) 28 29 self.viewControllers = [main, setting,color] 30 self.selectedIndex = 0 31 } 32 33 override func didReceiveMemoryWarning() { 34 super.didReceiveMemoryWarning() 35 // Dispose of any resources that can be recreated. 36 } 37 38 func colorListController(controller: KKColorListViewController!, didSelectColor color: KKColor!) { 39 viewMain.view.backgroundColor = color.uiColor() 40 41 UserModel.sharedInstance().saveColor(color.uiColor()) 42 self.selectedIndex=0 43 } 44 45 func colorListPickerDidComplete(controller: KKColorListViewController!) { 46 self.selectedIndex=0 47 } 48 49 } (一)主题页面其中,主题页面直接使用GitHub上的一个开源项目KKColorListViewController,选中颜色后,改变游戏页面的背景色。 这个项目可以从GitHub直接下载,但这个项目是用Objective-C写的,所以添加到Swift项目中后,需要新建一个Bridge头文件,这个头文件需要保存在项目文件夹的根目录下,而不是项目文件夹里面的源码文件夹(否则,可能需要自己配置头文件的目录) 1 #ifndef Bridging_Header_h 2 #define Bridging_Header_h 3 #import "KKColorListPicker.h" 4 5 #endif /* Bridging_Header_h */ 另外,添加到项目后,编译时还会有一些文件会报错,需要修改一些细节才能正常使用。 (1)KKColorsSchemeType.h中需添加 #import <Foundation/Foundation.h>
(2)KKColorListViewController中(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath方法,将最后一句注释掉。否则每次选完颜色,程序就会关闭当前的MainTabViewController而回到开始游戏页面。 1 - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 2 { 3 KKColor *color = self.colors[indexPath.row]; 4 if (self.delegate) { 5 [self.delegate colorListController:self didSelectColor:color]; 6 } 7 // [self dismissViewControllerAnimated:YES completion:nil]; 8 } (二)游戏页面
1、ScoreView游戏页面,上方有两个自定义的UIView,用于动态显示游戏分数。
1 import UIKit 2 3 enum ScoreType{ 4 case Common 5 case Best 6 } 7 8 protocol ScoreViewProtocol{ 9 func changeScore(value s:Int) 10 } 11 12 class ScoreView: UIView, ScoreViewProtocol { 13 14 var label:UILabel! 15 let defaultFrame = CGRectMake(0, 0, 100, 30) 16 var stype:String! 17 18 var score:Int = 0 { 19 didSet{ 20 label.text = "\(stype):\(score)" 21 } 22 } 23 24 init(stype: ScoreType){ 25 super.init(frame: defaultFrame) 26 self.stype = (stype == ScoreType.Common ? "分数":"最高分") 27 28 backgroundColor = UIColor.orangeColor() 29 label = UILabel(frame: defaultFrame) 30 label.textAlignment = NSTextAlignment.Center 31 label.font = UIFont(name: "微软雅黑", size: 16) 32 label.textColor = UIColor.whiteColor() 33 34 self.addSubview(label) 35 36 //布局约束 37 //必须将该属性值设置为false,否则自己设置的约束和AutoresizingMask生成的约束有冲突,运行时会产生异常 38 self.translatesAutoresizingMaskIntoConstraints = false 39 //宽度约束 40 self.widthAnchor.constraintEqualToConstant(100).active=true 41 //高度约束 42 self.heightAnchor.constraintEqualToConstant(30).active=true 43 } 44 45 required init?(coder aDecoder: NSCoder) { 46 super.init(coder: aDecoder) 47 } 48 49 func changeScore(value s: Int) { 50 self.score = s 51 } 52 53 } 上面的ScoreView首先由一个ScoreType来选择显示“分数”还是“最高分”,然后有一个changeScore的方法,可以改变Score属性值,改变该值得时候同时改变Label显示的数字。 值得一提的是,在该UIView中,添加了布局约束,方便我们把该UIView添加到页面时控制它的布局。在iOS9.0中,多了一个 2、按钮游戏页面下方是两个按钮,重置清空本次游戏的数字,生成则产生一个数字,这两个按钮主要用于调试。 按钮是在一个ViewFactory的工厂类中生产的,同样生产时,添加了一些布局约束。 1 class func createButton(title:String,action:Selector,sender:UIViewController) -> UIButton{ 2 let button = UIButton(frame: CGRectZero) 3 button.backgroundColor=UIColor.orangeColor() 4 button.setTitle(title, forState: .Normal) 5 button.titleLabel!.textColor=UIColor.whiteColor() 6 button.titleLabel!.font=UIFont.systemFontOfSize(14) 7 8 //布局约束 9 button.translatesAutoresizingMaskIntoConstraints = false 10 button.widthAnchor.constraintEqualToConstant(100).active=true 11 button.heightAnchor.constraintEqualToConstant(30).active=true 12 13 button.addTarget(sender, action: action, forControlEvents: UIControlEvents.TouchUpInside) 14 return button 15 } 3、游戏区域(游戏地图)一个5X5的矩阵,首先在所有位置上放置灰色的方块UIView。 1 var backgrounds:Array<UIView>! //所有方块的背景 2 func setupGameMap(){ 3 let margins = self.view.layoutMarginsGuide 4 5 for row in 0..<self.dimension { 6 for col in 0..<self.dimension { 7 //放置灰色的方块在对应的矩阵位置上 8 let background = UIView(frame: CGRectMake(0, 0, self.width, self.width)) 9 background.backgroundColor = UIColor.darkGrayColor() 10 background.translatesAutoresizingMaskIntoConstraints = false 11 background.widthAnchor.constraintEqualToConstant(self.width).active = true 12 background.heightAnchor.constraintEqualToConstant(self.width).active = true 13 self.view.addSubview(background) 14 self.backgrounds.append(background) 15 16 //布局约束 17 background.translatesAutoresizingMaskIntoConstraints=false 18 background.widthAnchor.constraintEqualToConstant(self.width).active=true 19 background.heightAnchor.constraintEqualToConstant(self.width).active=true 20 21 //用代码进行布局约束 22 var centerXConstant:CGFloat 23 var centerYConstant:CGFloat 24 if self.dimension%2 == 1 { 25 centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/2) 26 centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/2) 27 }else{ 28 centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/2)+0.5) 29 centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/2)+0.5) 30 } 31 32 background.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true 33 background.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true 34 35 } 36 } 37 } 然后,添加数字时,再在相应位置上放置数字方块TileView。 1 var tiles = [NSIndexPath:TileView]() //存储当前的有数字的方块 2 3 //插入一个数字方块 4 func insertTile(pos:(Int,Int),value:Int){ 5 let (row,col)=pos 6 7 let x=50 + CGFloat(col) * (self.width+self.padding) 8 let y=150 + CGFloat(row) * (self.width+self.padding) 9 10 let tile=TileView(pos:CGPointMake(x,y),width:self.width,value:value) 11 12 self.view.addSubview(tile) 13 self.view.bringSubviewToFront(tile) 14 15 let index = NSIndexPath(forRow: row, inSection: col) 16 17 tiles[index] = tile 18 19 //布局约束 20 var centerXConstant:CGFloat 21 var centerYConstant:CGFloat 22 if self.dimension%2 == 1 { 23 centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/2) 24 centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/2) 25 }else{ 26 centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/2)+0.5) 27 centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/2)+0.5) 28 } 29 let margins=self.view.layoutMarginsGuide 30 tile.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true 31 tile.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true 32 } 上面用了一个数字tiles来保存插入的TileView,以便清除数字时,可以把对应的TileView从界面中移除。 1 //移除一个数字方块 2 func clearTile(row:Int,col:Int){ 3 4 let index=NSIndexPath(forRow: row, inSection: col) 5 let tile=tiles[index]! 6 tile.removeFromSuperview() 7 tiles.removeValueForKey(index) 8 9 } TileView的实现代码如下: 1 import Foundation 2 import UIKit 3 4 5 class TileView : UIView { 6 let colorMap=[ 7 2:UIColor.redColor(), 8 4:UIColor.orangeColor(), 9 8:UIColor.lightTextColor(), 10 16:UIColor.greenColor(), 11 32:UIColor.brownColor(), 12 64:UIColor.blackColor(), 13 128:UIColor.purpleColor(), 14 256:UIColor.lightGrayColor(), 15 512:UIColor.cyanColor(), 16 1024:UIColor.magentaColor(), 17 2048:UIColor.blackColor() 18 ] 19 20 var value:Int{ 21 didSet{ 22 backgroundColor=colorMap[value] 23 numberLabel.text="\(value)" 24 } 25 } 26 27 var numberLabel:UILabel! 28 29 init(pos:CGPoint,width:CGFloat,value:Int){ 30 self.value=value 31 32 numberLabel=UILabel(frame: CGRectMake(0, 0, width, width)) 33 numberLabel.textColor=UIColor.whiteColor() 34 numberLabel.textAlignment=NSTextAlignment.Center 35 numberLabel.minimumScaleFactor=0.5 36 numberLabel.font=UIFont(name: "微软雅黑", size: 20) 37 numberLabel.text="\(value)" 38 39 super.init(frame: CGRectMake(pos.x, pos.y,width , width)) 40 addSubview(numberLabel) 41 backgroundColor=colorMap[value] 42 43 //代码约束 44 self.translatesAutoresizingMaskIntoConstraints = false 45 self.widthAnchor.constraintEqualToConstant(width).active=true 46 self.heightAnchor.constraintEqualToConstant(width).active=true 47 48 } 49 50 required init?(coder aDecoder: NSCoder) { 51 self.value = 2 52 super.init(coder: aDecoder) 53 } 54 55 56 } 4、游戏模型(1)游戏数据存储采用一个自定义的矩阵数据结构,存储游戏数据。 //自定义矩阵,对应2048的游戏面板 struct Matrix { let rows:Int,columns:Int var grid:[Int] init(rows:Int,columns:Int){ self.rows=rows self.columns=columns grid = Array<Int>(count: rows * columns, repeatedValue: 0) } func indexIsValidForRow(row:Int,column:Int)->Bool{ return (row >= 0) && (row < rows) && (column >= 0) && (column < columns) } subscript(row:Int,column:Int)->Int{ get{ assert(indexIsValidForRow(row, column: column),"超出范围") return grid[(row * columns)+column] } set{ assert(indexIsValidForRow(row, column: column),"超出范围") grid[(row * columns)+column]=newValue } } // 相等函数,判断两个Matrix是否相等 func isEqualTo(matrix:Matrix)->Bool{ if rows != matrix.rows{ return false } if columns != matrix.columns{ return false } for i in 0..<rows{ for j in 0..<columns{ if self[i,j] != matrix[i,j]{ return false } } } return true } } 游戏模型中,包含tiles和维度两个属性,用维度来对tiles这个Matrix进行初始化,同时,提供一些方法。 1 class GameModel{ 2 var dimension:Int = 0{ 3 didSet{ 4 self.tiles = Matrix(rows: self.dimension, columns: self.dimension) 5 } 6 } 7 var tiles:Matrix 8 init(dimension:Int){ 9 self.dimension=dimension 10 self.tiles = Matrix(rows: self.dimension, columns: self.dimension) 11 } 12 13 func emptyPosition()->[Int]{ 14 var emptytiles=Array<Int>() 15 for row in 0..<self.dimension{ 16 for col in 0..<self.dimension{ 17 let val=tiles[row,col] 18 if val==0 { 19 emptytiles.append(tiles[row,col]) 20 } 21 } 22 23 } 24 return emptytiles 25 } 26 27 func isFull()->Bool{ 28 if emptyPosition().count==0 { 29 return true 30 } 31 return false 32 } 33 // 打印矩阵,调试时使用 34 func printTiles(){ 35 for i in 0..<self.dimension{ 36 print(tiles.grid[(i*self.dimension)...((i+1)*self.dimension-1)]) 37 } 38 } 39 40 // 设置某个位置的值 41 func setPosition(row:Int,col:Int,value:Int)->Bool{ 42 assert(row>=0 && row<dimension) 43 assert(col>=0 && col<dimension) 44 45 print("值更新之前:") 46 printTiles() 47 48 tiles[row,col]=value 49 50 print("值更新之后:tiles[\(row),\(col)]=\(tiles[row,col])") 51 printTiles() 52 return true 53 } 54 // 清除数据 55 func clearAll(){ 56 tiles=Matrix(rows: self.dimension, columns: self.dimension) 57 } 58 59 } (2)游戏数据变更产生一个数字: 1 //生成新的数字,生成按钮的响应方法 2 func genNumber(){ 3 //// 随机产生数字2和4,几率为1:4 4 // let randv=Int(arc4random_uniform(5)) 5 // var seed:Int = 2 6 // if randv==1 { 7 // seed = 4 8 // } 9 10 let col=Int(arc4random_uniform(UInt32(dimension))) 11 let row=Int(arc4random_uniform(UInt32(dimension))) 12 13 if gameModel.isFull(){ 14 print("位置已经满了") 15 return 16 } 17 18 if gameModel.tiles[row, col]>0 { 19 genNumber() 20 return 21 } 22 23 let seed=2 //原书的程序,按照一定的几率比来生成2或4。此处改成一直生成2. 24 gameModel.setPosition(row, col: col, value: seed) 25 insertTile((row,col), value: seed) 26 27 // 生成数字后,判断一下游戏是否结束 28 checkGameOver() 29 } 每次生成一个数字后,判断一下游戏是否结束。如果矩阵的所有位置都有数字,并且相邻位置的数字都不相同,则本轮游戏结束,弹出一个提示框,重新开始游戏。 1 //检查游戏是否结束 2 func checkGameOver(){ 3 if self.gameModel.isFull(){ 4 for row in 0..<self.dimension{ 5 for col in 0..<self.dimension{ 6 let val = gameModel.tiles[row,col] 7 let left:Int? = (col-1)>=0 ? gameModel.tiles[row,col-1] : nil 8 let right:Int? = (col+1)<self.dimension ? gameModel.tiles[row,col+1] : nil 9 let up:Int? = (row-1)>=0 ? gameModel.tiles[row-1,col] : nil 10 let down:Int? = (row+1)<self.dimension ? gameModel.tiles[row+1,col] : nil 11 if (val==left) || (val==right) || (val==up) || (val==down){ 12 return 13 } 14 15 } 16 } 17 18 let alerController = UIAlertController(title: "游戏结束", message: "本轮游戏结束,重新开始吧!", preferredStyle: UIAlertControllerStyle.Alert) 19 alerController.addAction(UIAlertAction(title: "Ready Go!", style: UIAlertActionStyle.Default, handler: { 20 |
请发表评论