C#开发俄罗斯方块小游戏.docx

上传人:b****8 文档编号:29675675 上传时间:2023-07-26 格式:DOCX 页数:44 大小:2.21MB
下载 相关 举报
C#开发俄罗斯方块小游戏.docx_第1页
第1页 / 共44页
C#开发俄罗斯方块小游戏.docx_第2页
第2页 / 共44页
C#开发俄罗斯方块小游戏.docx_第3页
第3页 / 共44页
C#开发俄罗斯方块小游戏.docx_第4页
第4页 / 共44页
C#开发俄罗斯方块小游戏.docx_第5页
第5页 / 共44页
点击查看更多>>
下载资源
资源描述

C#开发俄罗斯方块小游戏.docx

《C#开发俄罗斯方块小游戏.docx》由会员分享,可在线阅读,更多相关《C#开发俄罗斯方块小游戏.docx(44页珍藏版)》请在冰豆网上搜索。

C#开发俄罗斯方块小游戏.docx

C#开发俄罗斯方块小游戏

三天教你做俄罗斯方块

小花朵2010-07-12

序言

大学学C#的时候做了一个俄罗斯方块,发现挺多新手都想牛刀小试一把,我就重写了一遍,并写了这份文档教程,如果你理解快的话,三天就能做出来你的俄罗斯方块了。

先看一下我的俄罗斯方块吧,游戏规则估计不用多说了,另外,你还可以自己设定各个参数,包括游戏窗口的大小,按键,背景音乐,甚至自定义砖块样式。

第一部分:

基础知识

1.了解认识GDI+

GDI+的技术是建立在GDI上的。

GDI+提供了一个抽象层,隐藏了不同视频卡之间的区别,这样就可以调用windowsAIP函数完成指定的任务了

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.绘制图形

下面用一个小示例来说明如何在应用程序的窗口中绘图(文章所有的示例都在VisualStudio2005中建立为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.实现原理

了解了基础知识以后我们就可以思考游戏的实现了,其实这个游戏实现的原理非常简单,就是不断的在窗口上画砖块,清砖块。

注意这里的清砖块其实就是用背景颜色把某个区域给填充而已,本质还是绘制。

具体的设计思路如下:

●通过timer定时执行某个操作来改变活动砖块的坐标,并更新窗口的绘图;

●通过键盘事件改变活动砖块的坐标以及形状,并更新窗口的绘图;

●每次更新都要检测是否有填满的行,或者是否游戏结束等等;

●针对消除的行数更新游戏得分,等级等信息

2.抽象建模

了解了基础知识以后就可以动手设计我们的俄罗斯方块的现实了,根据面向对象的设计思想,我们第一时间想到的就是抽象建模,提取的最明显的类,模块。

既然如此我们就开始吧。

我们知道游戏的规则是把一个一个不规则的砖块尽可能的填满到槽里,我们就从这些不规则的砖块入手吧。

砖块的样式很多,但是根据我们对游戏的了解,最长(宽)的砖块也不找过5个,根据这个限制,我们定义一个5x5大小的一个容器来存储各个砖块的样式。

标准的俄罗斯方块的砖块包括下面7中样式。

当然,将来如果我们游戏设计的好,可以自己定义很多样式。

到这里,我们就已经提取出了一个砖块的类了,我们新建一个类,类名为Brick。

接下来,由于如何才能控制产生这些砖块呢?

首先要知道总共有多少种砖块信息(样式,颜色),基于这个需要,我们再抽象出一个砖块模板类,用于描述一个砖块的样式,颜色等信息,这类就叫做BrickTemplate吧。

然后为了能够统一管理这些BrickTemplate信息,我们需要设计一个类,用来维护这些砖块模板,以方便我们将来随时增加或者获取某个砖块模板,我们就定义一个TemplateArray类吧。

然后呢,还差一个用来产生砖块的工厂,所以再定义一个BrickFactory类,用于生产砖块。

到这里,我们对砖块的处理流程已基本清晰了,规整一下如表2-1下:

表2-1

说明

BrickTemplate

砖块的信息描述类,相当于一个模具,用来制作Brick

TemplateArray

管理砖块信息的管理类,用于维护BrickTemplate

BrickFactory

用于根据BrickTemplate产生一个实际的Brick

Brick

砖块实体类,用于描述一个实际砖块

最后,我们还需要设计一个游戏画布类,负责实现游戏的展示以及逻辑处理。

这个类是整个游戏的核心,就起名叫做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),这样就可以完整显示整个砖块了

●顺时针旋转

结合下图可以更好的理解转换规则,同样适用于逆时针旋转:

∙画砖块到画布

这个函数很好理解,把每个点放大为一个矩形区域,然后以砖块颜色填充这个区域。

这个函数需要注意的地方是我们在使用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函数。

接着原先的下一个砖块变成了当前活动砖块,再检查这个砖块能否放在画布上,也就是检查游戏结束,检查的原理注释上有。

如果游戏没有结束,那么生成下一个砖块显示在“小地图”上。

●CheckAndDelFullRow

这个函数有点长,不过逻辑倒是不太复杂,通过注释可以看出来,就是判断当前砖块所在的那些行是不是存在满行的条件以及行的数目,然后删除这些行,并将上面的砖块下移,根据删除行的数目追加相应的得分以及修改对应的级别和速度。

中间闪烁效果的那个for循环是可选的,可以不要。

∙InitRandomBrick

这是一个可选函数,写不写都行,函数的作用是根据初始等级随机先在画布上放置一些零碎的砖块,用于增加游戏可玩性。

∙PaintGameOver

这个函数用于打印一个"GAMEOVER"的结束信息,也是可选函数,不多讲了。

至此整个游戏的核心类GamePalette类已经全部实现了,我们的游戏也已经完成80%了,接下来就是前台UI的设计了。

在前面的测试中,我们已经大体实现了前台的布局了,剩下要改的东西不多,你可以根据自己的喜好,设计出多种多样的游戏界面,下面介绍一下我的设计吧,虽然丑了点,但还是中规中矩的。

主界面就先这样设计吧,没有复杂的控件,在前面的测试窗体基础上改动一下就差不多了。

这里需要提醒一下,窗体的KeyPreview属性必须设定true,这样窗体才能接受键盘事件。

接着添加键盘按下事件。

并在事件里调用对应的功能即可,这个窗体代码如下:

当按下F2时,如果当前游戏存在,那么需要先关闭游戏,至于为什么要关闭,你可以测试一下不关闭的后果。

然后定义7中标准的砖块样式模板用来构造砖块,然后初始化一个新的游戏。

调用开始函数,就可以玩了。

当按下其他键时,必须首先判断游戏存在才行,因为他们是

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1