博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Swift 实现俄罗斯方块详细思路解析(附完整项目)
阅读量:4685 次
发布时间:2019-06-09

本文共 9092 字,大约阅读时间需要 30 分钟。

一:写在开发前

    俄罗斯方块,是一款我们小时候都玩过的小游戏,我自己也是看着书上的思路,学着用 Swift 来写这个小游戏,在写这个游戏的过程中,除了一些位置的计算,数据模型和理解 Swift 语言之外,最好知道UIKIt框架中的  这个知识点。是我在简书上面找的,是关于  这个知识点的,看它我觉得也就够学习。经过这两天的整理,充分觉得在写这些之前,一定要理清楚思路,你可能会花很多时间在它上面,你要知道了,怎么写就变的反而简单了。

二:具体开发思路及主要代码

    我在博客的最下面附上了完整的代码,大家可以在Git上下载到它,你要也使用Git,就顺便给我个小星星吧 O(∩_∩)O哈哈~。。

  1》游戏界面的布局设计

    这个里面的Label 和 Button 就不多费口舌了,这不是我们的重点,看看这个效果我们也就一笔带过了吧!重点是我们使用的上面说的利用  这个知识画出来表格。它单看就是一个 N * M 的表格,在它里面就要运行我们的俄罗斯小方块,在下面的代码里面也会详细的说明它的制作。

    

 

      下面是我们绘制上面网格视图的方法,下面所有代码方法里面的有些参数是定义成全局变量的,大家可以下载完整版的代码去看看。在代码中也加了许多的注释,相信都能看的明白的。

// MARK: 绘制俄罗斯方库网格的方法    func creatcells(rows:Int,cols:Int,cellwidth:Int,cellHeight:Int) -> Void {                // 开始创建路径        CGContextBeginPath(CTX)        // 绘制横向网格对应的路径        for  i  in 0...TETRIS_Row {                        CGContextMoveToPoint(CTX, 0, CGFloat(i  *  CELL_Size))            CGContextAddLineToPoint(CTX, CGFloat(TETRIS_Cols * CELL_Size), CGFloat(i * CELL_Size))                    }        // 绘制纵向的网格对应路径        for  i  in 0...TETRIS_Cols {                        CGContextMoveToPoint(CTX, CGFloat(i  *  CELL_Size),0)            CGContextAddLineToPoint(CTX, CGFloat(i * CELL_Size), CGFloat(TETRIS_Row * CELL_Size))                    }        // 关闭        CGContextClosePath(CTX)                // 设置笔触颜色        CGContextSetStrokeColorWithColor(CTX, UIColor(red: 0.9 , green: 0.9 , blue: 0.9,alpha: 1).CGColor)        // 设置效线条粗细        CGContextSetLineWidth(CTX, CGFloat(STROKE_Width))        // 绘制线条        CGContextStrokePath(CTX)            }

   

    2》小游戏的数据模型

        1: 游戏的游戏界面是一个 N * M 的网格,每一张网格显示一张图片,但对于我们来说,我门就得用一个二维数组来定义,纪录每一块的行和列!来保存游戏的状态。我们在最开始把每一个小块的游状态都初始化为 0 ,看下面代码。

// 定义用于纪录方块游戏状态的二维数组    var tetris_status = [[Int]]()        // MARK初始化游戏状态    func initTetrisStatus() -> Void {                let tmpRow = Array.init(count: TETRIS_Cols, repeatedValue: NO_Block)        tetris_status  = Array.init(count: TETRIS_Row, repeatedValue: tmpRow)            }

       2: 游戏的过程中有一只处于“下落”状态的四个方块,这四个方块我们也会是要纪录,才可以做它的旋转、向左、向右等等的处理。我们就用一个数组包含着四个方块,那具体到这四个方块呢?我们就用一个结构体去体现你这四个方块它的 X、Y值和颜色。

struct Block {        var X:Int    var Y:Int    var Color:Int    var description:String {                return "Block[X=\(X),Y=\(Y),Color=\(Color)]"    }}

    3:在俄罗斯方块这个游戏中,你也肯定得知道有哪些方块的组合可以下落,这也是一个数据源!你也得定义好,在每次要下落的时候你就随机取出这个而数据源里面的数据,让它随机的出现下落。这些工作也就是你要在初始化上面要纪录的四个正在下落的方块数组的时候做的事了,下面是这些个组合的数据源。

