VC++项目开发实验报告
课题名称:基于MFC的俄罗斯方块游戏设计
姓 名:
学 号:
系 别: 计算机学院
班 级:
专 业:
指导教师:
20##年4月 9日
目 录
一、课程设计的目的与要求.............................................................................................. 3
1.1课程设计的目的................................................................................................... 3
1.2课程设计的要求................................................................................................... 3
二、设计正文................................................................................................................... 3
2.1需求分析.............................................................................................................. 3
2.1.1 游戏需求................................................................................................... 3
2.2.2游戏界面需求:............................................................................................ 3
2.1.3游戏形状(方块)需求:.................................................................................. 3
2.2算法分析.............................................................................................................. 4
2.2.1程序流程图................................................................................................ 4
2.2.2定义方块的数据结构.................................................................................. 6
2.2.3 游戏设计分析............................................................................................ 7
三 系统设计..................................................................................................................... 8
3.1创建界面的主框架................................................................................................ 8
3.2正常流程的设计................................................................................................... 8
3.2.1定时制机制................................................................................................ 8
3.2.2定时处理.................................................................................................... 9
4.1底部到达的判断与销行的实现............................................................................ 10
4.2中断操作流程的实现.......................................................................................... 13
4.3变形的实现........................................................................................................ 14
4.4 游戏区域绘图的实现......................................................................................... 15
4.5 功能的完善....................................................................................................... 15
4.6 游戏演示........................................................................................................... 16
4.7主程序源程序清单.............................................................................................. 17
六、参考文献................................................................................................................. 32
用VC++实现俄罗斯方块的程序设计
一、课程设计的目的与要求
1.1课程设计的目的
综合运用所学知识,熟练掌握VC++程序设计的编码与MFC框架结构 。
1.2课程设计的要求
要求用VC++设计与实现俄罗斯方块游戏。要求包括系统的需求分析;系统总框图及每个模块的设计分析;MFC应用程序架构;框架的扩展;算法的设计与实现;游戏的内部实现;游戏区域绘图的实现;系统存在的问题及错误处理;列出所有定义的函数及说明;附上程序源代码。
二、设计正文
2.1需求分析
2.1.1 游戏需求
随机给出不同的形状(长条形、Z字形、反Z形、田字形、7字形、反7形、T字型)下落填充给定的区域,若填满一条便消掉,记分,当达到一定的分数时,过关,设置六关,每关方块下落的速度不同,若在游戏中各形状填满了给定区域,为输者。游戏功能要求如下:
2.2.2游戏界面需求:
良好的用户界面,有关数显示和分数显示。让方块在一定的区域内运动和变形,该区域用一种颜色表明,既用一种颜色作为背景,最好设为黑色。还需用另一种颜色把黑色围起来,宽度适中,要实现美感。
2.1.3游戏形状(方块)需求:
良好的方块形状设计,绘制七种常见的基本图形(长条形、Z字形、反Z形、田字形、7字形、反7形、T字型),各个方块要能实现它的变形,可设为顺时针或逆 时针变形,一般为逆时针。
2.2算法分析
2.2.1程序流程图
图2.2.3 程序运行调用图
2.2.2定义方块的数据结构
对于方块在某一瞬间的位置标识,我们采用一个4×2的小数组标识出来,即用4个存储单位空间存储当前下坠物的每一子块的位置,也就是说,用4个存储单位空间存储当前下坠物的每一子块的位置来对整个下坠物件的位置进行标识,而每个存储空间的大小就是一个典的坐标值(x,y),而每个方块按照从左到右的方式进行编号,并且在编号过程中对于同一列的方块实行从上到下进行编号[2] 。
图3.1 方块编号
ActiveStatus[0][0]和ActiveStatus[0][1]则是第0号方块的横坐标x和纵坐标y ;ActiveStatus[2][0]和ActiveStatus[2][1]则是第2号方块的横坐标x和纵坐标y。
2.2.3 游戏设计分析
有前面的功能描述可知,我先虚拟出俄罗斯方块游戏的类对象,并抽象出核心的数据属性和操作方法等,然后再作细化,最后将整个虚拟类的外壳脱掉,再移植到视图类中去,其实现如下:
CRectGameView : public CView
{
//内部存取数据结构
int m_stateMap[MAX_ROW][MAX_COL];
//初始化操作
GameInitnal(); //游戏的初始化
//用于判断数据相关状态的操作
IsLeftLimit(); //下坠物件是否可向左移动
IsRightLitmit(); //
IsBottom(); //是否已经到达了底部
IsGameEnd(); //是否游戏已经结束
//方块物件下坠过程中的操作
RectChange(); //下坠物件变形
RectDown(); //下坠物件正常下落
RectArrow(); //下坠物件方向移动(左,右,下加速)
//状态控制操作
GameStart(); //游戏开始
GamePause(); //游戏暂停
GameEnd(); //游戏结束
}
通过上面的代码可以看出,在虚拟类中抽象出了核心的内部数据和一些基本的操作函数。对于操作函数,可以把它们分为内部实现的基本核心操作(如判断操作)以及明显提供给外部使用的整体模块外部操作(如状态控制操作)。而内部的基本操作又可以分为判断操作和执行操作这样两种类型[3]。
三 系统设计
3.1创建界面的主框架
首先建立一个项目工程,名为skyblue_Rect,并在AppWizard的架构选择过程中选择单文档方式,其他保持默认选项。其项目的架构类视图信息如图所示:
在构架类视图中是MFC基本架构组合:App(应用程序)类、Document(文档)类、View(视图)类、Frame(框架)类和用于提示关于作者的对话框CAboutDlg类,至于COptionDlg类是用作俄罗斯方块参数选择的对话框类对象。
3.2正常流程的设计
3.2.1定时制机制
从分析游戏的特性可以知道,定时器的产生与生效应该在游戏开始的时候,而在游戏暂停或者游戏结束时则将已经设定的定时器失效/销亡(对于暂停的情况,使它销亡,当游戏从暂停状态又进入游戏状态时候,则重新创建一个定时器并激活它的运作),所以分别在游戏的开始函数、暂停函数已经结束函数中实现定时器的激活与去激活工作。这里,先在资源编辑器菜单资源里面添加三个菜单选项,分别是游戏的“开始”、“暂停”、和“结束”,然后利用ClassWizard直接在视图类对象Cskyblue_RectView中为它们添加空白的处理函数,具体如表2所示。
3.2.1菜单选项功能对应表
3.2.2定时处理
经过定时器的设置后,这里通过利用ClassWizard跳到定时器到时候的处理函数OnTimer()去实现,当固定时间片间隔到达后,先检测当前下坠物是否已经到达了底部,不是则进行RectDown()下坠物向下移动一个单位的操作,是则到底后产生一个新的“下一个下坠物”,并代替旧的,将原先旧的“下一个下坠物”用作当前激活状态下正在使用的下坠物,并对使用后的一些状态进行检测:是否马上到达底部,使则进行销行操作
;是否在到达底部的同时到达游戏区域的顶部,从而判定游戏是否因违规而结束。
图 3.2.2装载方块
视图类创建了m_icurrentStatus和m_inextStatus两个成员变量来记录下坠物的类型,共有七种形状,并从7种方块中随机抽取图形。而m_currentRect除了记录下坠物的类型外,还需记录其当前的变形状态,最多用两位表示,第1位用作类型标识(1~7),第2位用作同种类型的不同表现方式,最多有4种状态(1~4)。
在产生新的下一个下坠物前,需要先将当前状态物的记录和旧的下一个下坠物保存下来,然后用随机函数Random()产生一个最大值不大于指定值的随机正整数,将这个新生成的正整数用作新的“下一个下坠物”的形状值。
四、系统实施
4.1底部到达的判断与销行的实现
图3.2.3 处理方块到达图
将新的下坠物放置到游戏区域中去,这时可能出现马上到达底部的情况,因此需要对它进行判断,如果是到达底部,则进行销行处理,并且修改相应的数据状态。而判断是否已经到达了底部,可以通过当前下坠物件所对应的接触面的方块位置为被占用状态(MAP_STATE_NOT_EMPTY=1)来确定,利用数组InterFace[74][4]记录1~7种下坠物的1~4种形态的接触面信息。
统计分数:在消行处理里面有一个专门用来统计消行数的变量,然后根据变量的值决定分数的多少,程序统计分数是:消一行得100分,同时消2行得400分,销掉x行,则分数为:x*(x*100)。如果总分数达到过关条件就过关,改变游戏速度,游戏初始化,开启新的一关,然后再加载方块。没有达到过关分数或者没有满行,则加载下一个方块继续游戏。
4.2中断操作流程的实现
(1) 处理键盘事件
关于按键命令消息的响应,可以通过对WM_KEYDOWN消息的处理函数进行截获并重写来实现,下面是对该处理函数OnKeyDown()的重写。
// 功能:处理用户的输入,方块的左,右移,加速及变形
void CSkyblue_RectView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch(nChar)
{
case VK_LEFT:
RectArrow(LEFT);
break;
case VK_RIGHT:
RectArrow(RIGHT);
break;
case VK_UP:
RectChange();
break;
case VK_DOWN:
RectArrow(DOWN);
break;
}
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
4.3变形的实现
当按下向上键时,将会执行方块变化事件(change())。常见的方块有7种(长条形、Z字形、反Z形、田字形、7字形、反7形、T字型),所有图形都是用两个一维数组来统计它的横坐标和纵坐标,每个方块有4种不同的变化形状。
例计算变形后的小方块的坐标和显示的状态值
//变形后位置在数组中的存放顺序仍需遵循先左后右,在同一列中先上后下
xx1=x1; xx2=x2; xx3=x3; xx4=x4; yy1=y1; yy2=y2; yy3=y3; yy4=y4;
switch(m_currentRect)
{
case 1:
xx1=x1+1; yy1=y1-1; xx3=x3-1; yy3=y3+1; xx4=x4-2; yy4=y4+2;
m_lscurrentRect = 11;
break;
case 11:
xx1=x1-1; yy1=y1+1; xx3=x3+1; yy3=y3-1; xx4=x4+2; yy4=y4-2;
m_lscurrentRect = 1;
break;
……
//省略部分为同类实现的变形后小方块坐标的计算代码
case 73:
xx2=x2+1; yy2=y2-1; xx3=x3+2; yy3=y3-2; xx4=x4-1; yy4=y4-1;
m_lscurrentRect = 7;
break;
}
4.4 游戏区域绘图的实现
首先将外部位图文件rect.bmp中的位图动态导入(映射)到内存位图里面,根据游戏区域中的二维数组GameStatus[MAX_ROW][MAX_COL]中的内部数据将所有数据状态中为被占用状态MAP_STATE_NOT_EMPTY的小方块区域用指定的小方块图样类型来填充,然后将已经绘制好的游戏区域图像一次性的拷贝到与屏幕关联的设备环境中,从而达到屏幕的显示。
4.5 功能的完善
为了使得游戏功能更加健全,另外为用户提供了一些附加功能,如表4.5.1所示。
表 4.5.1 附加功能描述列表
先将这些目标功能通过资源编辑器在主菜单条进行添加,将前面已有的菜单选项补全,再通过ClassWizard添加对应的响应处理函数。其最终效果如图
1
图 4..5.2 游戏设置
游戏是用来给大家娱乐的,所以要能在使用的过程中给大家带来快乐,消除大家的疲劳,所以我在游戏中添加了漂亮的场景和动听的背景音乐,设置了个性化的工具栏快捷键,激发大家的娱乐激情。
4.6 游戏演示
游戏主界面如图所示。
图4..6.1 俄罗斯方块游戏运行主界面
4.7主程序源程序清单
CSkyblue_RectView::CSkyblue_RectView()
{
//第一次开始游戏
m_bFistPlay = TRUE;
//缺省为不是游戏暂停状态
m_bGamePaush = FALSE;
//缺省为不插放背景音乐
m_bMusic = FALSE;
//缺省为画网格线
m_bDrawGrid = TRUE;
//总分值清零
m_iPerformance = 0;
//测试值:为12行,10列
m_iRow = 12;
m_iCol = 10;
//左上角X,Y坐标
m_iStartX = 10;
m_iStartY = 10;
//缺省级别为3级
m_iLevel = 2;
//第一种样式
m_iBlockSytle = 0;
//缺省方块大小为m_iLarge个象素
m_iLarge = 30;
//缺省游戏是结束的
m_bGameEnd = TRUE;
int i,j;
//赋初值
for (i=0;i<100;i++)
for (j=0;j<100;j++)
GameStatus[i][j]=0;
//各种形状方块的接触面数据,参见设计书的接触面表格,
//5.判断游戏是否已结束: 碰了底,且第1行有小方块
if (m_isBottom)
for (i=0;i<m_iCol;i++)
if (GameStatus[0][i])
{
KillTimer(1);
AfxMessageBox("游戏已结束!");
for (j=0;j<m_iRow;j++)
for (k=0;k<m_iCol;k++)
GameStatus[j][k]=0;
Invalidate(FALSE);
m_bGameEnd = TRUE;
break;
}
}
else //当前方块下降
{
RectDown();
}
CView::OnTimer(nIDEvent);
}
// 函数:产生一个最大值不大于指定值的随机正整数(Random)
// 参数:MaxNumber : 随机数的上限
// 返回值: 产生的随机数
int CSkyblue_RectView::Random(int MaxNumber)
{
//布下随机种子
srand( (unsigned)time( NULL ) );
//产生随机数
int random = rand() % MaxNumber;
//保证非0
if(random == 0 ) random++;
return random;
}
}
}
//内部函数:刷新当前的区域
void CSkyblue_RectView::InvalidateCurrent()
{
int i;
for (i=0;i<4;i++)
{
CRect rect(m_iStartX+ActiveStatus[i][1]*m_iLarge,
m_iStartY+ActiveStatus[i][0]*m_iLarge,
m_iStartX+(ActiveStatus[i][1]+1)*m_iLarge+5,
m_iStartY+(ActiveStatus[i][0]+1)*m_iLarge);
//InvalidateRect(&rect);
Invalidate(FALSE);
}
}
// 内部函数:当前方块下降加速,左移,右移
void CSkyblue_RectView::RectArrow(int m_Type)
{
//获取当前下坠物4个小方块的位置坐标
int x1,x2,x3,x4,y1,y2,y3,y4;
x1 = ActiveStatus[0][0];
x2 = ActiveStatus[1][0];
x3 = ActiveStatus[2][0];
x4 = ActiveStatus[3][0];
y1 = ActiveStatus[0][1];
y2 = ActiveStatus[1][1];
y3 = ActiveStatus[2][1];
y4 = ActiveStatus[3][1];
//对不同的移动命令指示进行分类实现
switch(m_Type)
{
case LEFT:
//对每种不同的移动命令指示特性作相应的可移动分析
if ( (ActiveStatus[0][1]>0) && IsLeftLimit() && !m_isBottom)
{
//清原来的方块
GameStatus[x1][y1]=MAP_STATE_EMPTY;
GameStatus[x2][y2]=MAP_STATE_EMPTY;
GameStatus[x3][y3]=MAP_STATE_EMPTY;
GameStatus[x4][y4]=MAP_STATE_EMPTY;
//添加新的移动后数据状态
ActiveStatus[0][1] -= 1;
ActiveStatus[1][1] -= 1;
ActiveStatus[2][1] -= 1;
ActiveStatus[3][1] -= 1;
GameStatus[x1][y1-1]=MAP_STATE_NOT_EMPTY;
GameStatus[x2][y2-1]=MAP_STATE_NOT_EMPTY;
GameStatus[x3][y3-1]=MAP_STATE_NOT_EMPTY;
GameStatus[x4][y4-1]=MAP_STATE_NOT_EMPTY;
InvalidateCurrent();
}
break;
case RIGHT:
if ( (ActiveStatus[3][1]< m_iCol-1) && IsRightLitmit() && !m_isBottom)
{
//清原来的方块
GameStatus[x1][y1]=MAP_STATE_EMPTY;
GameStatus[x2][y2]=MAP_STATE_EMPTY;
GameStatus[x3][y3]=MAP_STATE_EMPTY;
GameStatus[x4][y4]=MAP_STATE_EMPTY;
//添加新的移动后数据状态
ActiveStatus[0][1] += 1;
ActiveStatus[1][1] += 1;
ActiveStatus[2][1] += 1;
ActiveStatus[3][1] += 1;
GameStatus[x1][y1+1]=MAP_STATE_NOT_EMPTY;
GameStatus[x2][y2+1]=MAP_STATE_NOT_EMPTY;
GameStatus[x3][y3+1]=MAP_STATE_NOT_EMPTY;
GameStatus[x4][y4+1]=MAP_STATE_NOT_EMPTY;
InvalidateCurrent();
}
break;
case DOWN:
RectDown();
break;
}
}
// 内部函数:方块的变形
void CSkyblue_RectView::RectChange()
{
//先预先变形,然后判断变形后的方块是否有空间,如有足够空间,则进行实际变形,否则不变
int xx1,xx2,xx3,xx4,yy1,yy2,yy3,yy4;
int m_lscurrentRect;
CString lsStr;
int x1,x2,x3,x4,y1,y2,y3,y4;
x1 = ActiveStatus[0][0];
x2 = ActiveStatus[1][0];
x3 = ActiveStatus[2][0];
x4 = ActiveStatus[3][0];
y1 = ActiveStatus[0][1];
y2 = ActiveStatus[1][1];
y3 = ActiveStatus[2][1];
y4 = ActiveStatus[3][1];
//变形后位置在数组中的存放顺序仍需遵循先左后右,在同一列中先上后下
xx1=x1; xx2=x2; xx3=x3; xx4=x4; yy1=y1; yy2=y2; yy3=y3; yy4=y4;
switch(m_currentRect)
{
case 1:
xx1=x1+1; yy1=y1-1; xx3=x3-1; yy3=y3+1; xx4=x4-2; yy4=y4+2;
m_lscurrentRect = 11;
break;
case 11:
xx1=x1-1; yy1=y1+1; xx3=x3+1; yy3=y3-1; xx4=x4+2; yy4=y4-2;
m_lscurrentRect = 1;
break;
……
//省略部分为同类实现的变形后小方块坐标计算代码
case 73:
xx2=x2+1; yy2=y2-1; xx3=x3+2; yy3=y3-2; xx4=x4-1; yy4=y4-1;
m_lscurrentRect = 7;
break;
}
//改变形状代码
m_currentRect = m_lscurrentRect;
}
else
{//恢复原来状态
GameStatus[x1][y1] = MAP_STATE_NOT_EMPTY;
GameStatus[x2][y2] = MAP_STATE_NOT_EMPTY;
GameStatus[x3][y3] = MAP_STATE_NOT_EMPTY;
GameStatus[x4][y4] = MAP_STATE_NOT_EMPTY;
}
//判断是否已到底
IsBottom();
}
//绘图设备环境的初始化
void CSkyblue_RectView::DcEnvInitial(void)
{
if(m_bFistPlay)
{
m_bFistPlay = FALSE;
//黑色的黑笔
m_pBlackPen = new CPen(PS_SOLID,1,BLACK);
//画刷
m_pGrayBrush = new CBrush(RGB(66,66,66));
m_pBlackBrush = new CBrush(BLACK);
}
}
void CSkyblue_RectView::DCEnvClear(void)
{
//设备环境
m_memDC.DeleteDC();
m_memRectDC.DeleteDC();
//位图资源
DeleteObject(m_memBmp);
DeleteObject(m_hMemRectBmp);
delete(m_pBlackPen);
delete(m_pGrayBrush);
delete(m_pBlackBrush);
}
void CSkyblue_RectView::DrawGame(CDC *pDC)
{
int i,j;
//选用黑色画刷,绘制整个游戏所在窗口的背景
pDC -> SelectObject(m_pBlackBrush);
CRect rect;
GetClientRect(&rect);
pDC -> Rectangle(rect);
//选用灰色画刷,绘制游戏区域的背景
pDC -> SelectObject(m_pGrayBrush);
pDC -> Rectangle(m_iStartY ,m_iStartX, m_iStartY + 301, m_iStartX + 360);
pDC->SelectObject(m_pBlackPen);
//画网格线
if (m_bDrawGrid)
{
//画横线
for (i=0;i<m_iRow;i++)
{
pDC->MoveTo(m_iStartY, m_iStartX + i*m_iLarge);
pDC->LineTo(m_iStartY+300, m_iStartX +i*m_iLarge);
}
//画竖线
for (i=0;i<m_iCol;i++)
{
pDC->MoveTo(m_iStartY+i*m_iLarge, m_iStartX);
pDC->LineTo(m_iStartY+i*m_iLarge, m_iStartX+360);
}
}
int x,y,nW,nH;
//小方块的绘制
for (i=0;i<m_iRow;i++)
for (j=0;j<m_iCol;j++)
{
if (GameStatus[i][j]==MAP_STATE_NOT_EMPTY)
{
//在游戏区域中状态为被占用状态的区域绘制小方块
x = m_iStartY+j*m_iLarge +2;
y = m_iStartX+i*m_iLarge +2;
nW = m_iLarge-4;
nH = m_iLarge-4;
pDC->BitBlt(x,y,nW,nH,&m_memRectDC,m_iBlockSytle*30,0,SRCCOPY);
}
}
//画下一次将要出现的方块,用于提示用户
if (!m_bGameEnd)
{
pDC -> SetBkColor(BLACK);
pDC -> SetTextColor(WHITE);
pDC -> TextOut(m_iStartY+320, m_iStartX,"下一个方块:");
int x,y,nW,nH;
for (UINT k=0;k<4;k++)
{
i = NextStatus[k][0];
j = NextStatus[k][1];
x = m_iStartY+j*30 +2+320;
y = m_iStartX+i*30 +2+30;
nW = m_iLarge-4;
nH = m_iLarge-4;
pDC->BitBlt(x,y,nW,nH,&m_memRectDC,m_iBlockSytle*30,0,SRCCOPY);
}
}
}
// 功能:承担所有绘制屏幕工作
void CSkyblue_RectView::OnDraw(CDC* pDC)
{
DcEnvInitial();
DrawGame(&m_memDC); //在内存位图的游戏区域绘制
pDC->BitBlt(0,0,m_nWidth,m_nHeight,&m_memDC,0,0,SRCCOPY);
}
五、课程设计总结或结论
在本次课程设计中,我从指导老师颜宏文身上学到了很多东西。老师认真负责的工作态度,严谨的治学精神和深厚的理论水平都使我收益匪浅。她无论在理论上还是在实践中,都给与我很大的帮助,使我得到不少的提高这对于我以后的工作和学习都有一种巨大的帮助,感她耐心的辅导。另外,在游戏开发过程中谢中科老师也给于我们很大的帮助,帮助解决了不少的难点,使得游戏能及时开发完成,还有所有的同学同样给与我不少帮助,这里一并表示感谢。
六、参考文献
[1]Ben Sawyer. 游戏软件设计与开发指南[M].北京:人民邮电出版社,1998.8~46
[2]钦科技. Visual C++游戏设计[M]. 北京:科海电子出版社,2003.1~211
[3]坂本千寻.Visual C++专业游戏程序设计[M]. 北京:中国铁道出版社,2004