一步一步教你用Swift开发俄罗斯方块:No.6 变形记

The object of art is to give life shape

- William Shakespeare

上一章节我们介绍了这个游戏最基本的组成元素,block,那么接下来我们就开始更为清晰和形象地了解如果做出来俄罗斯方块的shape吧。是的,就是这样的形状:

首先我们来新建一个类,名字叫做Shape;到这里新建一个类的步骤应该很熟练了吧。 我们来修改下面的代码

在代码的第一部分,我们先建立了一个枚举类型,这个enumeration用来辅助我们定义我们的shape的4个方向,无论我们的形状处于什么角度,我们都将把它修正成4个方向: 0,90,180和270,形象点,就是这样的:

在这部分代码里面,还有两个函数,一个是随机生成函数(random),一个是旋转函数(rotate)

random函数我们之前有接触过,而且代码看起来也很清楚,就不多说了。而rotate函数其实也不难,传入两个参数,一个是我们上面建立好的枚举类型
Orientation,一个是bool型的变量,表示是顺时针还是逆时针旋转,如果是顺时针,枚举类型就+1, 如果是逆时针就-1

当然,这样很容一出现4和-1的情况,如果是270°(3)再顺时针转一圈就+1变成了4,我们要手动把它修复成0度;同理-1的情况修复成270°,然后我们把这个经过旋转后的角度返回出来。

好了,定义好了角度,我们就来写shape类了,准备好了么?

提醒:可能在你输入了下面的代码以后会有部分错误,不要着急,我们会在后面修补它们

在执行printable的 computed property里面,图片里面没有显示完全,这里重新打一遍吧:

var description:String {
         return "\(color) block facing \(orientation): \(blocks[FirstBlockIdx]), \(blocks[SecondBlockIdx]), \(blocks[ThirdBlockIdx]), \(blocks[FourthBlockIdx])"
     }

shape类其实是一个父类,我们游戏中用到的所有shapes都将继承自这个类,所以NumShapeTypes被定义为7,因为我们一共有7种形状。而对于每一个shape,我们都可以用4个block来组建成,所以我们把4个block分别加上index

#1#2
介绍了新的swift 特性,你一定会很感兴趣。这里我们定义了两个 computed properties,然后把它们的返回值设置为空,这就好比是C++里面的纯虚函数,必须在它的子类中进行重新定义。我们一会将会看到它。

#1中,blockRowColumnPositions 定义了一个computed 的字典,字典是被一对方括弧【】定义的:字典中的内容都是成对出现的,一个是key(关键字),对应的是value(值)

关于更多swift dictionary 的细节可以点击这里

字典我们知道的,可以当我第一次看到Array<(columnDiff:Int, rowDiff: Int)>
的时候,还是特别不能理解这又是个啥?

这个在swift里面其实还是一个传统的数组,我们从Array上面也能理解,只不过里面是一个叫做tuple的类型。 tuple其实就是为了简化开发者的工作量,让我们可以直接定义一个返回multiple variable的结构。

总体说来,这个blockRowColumnPositions字典,里面定义的是一个shape的4个方向(这就是为什么key是orientation)时,block的位置(一个shape是由多个block组成的,所以是一个数组;而位置需要坐标来定义,所以需要tuple)。

如果大家在这里还是不太明白,不用着急,一会看到子类的定义后,再回来看这个定义就能够明白了。

关于更多tuple的知识,点击这里

#3 我们定义了一个完整的computed property,我们需要返回处于底部的blocks,你可以想象下你的shape落到底层,或者和别的shape堆叠起来时的样子,这也是为什么我们需要这样的定义。

#4 这里我们用到了 reduce<S : Sequence, U>(sequence:
S, initial: U, combine: (U, S.GeneratorType.Element) -> U) -> U
  方式 去hash我们的整个blocks数组,和之前一样,我们用$0表示第一个参数,$1,表示第二个参数,用他们的亦或值来唯一的定位他们

#5 这里我们遇到了swift的一个新的关键字 : convenience。 其实就相当于构造函数的重载 , 之前的那个init在swift里面叫做 designated init,也就是必须要有的,而为什么要叫 convenience 就如它的字面意思一样,是一个便利用户的init,在这里面必须调用之前的
designated
init,否则会出错。其实就是在convenience init里面做了一些定制化的操作,例如在我们的程序里面,构造了一个随机颜色,随机方向的shape。

关于更多convenient 的知识,点击这里

或者有篇中文的blog介绍的也不错,可以点击这里

接下来,让我们来修复里面的一些bug吧:

#1 我们定义了一个final func意味着这个函数不能被子类重写,而且这个initializeBlocks只能被shape类及它的子类所调用。

#2
上去的if语句其实相当于这样:

我们注意到里面还有个符号
..<
其实就是 i >=0 && i< blockRowColumnTranslations.count ,  而如果是 ...
就表示    0 <=  i <= count了。

好了,我们的shape父类就定义好了,让我们来定义它的子类吧:

我们首先来定义一个方块的shape吧:

和新建shape类的步骤一样,我们新建一个名为 SquareShape的类

我们可以在注释掉的内容里面看懂,其实一个方块的shape,就是4个block堆积起来的,仿佛你画一个草稿,4个方块,以此是0,1,2,3 ,我们只需要补充一下在父类里面的两个“纯虚函数”
blockRowColumnPositionsbottomBlocksForOrientations

因为方块无论你怎样旋转,它看起来都是不变的,所以对于4个方向,我们的0,1,2,3号block其实不需要变换位置,坐标都是固定的。

接下来就是TShape,LineShape,SShape,ZShape,LShape和JShape总共7种shape了,建议大家可以新建一个文件夹,然后把这些文件都放进去,这样看起来整洁一点:

这部分的代码确实比较繁琐,如果大家可以试着自己写一写,当然,也可以直接用我们提供给大家的

TShape.swift

class TShape: Shape{
    /*
        orientation 0
        *  |0|
        |1||2||3|

        orientation 90
        *  |1|
           |2||0|
           |3|

        orientation 180
        *                    or             *
        |3||2||1|                           |1||2||3|
           |0|                                 |0|

        orientation 270
        *  |3|               or             *    |1|
        |0||2|                                |0||2|
           |1|                                   |3|

        * marks the row/column indicator for the shape
    */
    override var blockRowColumnPositions: [Orientation: Array<(columnDiff:Int, rowDiff: Int)>]{
        return [
            Orientation.Zero : [(1,0),(0,1),(1,1),(2,1)],
            Orientation.Ninety : [(2,1),(1,0),(1,1),(1,2)],
            Orientation.OneEighty : [(1,2),(2,1),(1,1),(0,1)],
            Orientation.TwoSeventy : [(0,1),(1,2),(1,1),(1,0)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>]{
        return [
            Orientation.Zero : [blocks[SecondBlockIdx],blocks[ThirdBlockIdx],blocks[FourthBlockIdx]],
            Orientation.Ninety : [blocks[FirstBlockIdx],blocks[FourthBlockIdx]],
            Orientation.OneEighty : [blocks[FirstBlockIdx],blocks[SecondBlockIdx],blocks[FourthBlockIdx]],
            Orientation.TwoSeventy : [blocks[FirstBlockIdx],blocks[SecondBlockIdx]]
        ]
    }

}

这里我把它的程序中 180和270°的位置换一下,我的是完全按照顺时针的顺序转下去时每个block的位置计算的,而原始教材里面的代码会出现的一个情况是,旋转的时候会出现类似跳帧的情况,你会看到左上角的方块直接到右下角了,大家可以按照两种不同的位置试一下。

LineShape.swift

class LineShape:Shape {
    /*
        Orientations 0 and 180:

            | 0?|
            | 1 |
            | 2 |
            | 3 |

        Orientations 90 and 270:

        | 0 | 1?| 2 | 3 |

    ? marks the row/column indicator for the shape

    */

    // Hinges about the second block

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(0, 0), (0, 1), (0, 2), (0, 3)],
            Orientation.Ninety:     [(-1,0), (0, 0), (1, 0), (2, 0)],
            Orientation.OneEighty:  [(0, 0), (0, 1), (0, 2), (0, 3)],
            Orientation.TwoSeventy: [(-1,0), (0, 0), (1, 0), (2, 0)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[FourthBlockIdx]],
            Orientation.Ninety:     blocks,
            Orientation.OneEighty:  [blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: blocks
        ]
    }
}

LShape.swift