// 几种可能的组合方块        self.blockArr = [                      // 第一种可能出现的组合 Z            [                Block(X:TETRIS_Cols/2 - 1,Y:0,Color:1),                Block(X:TETRIS_Cols/2,Y:0,Color:1),                Block(X:TETRIS_Cols/2,Y:1,Color:1),                Block(X:TETRIS_Cols/2 + 1,Y:1,Color:1)                        ],            // 第二种可能出现的组合 反Z            [                Block(X:TETRIS_Cols/2 + 1,Y:0,Color:2),                Block(X:TETRIS_Cols/2,Y:0,Color:2),                Block(X:TETRIS_Cols/2,Y:1,Color:2),                Block(X:TETRIS_Cols/2 - 1,Y:1,Color:2)                            ],            // 第三种可能出现的组合 田            [                Block(X:TETRIS_Cols/2 - 1,Y:0,Color:3),                Block(X:TETRIS_Cols/2,Y:0,Color:3),                Block(X:TETRIS_Cols/2 - 1,Y:1,Color:3),                Block(X:TETRIS_Cols/2 ,Y:1,Color:3)                                ],            // 第四种可能出现的组合 L            [                Block(X:TETRIS_Cols/2 - 1,Y:0,Color:4),                Block(X:TETRIS_Cols/2 - 1,Y:1,Color:4),                Block(X:TETRIS_Cols/2 - 1,Y:2,Color:4),                Block(X:TETRIS_Cols/2 ,Y:2,Color:4)                                ],            // 第五种可能出现的组合 J            [                Block(X:TETRIS_Cols/2,Y:0,Color:5),                Block(X:TETRIS_Cols/2,Y:1,Color:5),                Block(X:TETRIS_Cols/2,Y:2,Color:5),                Block(X:TETRIS_Cols/2 - 1,Y:2,Color:5)                                ],            // 第六种可能出现的组合 ——            [                Block(X:TETRIS_Cols/2,Y:0,Color:6),                Block(X:TETRIS_Cols/2,Y:1,Color:6),                Block(X:TETRIS_Cols/2,Y:2,Color:6),                Block(X:TETRIS_Cols/2,Y:3,Color:6)                            ],            // 第七种可能出现的组合 土缺一            [                Block(X:TETRIS_Cols/2,Y:0,Color:7),                Block(X:TETRIS_Cols/2-1,Y:1,Color:7),                Block(X:TETRIS_Cols/2,Y:1,Color:7),                Block(X:TETRIS_Cols/2 + 1,Y:1,Color:7)                                ],        ]

       随机取出下落

// 定义纪录 “正在下掉的四个方块” 位置    var currentFall = [Block]()    func initBlock() -> Void {                // 生成一个在 0 - blockArr.count  之间的随机数        let rand =  Int(arc4random()) % blockArr.count        // 随机取出 blockArr 数组中的某个元素为正在下掉的方块组合        currentFall = blockArr[rand]    }

 3》 游戏逻辑处理

    1:下落

   前面我们提到过有用数组纪录正在下落的四个方块的状态,我们梳理一下“下落”状态的逻辑关系。如果在下落的状态,你只需要把这四个正在下落的方块的 Y 值加 1 即可! 但是得注意什么情况下它不能再下落了。。

      (1):如果方块组合中任意一个方块已经到达了最底下就不能再下落了。

       (2) :如果方库组合中任意一个方块的下面有了方块就不能再下落了。

       下落的实现思路就是,如果有方块可以下落,那么就把方块组合原来所在位置的颜色清楚,然后把组合中的每一个方块的 Y 属性加1 ,最后把当前方块的所在位置加上相应的颜色,下面是思路实现的代码。

// MARK:控制方块组合向下移动    func movedown () -> Void {                // 定义能否向下掉落的 标签        var canDown = true                // 遍历每一块方块,判断它是否能向下掉落        for i in 0..
= TETRIS_Row - 1 { canDown = false break } // 第二种情况,如果他的下面有了方块,不能再下落 if tetris_status[currentFall[i].Y + 1][currentFall[i].X] != NO_Block { canDown = false break } } // 如果能向下掉落 if canDown { self.drawBlock()// for i in 0..

 

      里面的代理更新UI(及分数和速度)我们就不多说了,说说 drawBlock() 这个方法,它是来绘制了我们在所有的方块,相当于把我们的互数据模型给全都可视化;

//MARK: 绘制俄罗斯方块的状态    func drawBlock() -> Void {                for i in 0..

 2:判断这行是否已满    

    上面是让它下落了,里面有调用判断一行是否已满,其实这里的逻辑就是遍历每一行每一个方块,给你的每一行都加一个状态,这里是 true ,判断你该行的每一个方块的状态是不是初始化时候的 0  ,要是,那说明是缺方块的,这行没有满,跳出。。要是都不是,那就说明这行都满了。。就可以进行消除这行的后续操作了。增加积分,消除相应的行等,下面是它的代码。

