上周在图书馆借了一本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中,多了一个NSLayoutAnchor类,用它来完成布局约束,比原来低版本用的NSLayoutConstraint要更方便一些。
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 }