class LShape:Shape {
    /*

    Orientation 0

        | 0?|
        | 1 |
        | 2 | 3 |

    Orientation 90

          ?
    | 2 | 1 | 0 |
    | 3 |

    Orientation 180

    | 3 | 2?|
        | 1 |
        | 0 |

    Orientation 270

          ? | 3 |
    | 0 | 1 | 2 |

    ? marks the row/column indicator for the shape

    Pivots about `1`

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [ (0, 0), (0, 1),  (0, 2),  (1, 2)],
            Orientation.Ninety:     [ (1, 1), (0, 1),  (-1,1), (-1, 2)],
            Orientation.OneEighty:  [ (0, 2), (0, 1),  (0, 0),  (-1,0)],
            Orientation.TwoSeventy: [ (-1,1), (0, 1),  (1, 1),   (1,0)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.OneEighty:  [blocks[FirstBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[ThirdBlockIdx]]
        ]
    }
}

JShape.swift

class JShape:Shape {
    /*

    Orientation 0

      ? | 0 |
        | 1 |
    | 3 | 2 |

    Orientation 90

    | 3?|
    | 2 | 1 | 0 |

    Orientation 180

    | 2?| 3 |
    | 1 |
    | 0 |

    Orientation 270

    | 0?| 1 | 2 |
            | 3 |

    ? marks the row/column indicator for the shape

    Pivots about `1`

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(1, 0), (1, 1),  (1, 2),  (0, 2)],
            Orientation.Ninety:     [(2, 1), (1, 1),  (0, 1),  (0, 0)],
            Orientation.OneEighty:  [(0, 2), (0, 1),  (0, 0),  (1, 0)],
            Orientation.TwoSeventy: [(0, 0), (1, 0),  (2, 0),  (2, 1)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[ThirdBlockIdx]],
            Orientation.OneEighty:  [blocks[FirstBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[FourthBlockIdx]]
        ]
    }
}

SShape.swift

class SShape:Shape {
    /*

    Orientation 0

    | 0?|
    | 1 | 2 |
        | 3 |

    Orientation 90

      ? | 1 | 0 |
    | 3 | 2 |

    Orientation 180

    | 0?|
    | 1 | 2 |
        | 3 |

    Orientation 270

      ? | 1 | 0 |
    | 3 | 2 |

    ? marks the row/column indicator for the shape

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(0, 0), (0, 1), (1, 1), (1, 2)],
            Orientation.Ninety:     [(2, 0), (1, 0), (1, 1), (0, 1)],
            Orientation.OneEighty:  [(0, 0), (0, 1), (1, 1), (1, 2)],
            Orientation.TwoSeventy: [(2, 0), (1, 0), (1, 1), (0, 1)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.OneEighty:  [blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]]
        ]
    }
}

ZShape.swift

class ZShape:Shape {
    /*

    Orientation 0

      ? | 0 |
    | 2 | 1 |
    | 3 |

    Orientation 90

    | 0 | 1?|
        | 2 | 3 |

    Orientation 180

      ? | 0 |
    | 2 | 1 |
    | 3 |

    Orientation 270

    | 0 | 1?|
        | 2 | 3 |

    ? marks the row/column indicator for the shape

    */

    override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
        return [
            Orientation.Zero:       [(1, 0), (1, 1), (0, 1), (0, 2)],
            Orientation.Ninety:     [(-1,0), (0, 0), (0, 1), (1, 1)],
            Orientation.OneEighty:  [(1, 0), (1, 1), (0, 1), (0, 2)],
            Orientation.TwoSeventy: [(-1,0), (0, 0), (0, 1), (1, 1)]
        ]
    }

    override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
        return [
            Orientation.Zero:       [blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.Ninety:     [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
            Orientation.OneEighty:  [blocks[SecondBlockIdx], blocks[FourthBlockIdx]],
            Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]]
        ]
    }
}
时间: 2024-10-25 14:22:54

一步一步教你用Swift开发俄罗斯方块:No.6 变形记的相关文章

一步一步教你用Swift开发俄罗斯方块:No.1 建立你的第一个Swift游戏工程

原文地址:https://www.bloc.io/tutorials/swiftris-build-your-first-ios-game-with-swift#!/chapters/677 好了,我们正式开始我们的swift游戏开发! 首先,需要新建一个工程,熟悉iOS开发的童鞋应该对这个步骤不会陌生.我们还是一步一步来吧: 这里有两种途径建立全新的工程,你可以: 如果你的Mac没有运行Xcode,请打开它,然后在欢迎页面点击Create a new Xcode project 这里我还是要重

一步一步教你用Swift开发俄罗斯方块:No.7 下落机制

上一章节我们完成了shape的建立,现在游戏里面的元素(blocks,shapes)都已经完成了,背景也搭好了(array2D),让我们开始制定游戏规则吧.首先就是需要让我们的shape掉下来,还记得我们刚开始的时候每个600毫秒要刷新一下屏幕呢?那会还有一个closure我们都不太明白是干嘛用的,马上就知道了. 好了,今天章节过后,你的程序运行起来应该是这样的: 让我们来修改代码吧,这次要修改的代码比较多,而且没有上一章节那样重复的工作.不用太过担心,我们一步一步来: 在#1, 函数在执行时会

