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-11-11 03:10:11

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.0

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

Swift游戏开发之俄罗斯方块:No.4 滴答作响的时钟机制

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

Swift游戏开发之俄罗斯方块:No.2 准备工作

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

Swift游戏开发之俄罗斯方块:No.9 添加点击和移动事件

本节内容过后,我们的程序运行起来将是这样的: 我们一步一步来,首先添加点击事件: 接触过iOS开发的应该对这些步骤不陌生,我们要添加UITapGestureDetector到view里面. 打开Main.storyboard,界面应该类似于下面这样 在右下角通过搜索找到 Tap Gesture Recognizer 然后把它拖入到GameViewControllerScene 里面 接下来打开Assistant Editor,界面看起来是这样的: 按住ctrl将Tap Gesture Recog

Swift游戏开发之俄罗斯方块:No.3 二维数组

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

Swift游戏开发之俄罗斯方块:No.8 游戏规则

每个游戏都有它自己的规则,我们的俄罗斯方块的规则很明显,shape落到最底端时就停止下落,然后下一个shape开始往下落:当任一一个点挡住下落的shape时,整个shape就认为是已经到底了:当一行充满所有blocks时,这行消除,然后所有的往下落一行等等等等. 那么我们现在就开始制定我们的游戏规则,本节过后,我们的程序运行起来是这样子的: 我们首先从自定义的协议(protocol)开始,之前我们已经接触过两个swift自带的协议,hashable和printable.我们首先在swiftris

Swift游戏开发之俄罗斯方块:No.5 Block Party

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

Swift游戏开发之俄罗斯方块:No.10 最后一步!美化你的程序

好了,本节是我们这系列教程的最后一篇.经过之前的代码磨练,到了最后一步,基本已经没有什么太多的知识点和难点了.最后,我们的程序看起来是非常酷炫的: 从图里可以看到,我们添加了分数和关卡,然后添加了消除方块时的动画效果,其实还有声音.有没有小激动啊,我们这就开始. 首先确保你的属性和图中保持一致. 我们从右下角中找到view,然后把他拖入屏幕中,然后在属性中把它的背景设置成default也就是透明,然后按照图示的坐标和大小设置好 接下来我们拖入进来一个image view,因为这个image是在v