// MARK: 判断是否有一行已满    func lineFull() -> Void{      // 遍历每一行        for i in 0..
= curSpeed * curSpeed * 500{ curSpeed += 1 // 代理更新当前速度 self.delegate.UpdateSpeed(curSpeed) curTimer?.invalidate() curTimer = NSTimer.scheduledTimerWithTimeInterval(BASE_Speed/Double(curSpeed), target: self, selector: #selector(self.movedown), userInfo: nil, repeats: true) } } // 把所有的整体下移一行 for var j = i; j < 0 ; j -= 1 { for k in 0..

 3.左移处理

   它的处理方式和上面的下落的逻辑是一样的,也就是两点,到了最左边和左边有了两类型的情况,代码如下。

//MARK: 定义左边移动的方法    func moveLeft () -> Void {                // 定义左边移动的标签        var canLeft = true        for i in 0..

 4.右移处理

   右边移动的处理情况几乎就和左边的完全相同了,见代码

// MARK: 定义右边移动的方法    func moveRight () -> Void {                // 能否右移动的标签        var canRight = true        for i in 0..
= TETRIS_Cols - 1 { canRight = false break } // 如果右边有方块,就不能再移动 if tetris_status[currentFall[i].Y][currentFall[i].X + 1] != NO_Block { canRight = false break } } // 如果能右边移动 if canRight { self.drawBlock() // 将香油移动的每个方块涂白色 for i in 0..

 5.旋转处理 

   旋转处理,就得用点数学知识了,你画一个坐标轴,试着把一个点顺时针或者逆时针旋转九十度,你再写出旋转后的坐标。其实清楚了这点也就OK了,我们是按逆时针旋转处理的,四个方块,就按照第三个作为它的旋转轴心。

// MARK: 定义旋转的方法    func rotate () -> Void {            // 定义是否能旋转的标签        var canRotate = true        for i in 0..
TETRIS_Cols - 1 || afterRotateY < 0 || afterRotateY > TETRIS_Row - 1 || tetris_status[afterRotateY][afterRotateX] != NO_Block { canRotate = false break } } } // 如果能旋转 if canRotate { self.drawBlock() for i in 0..

三:启动游戏

      做完了上面的工作,你就可以启动你的游戏了,你的做的工作就有下面这些;

    重置游戏积分,将积分设置为 0 

    重置下落的速度,也将它设置为0

    初始化俄罗斯方块的状态,将它们的值全都初始化为 0 

    生成一组在下落的方块组

    启动计时器,控制下落的方块

// MARK:开始游戏    func startGame()    {                self.curSpeed = 1        self.delegate.UpdateSpeed(self.curSpeed)                self.curScore = 0        self.delegate.UpdateScore(self.curScore)                // 初始化游戏状态        self.initTetrisStatus()                // 初始化四个正在下落的方块        self.initBlock()                // 定时器控制下落        curTimer = NSTimer.scheduledTimerWithTimeInterval(BASE_Speed/Double(curSpeed), target: self, selector: #selector(self.movedown), userInfo: nil, repeats: true)            }

 PS:一张游戏运行图片

 

四:写在开发后

       差不多到这里也就结束了,但里面有一个BUG,有些时候会发生一个数组的越界导致的崩溃,这个问题有时间在好好看一下,自己写的里面可能还有我不知道的问题,也没做大量的测试,感兴趣的朋友可以自己好好完善一下,比如试试暂停,重新开始这些功能的。。反正肯定还有写的不好的地方,有问题大家可以发消息随时交流!!

 

 

    写完了,说点无聊的,说说自己?,其实在大学的时候,我打死也不可能相信自己将来会走上编程这条路,一个大一连C语言都挂科不懂得人。现在想想真的就是着实蛋疼。要是那时候上帝给我说一句,你将来要会是一个开发软件的,我一定觉得是上帝疯了。可转眼工作也一年多了,慢慢的,我喜欢上了自己做的事,至少我自己觉得挺好的。工作第一,但也得给自己充充电,每天敲着代码 PS:还有加着班,但心里踏实。没有为碌碌无为,荒废一天又一天感到不安!难道有什么比你心里踏实更重要的么,当然你要是有鸿鹄之志,额~~~你还是得充电呀,,O(∩_∩)O哈哈~

   最后就是完整代码。。Git地址给大家。。。点击下载 

 

转载于:https://www.cnblogs.com/zhangxiaoxu/p/5482127.html

你可能感兴趣的文章
node启动时, listen EADDRINUSE 报错;
查看>>
杭电3466————DP之01背包(对状态转移方程的更新理解)
查看>>
kafka中的消费组
查看>>
python--注释
查看>>
前端资源链接 ...
查看>>
yum install ntp 报错:Error: Package: ntp-4.2.6p5-25.el7.centos.2.x86_64 (base)
查看>>
leetcode-Single Number-136
查看>>
CF715C Digit Tree
查看>>
二分法练习1
查看>>
QT 制作串口调试小助手----(小白篇)
查看>>
前端MVC实践之hellorocket——by张舒彤
查看>>
OptimalSolution(2)--二叉树问题(3)Path路径问题
查看>>
IPC 之 Messenger 的使用
查看>>
macos 下usb键盘问题.
查看>>
SQL函数学习(十六):STUFF()函数
查看>>
Apache Hadoop 和Hadoop生态圈
查看>>
Ctrl+Enter 选中文本提交
查看>>
android WIFI
查看>>
常用的匹配正则表达式和实例
查看>>
小组成员及其git链接
查看>>