一步一步教你用Swift开发俄罗斯方块:No.2 准备工作

如果你已经成功建好了工程,运行之后你会发现是个小飞机的程序,如果我没记错的: 这个程序是spin-the-bottle:Space Edition,但是可惜的是,我们并不需要这些东西,我们需要一个干净的模板,所以,我们需要首先清理下战场,然后把我们需要用到的一些资源放进来. 打开项目导航栏,如下图中圆圈内的图标,或者点击? + 1: 右键点击GameScene.sks,选择delete 当系统询问的时候,选择move to trash 接下来点击image.xcassets,然后删除spaces

一步一步教你用Swift开发俄罗斯方块:No.3 二维数组

数组是一个很有用的数据结构,很多程序都建立在数组之上,我很少看到哪个应用程序不用到数组的. 而我们的俄罗斯方块显然也需要数组,而且是更为特殊的  二维数组. 为什么是二维数组呢?其实我们的整个游戏区域,可以看做是一个二维数组区域,就像这样: 我们的每一个block都占据在这样一个20x10的区域之内,20行,10列的一个200个block的二维数组让我们可以根据(x,y)坐标来确定block的位置,而这些blocks其实就是组成我们的L形,或者Z形 等等不同但我们都熟知的俄罗斯方块: swift

一步一步教你用Swift开发俄罗斯方块:No.4 滴答作响的时钟机制

为什么标题要叫做"滴答作响的时钟机制"呢? 想必我们大家都玩过俄罗斯方块,那些不同形状的东西,就是哪些不同形状,你懂的,会随着游戏级别的提高而下降的越来越快.是的 ,我们也要模仿那样,做出我们自己的时钟机制. 我们可以看到SKScene里面有一个函数 update(currentTime: CFTimeInterval). 这个函数被没一帧所调用.帧,frame, 是什么? 你可以理解一帧就是一副静态的图片,如果很多图片在很短的时间内连续播放,就成了动画.当你的眼睛开始去预知每一帧图像

一步一步教你用Swift开发俄罗斯方块:No.0

花了一周时间,按照Swift的开发教程,各种查阅资料,各种google,总算把俄罗斯方块游戏写完了,也想写个系列教学blog:因为虽然看着教程不长,但是对于从零学Swift的我,中间还是遇到了很多困难,好多bug不知道是怎么出来的,都是经过很纠结的过程才一一克服掉的,所以,这篇中文版的系列教学,也算是个为想学swift而又无从下手的童鞋们图个方便吧. 先上一个最终版本,基本效果就是这样,添加了动画效果和音效等等,其实还有很多事情可以做,只要照着这系列的blog进行下去,相信大家最终都创造出有着自

一步一步教你用Swift开发俄罗斯方块:No.5 Block Party

我实在想不出怎么才能起一个好听点的中文名字,还是用原教程中的名字,block party 吧 如果前面的几篇教程你觉得很简单,那么是时候开始加深难度了. 在俄罗斯方块游戏中,我们的主体就是那些形状,而每一个形状都是由不同的块组成的.所以,我们需要建立一个基础类,block,用来为我们更上层的显示打基础. 首先,按照建立二维数组array2D的步骤建立一个新的class,起名叫做Block 按照下面修改你的block类 可能你已经猜到了,这不是我们block 类的全部内容. 这部分只是定义了一个e

一步一步学习Swift之(一):关于swift与开发环境配置

一.什么是Swift? 1.Swift 是一种新的编程语言,用于编写 iOS 和 OS X 应用. 2.Swift 结合了 C 和 Objective-C 的优点并且不受 C 兼容性的限制. 3.Swift 采用安全的编程模式并添加了很多新特性,这将使编程更简单,更灵活,也更有趣. 4.Swift 是基于成熟而且倍受喜爱的 Cocoa 和 Cocoa Touch 框架,它的降临将重新定义软件开发. 5.Swift 是编写 iOS 和 OS X 应用的极佳手段,并将伴随着新的特性和功能持续演进.

教你一步一步部署.net免费空间OpenShift系列之一------帐号注册和验证

前几天有博友发布了一篇文章<一键部署mono 免费空间支持ASP.NET MVC 再也不担心伙食费换空间了>,支持MVC3和域名绑定,觉得不错,于是自己实践了一下,发现自己实际遇到的问题真不少,而且网上的关于此空间的帖子要么千篇一律,要么语焉不详,现总结为图文教程系列 帐号注册和验证 打开https://www.openshift.com/products/pricing,出现三种选择,前2种是免费的,建议选择第二个. 点击Create one跳转到创建用户界面 看到如下信息,填写邮箱和密码,