俄罗斯方块c#版.doc_第1页
俄罗斯方块c#版.doc_第2页
俄罗斯方块c#版.doc_第3页
俄罗斯方块c#版.doc_第4页
俄罗斯方块c#版.doc_第5页
已阅读5页,还剩34页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

三天教你做俄罗斯方块小花朵 2010-07-12序言大学学C#的时候做了一个俄罗斯方块,发现挺多新手都想牛刀小试一把,我就重写了一遍,并写了这份文档教程,如果你理解快的话,三天就能做出来你的俄罗斯方块了。先看一下我的俄罗斯方块吧,游戏规则估计不用多说了,我的俄罗斯方块的特色是有美女脱衣表演哦,每升一级,美女就脱一件衣服哦!另外,你还可以自己设定各个参数,包括游戏窗口的大小,按键,背景音乐,甚至自定义砖块样式。第一部分:基础知识1. 了解认识GDI+GDI+的技术是建立在GDI上的。GDI+提供了一个抽象层,隐藏了不同视频卡之间的区别,这样就可以调用windows AIP函数完成指定的任务了GDI+由.NET基类集组成,这些基类可用于在屏幕上完成定制绘图,能把合适的指令发送到图形设备的驱动程序上,确保在监视器屏幕上显示正确的输出,这里的输出包括打印到硬拷贝中。表1-1列出了GDI+基类的主要命名空间表 1-1命名空间说明System.Drawing包含与一类绘图功能有关的大多数累、结构、枚举、委托System.Drawing.Drawing2D为大多数高级2D和矢量绘图操作提供了支持,包括消除锯齿、几何转换和图形路径System.Drawing.Imaging帮助处理图像(位图、Gif文件等)的各种类System.Drawing.Printing把打印机或打印预览窗口作为输出设备时使用的类System.Drawing.Design一些预定义的对话框、属性表和其他用户界面元素,与在设计期间扩展用户界面相关System.Drawing.Text与字体和字体系列执行高级操作的类在GDI+中,设备环境(DC)包装在.NET基类System.Drawing.Graphics中。大多数绘图工作都是调用Graphics的实例来完成的。实际上,因为Graphics类负责处理大多数绘图操作,所以GDI+中很少有操作不涉及到Graphics实例。理解如何处理这个对象是理解如何使用GDI+在现实设备上绘图的关键。2. 绘制图形下面用一个小示例来说明如何在应用程序的窗口中绘图(文章所有的示例都在Visual Studio 2005 中建立为C#的Windows应用程序)。启动VS2005,创建一个windows应用程序的项目,语言是C#,名字为Tetris(俄罗斯方块),然后切换到代码视图,在构造函数的最下面追加如下代码:运行程序,我们期待的结果是在窗体上出现一个蓝色的矩形和一个红色的椭圆,但是实际运行结果呢?什么都没有显示,这是什么原因呢?原因就是在构造函数里执行画图代码的时候,窗口还没有显示出来,也就是说,还没有可以提供绘图的地方,所以,我们要看到期待中的蓝色矩形和红色椭圆,就必须在窗口显示出来以后再执行才能看到效果。知道了原因我们也就知道解决方案,回到设计视图,添加Form_Shown事件,通过下面的提示我们知道这个事件发生在窗口第一次显示的时候。然后我们把代码移动到Form_Shown事件中,再次运行一下程序,接下了就是见证奇迹的时刻了。期待中的蓝色矩形和红色椭圆如期出现了,矩形的坐标(0,0),大小(50,50)。椭圆的坐标(0,50),大小(80,50)。需要提醒一下的是如果是椭圆,则是外接矩形的坐标。这里坐标(x,y)表示从窗口的客户区域左上角开始向右的x个像素,向下y个像素这些是现实出来的图形的左上角的坐标。外面注意到椭圆的顶部和矩形的下边有轻度的重叠,这与代码中给出的坐标有点不同,这是因为windows在重叠的区域放置了巨星和椭圆的线条。在默认情况下,windows视图把图形边框所在的线条放到中心位置但这并不是总能做到的,因为线条是以像素为单位来绘制的,但每个图形的边框理论上位于两个像素之间。结果1个像素宽的线条就会正好位于图形顶边和左边的立面,而在右边和底边的外面。这样,从严格意义上讲,相邻的边框就会有1个像素的重叠。由于我们制定的线条宽度比较大,因此重叠区域也就比较大了。一般来说,可以设定Pen.Alignment属性来改变默认的操作方式,但这里使用默认的操作就足够了。接下了我们会发现一个问题,如果把这个窗口的绘图部分用别的窗体遮住,或者移动到屏幕的外边或者最小化,再恢复它,会发现绘制好的图形就不见了或者部分不见了。这是怎么回事?这个就要从windows处理屏幕数据的方式来说起了。如果窗口的一部分被隐藏了,windows通常会立刻删除与其中显示的内容相关的所有信息。这是必须的,否则存储屏幕数据的内存量就会是个天文数字。按照1024x768像素,24位彩色模式,屏幕上的每个点(像素)就会占据3个字节(byte),整个屏幕需要2.25MB的显存来存储这些数据。下面考虑一种最糟糕的情况:屏幕上有20个窗口,都是最大化状态,windows就需要45MB的显存来存储,如果是32位的彩色模式,或者分辨率更大点,则消耗更多的显存,很显然,windows不能这样管理用户界面。在窗口的某一部分消失时,那些像素也就丢失了。因为windows释放了保存这些像素的显存。但要注意,窗口的一部分被隐藏了,当它检测到窗口不再被隐藏时,就请求拥有该窗口的应用程序重新绘制该部分的内容。这个规则有一些例外窗口的一部分被挡住的时间比较短(如,菜单的拉出,临时挡住了下面的窗口)。但一般情况下,如果窗口的一部分被挡住,应用程序就需要在以后重新绘制那部分。换句话说,windows只需要花费一个屏幕的显存就可以处理N多个窗口同时打开的情况,这是我的理解。这就可以解释我们的程序为什么出问题了。我们的代码只是在第一次显示的时候才执行,并且只执行一次,不能在以后需要的时候自动重新绘制图形。备注:windows的标准控件非常专业,能够在windows需要的时候自动重新绘制他们自己。所以这就是在使用时不需要担心实际绘图过程的原因之一。3. 使用OnPaint()绘制图形上面的解释可以让你觉得挺复杂的,但实际上并非如此,要让应用程序在需要的时候绘制自身是非常简单的。windows会利用Paint事件通知应用程序完成一些重新绘制的请求。有趣的是,Form类已经执行了这个事件的处理,因此不需要再添加处理代码了。我们添加OnPaint()事件,根据注释,我们知道这个事件会在控件需要重新绘制的时候发生。然后我们把代码再移动到这个事件里。注意,我们修改了获取Graphics的方式,PaintEventArgs里包含一个Graphics实例,所以我们就不需要再调用CreateGraphics()来创建了。运行一下看看结果,发现bug消失了,我们已经成功的实现是在窗口上绘制我们需要的图形了,有了这些基础,我们就可以正式开工了。备注: 这一部分主要摘抄自C#高级编程(第四版),第25章第一节第二部分:详细设计1. 实现原理了解了基础知识以后我们就可以思考游戏的实现了,其实这个游戏实现的原理非常简单,就是不断的在窗口上画砖块,清砖块。注意这里的清砖块其实就是用背景颜色把某个区域给填充而已,本质还是绘制。具体的设计思路如下:l 通过timer定时执行某个操作来改变活动砖块的坐标,并更新窗口的绘图;l 通过键盘事件改变活动砖块的坐标以及形状,并更新窗口的绘图;l 每次更新都要检测是否有填满的行,或者是否游戏结束等等;l 针对消除的行数更新游戏得分,等级等信息2. 抽象建模了解了基础知识以后就可以动手设计我们的俄罗斯方块的现实了,根据面向对象的设计思想,我们第一时间想到的就是抽象建模,提取的最明显的类,模块。既然如此我们就开始吧。我们知道游戏的规则是把一个一个不规则的砖块尽可能的填满到槽里,我们就从这些不规则的砖块入手吧。砖块的样式很多,但是根据我们对游戏的了解,最长(宽)的砖块也不找过5个,根据这个限制,我们定义一个5x5大小的一个容器来存储各个砖块的样式。标准的俄罗斯方块的砖块包括下面7中样式。当然,将来如果我们游戏设计的好,可以自己定义很多样式。到这里,我们就已经提取出了一个砖块的类了,我们新建一个类,类名为Brick。接下来,由于如何才能控制产生这些砖块呢?首先要知道总共有多少种砖块信息(样式,颜色),基于这个需要,我们再抽象出一个砖块模板类,用于描述一个砖块的样式,颜色等信息,这类就叫做BrickTemplate吧。然后为了能够统一管理这些BrickTemplate信息,我们需要设计一个类,用来维护这些砖块模板,以方便我们将来随时增加或者获取某个砖块模板,我们就定义一个TemplateArray类吧。然后呢,还差一个用来产生砖块的工厂,所以再定义一个BrickFactory类,用于生产砖块。到这里,我们对砖块的处理流程已基本清晰了,规整一下如表2-1下:表2-1类说明BrickTemplate 砖块的信息描述类,相当于一个模具,用来制作BrickTemplateArray管理砖块信息的管理类,用于维护BrickTemplateBrickFactory用于根据BrickTemplate产生一个实际的BrickBrick砖块实体类,用于描述一个实际砖块最后,我们还需要设计一个游戏画布类,负责实现游戏的展示以及逻辑处理。这个类是整个游戏的核心,就起名叫做GamePalette。至此,整个类级别的设计就基本结束,还有需要的等到时候用时再实现,方便期间,我们把这些逻辑模块放到一个文件夹中,解决方案的物理结构如下3. 逻辑实现接下了我们逐步讲解各个类的具体实现,先从Brick类入手,上面一节我们说过了,这个类是一个具体的砖块类,这个类应具有以下属性和方法。这里详细介绍一下我们的设计思路,我们以一个小砖块元素为单位,每个砖块有5x5=25个坐标,其中只有一部分是有数据的,也就是有效的,这些有效的坐标也就形成了砖块的样式。比如,下图这个砖块样式的坐标数组就应该为(1,2),(1,3),(2,2),(2,3)。但是这个砖块有多大呢,由于我们是以一个小砖块元素为单位,所以,每个小砖块元素的坐标点对应为画布上一个矩形区域,也就是说每个坐标点的是一个RectPix大小的块。再考虑到砖块是活动的,所以砖块的实际坐标得加上在画布上的偏移,也就是说某个点的坐标(x, y)在画布上的实际坐标为(X+x, Y+y)。但是这样计算起来比较麻烦,所以我们采用坐标系平移的方法,把坐标中心放置在砖块的中心,即x轴向上,y轴向左偏移2个单位,这样就变成了上图所示,这样做的目的是为了方便计算。接下来,我们开始具体讲解每个函数的实现: 构造函数唯一需要说明一下的是初始偏移我们设定为(2,2),这样就可以完整显示整个砖块了l 顺时针旋转结合下图可以更好的理解转换规则,同样适用于逆时针旋转: 画砖块到画布这个函数很好理解,把每个点放大为一个矩形区域,然后以砖块颜色填充这个区域。这个函数需要注意的地方是我们在使用Graphics对象的时候要先lock,这样防止同一时间其他地方也在使用这个对象,从而引发异常。 擦除画布上的砖块可见,其实擦除就是用背景色把砖块颜色覆盖而已,本质上和上面画砖块是一样的。 点放大为区域这个函数的功能前面介绍过了,就不多说了,这里的width和height要-2是为了留出中间的间隔缝隙为画网格保留的。至此,砖块类的基本功能已经实现了,我们现在可以测试一下这个砖块类是否能够正确运行,我们回到Form1的设计视图,修改如下:注意,这里我们不在Form上画,而是在PictureBox上画,其实只要有Paint功能的标准控件都可以来画,我们就选择picturebox控件,使用这个控件的好处还有很多,因为这个控件本事就是为显示图形图像设计的,所以将来如果需要可以提供更多的操作,比如我们将来想把背景换成一个图片之类的,用picturebox效率比较高,不会闪烁。实现代码如下所示:这里需要注意一下,因为我们修改砖块的属性以后,必须通知picturebox再次重绘砖块才能看到效果,这也是我们为什么调用Refresh的原因,这个方法会导致picturebox的重绘。运行一下,会发现我们期待中的砖块已经显示出来了,同样可以测试其他功能是否能够正常工作好了,至此,我们对砖块的初步设计已成型,可以说砖块已经具有了灵魂,可以动了,接下来我们把其他配套的类来实现一下。砖块样式信息模板类是生产砖块的模具,很容易想到,它应该记录要生产砖块的样式,以及颜色。但是如何记录砖块的样式呢?这个可以用任何你觉得可以描述的方法,我们前面说过用一个5x5大小的二维数组可以存储,然后5x5的二维数组可以演变为一个25的一维数组,比如我们可以用“0000000110001000010000000”来描述一个砖块样式,这时你可能迷惑了,这咋看出来是个砖块的样式?不要急,我们说过这个一维数组是从二维数组演变过来的,我再把它还原看看(每5个换行),就变成了下面的格式,如果我们忽略0,只看1,那么这个形状不就是我们刚才测试的那个砖块的形状么? 明白了这个,我们就来实现这个砖块样式信息的模板类吧,我们用String类型存储这个0101的样式编码,由于这个模板类很简单,也很容易理解,就不多讲了,直接看代码就可以看懂了。砖块的模具也实现了,根据前面的介绍,标准的游戏有7种砖块样式,也就是说游戏运行过程中,需要有7个这样的模具来随机调用生产砖块。那接下了就看看如何管理这些模具,也就是TemplateArray类的实现,这个也很简单,为了使程序更简单,我们只提供了添加(add)和清空(clear)功能,至于编辑和删除如果有兴趣可以自己添加。代码如下:然后就差生产砖块的机器了,这个机器的工作原理就是随机的从TemplateArray中获取一个砖块模具BrickTemplate,然后按照这个模具生产出相应的砖块Brick。前面我们把这个类定义为BrickFactory,那就看看这个转窑怎么实现的吧。这个类的功能很简单,就一个函数,而且也不复杂,代码注释也很详细,就不再详细解释了,自己体会一下吧。接下来我们测试一下这些配套设施能否正确的产生砖块,我们回到Form1的设计界面,添加一个button,用于随机更换砖块,并修改代码如下:代码中添加了一个BrickFactory对象,用于随机生成砖块,构造函数里清楚的介绍了如何初始化相应的数据,至于那些0101编码,估计你现在已经知道他们的含义了吧?对,就是对应那7中标准的砖块编码,至于颜色,根据自己的喜好,设定你喜欢的颜色就行了。测试结果如下4. 游戏规则的实现如果你能够成功实现前面所介绍的功能,你已经完成50%了,而且这50%含金量很高,足够你开发一些其他的游戏了,比如简单点的贪吃蛇,或者经典的吃豆子等等小游戏了。不过我们还是要先完成我们俄罗斯方块的游戏再说,不能一知半解的就结束。我们的砖块虽然已经有了灵魂,但是还是不收束缚的灵魂,所以我们要做的就是控制它,不能让它无限制的移动,该落下的就落下,该停止的就停止。先看看这个游戏画布类应该具有哪些属性:常量区COLORS:用于在消除满行砖块时闪烁该行,增加游戏可视化效果TIME_SPANS:用于设定不同等级下的砖块的下落速度SCORE_SPANS:用于消除满行砖块时计算得分变量区m_BrickFactory:很容易理解,用于在游戏过程中生产砖块m_Width,m_Height:游戏画布水平和垂直格子的数目m_CoorArray:用于记录游戏画布上各个格子的颜色m_BgColor,m_GridColor:画布背景色和网格颜色m_Size:单元格的大小m_Level,m_Score,m_GameOver,m_ShowGrid,m_Pause,m_Ready这些变量容易理解,不再多说。m_MainPalette,m_NextPalette:这两个分别对应游戏中主画面和next的画面m_TimerBrick:用于实时更新游戏状态,比如砖块下落,检查是否得分等等timer的用法不知道你熟悉不熟悉,timer对象可以定时触发一个事件,而且不影响这段时间内其他部分的代码执行(timer的用法待会再说。如果你了解多线程),这个地方也可以换成多线程的方式。接下来看看构造函数,很简单,就不多说了。再看看属性访问器,也很简单,不说了接下来就是有含金量的函数了,这些函数共同形成了游戏规则以及游戏控制,先看看公有函数包含哪些这些函数是对外可见的,也就是通过这些接口我们来控制是玩游戏。我们着重介绍几个函数的作用 MoveDown这个函数最重要的一点就是中间的for循环判断是否能够向下移动,这一点是很重要的,也是游戏的核心规则。同样的方法处理MoveLeft,MoveRight,就不多说了。 DropDown这个函数很简单,也很容易理解,就是快速的调用MoveDown而已。 DeasilRotate这个函数的关键地方也是要判断能不能满足旋转的条件,由于代码不复杂,结合前面介绍过砖块旋转的算法,所以,就不多说了,同样的方法验证ContraRotate。 PaintPalette从注释上可以看出这个函数是用于绘制主画布上的游戏图形,至于其中调用的函数(PaintGridLine,PaintBricks),我们待会再介绍,暂时只需要先在代码里放一个空函数就行了。 PaintNext这个函数用于绘制下一个砖块到Next画布上,就先叫它“小地图”吧,代码很简单不再多解释了。 Start这函数用于开始游戏,开始的时候需要生成两个砖块,一个放在主画布上作为活动砖块,一个显示在“小地图”上,作为下一个。为了方便调试,我们没有启动计时器,等将来调试差不多的时候再取消注释即可,注意别忘了啊!或者不注释也可以调试。这里稍微介绍一下timer的使用,timer总共分3中,一种在System.Timers命名空间下,一种在System.Threading命名空间下,一种在System.Windows.Form命名空间下,它们各有各的特点,我们这里用的是System.Timers命名空间下的Timer,timer初始化的时候可以使用一个整形数字初始化触发间隔。我们这里就是用当前等级对应的速度来初始化,然后设定触发事件,也就是Elapsed事件,ElapsedEventHanlder的参数是一个委托,或者说是一个函数指针,用来绑定处理函数,我们这里设定的是OnBrickTimedEvent。至于AutoReset属性很容易理解,就是重置计时器。最后调用Start方法开始计时或者Stop方法停止计时。 Close这个函数用于析构不需要的资源,尤其是最后两句,否则重新开始游戏的时候,会有bug。剩下的暂停Pause和继续Resume函数就不说了,代码就一句,就是设定m_Pause变量的值而已。接下来我们来测试一下这些基本功能是否能够正确运行。重命名Form1为FormMain,并回到设计视图,修改设计视图如下,两个picturebox对应主画布和“小地图”(注意设定picturebox的边框样式为FixedSingle,另外大小按照图上所示,后面会介绍为什么设定大小)。4个button对应四个方向按键。然后回到代码视图,添加代码如下构造函数中初始化了m_GamePalette对象,并启动游戏。通过实参,我们可以计算出,游戏水平的宽度=水平格子数x格子大小 = 15 * 20 = 300,这也就是前面为什么设定游戏画布pbMainPalette的宽度为300的原因了,同样高度,以及小地图的大小也是这个道理。现在我们就可以测试是否砖块可以受控制了。运行一下,看看效果:同过点击四个按钮,看看主画布上的砖块能不能按照我们是要求旋转或者移动。至此这个砖块已经收到我们的控制了,剩下的就是处理落下的砖块了。接下来我们开始研究GamePalette类的其他私有函数,看看他们是怎么实现的。 OnBrickTimedEvent这个函数我们前面提到过了,这个是m_TimerBrick触发时的处理函数,这个函数在正常情况下会首先调用MoveDown函数,使得活动砖块自动下落一格,如果不能下移则执行CheckAndOverBrick函数,检查游戏状态,比如游戏结束,或者消除满行砖块,游戏得分等等. PaintGridLine这个函数很好理解,画m_Width-2 条竖线和m_Height-2条横线,实现网格效果。之所以每个方向都少画2条,是因为我们设定了Picturebox的边框样式了,也就是说边框不用画了,就少了两条,当然多画两条也无所谓。 PaintBricks这个函数用来绘制已经存在的砖块,其实原理和画砖块是一样的,应该没有什么看不懂的吧,如果看不懂,可以写个小程序试试加深理解。 CheckAndOverBrick这个函数用于检查,更新游戏状态,这个函数执行的前提是不能下移(MoveDown返回false)的时候,所以此时首先需要更新画布已有砖块的信息,就是把当前这个砖块各个位置的颜色设定到画布的m_CoorArray里,然后检查m_CoorArray并消除满行,具体方法见CheckAndDelFullRow函数。接着原先的下一个砖块变成了当前活动砖块,再检查这个砖块能否放在画布上,也就是检查游戏结束,检查的原理注释上有。如果游戏没有结束,那么生成下一个砖块显示在“小地图”上。l CheckAndDelFullRow这个函数有点长,不过逻辑倒是不太复杂,通过注释可以看出来,就是判断当前砖块所在的那些行是不是存在满行的条件以及行的数目,然后删除这些行,并将上面的砖块下移,根据删除行的数目追加相应的得分以及修改对应的级别和速度。中间闪烁效果的那个for循环是可选的,可以不要。 InitRandomBrick这是一个可选函数,写不写都行,函数的作用是根据初始等级随机先在画布上放置一些零碎的砖块,用于增加游戏可玩性。 PaintGameOver这个函数用于打印一个GAME OVER的结束信息,也是可选函数,不多讲了。至此整个游戏的核心类GamePalette类已经全部实现

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论