您的位置:首页 > 其它

VC/MFC 俄罗斯方块双人版(基于MFC单文档)

2017-01-05 15:58 253 查看
游戏最终界面如图:



目录:

题目要求

功能需求

总体设计
系统模块

系统业务处理流程

详细设计
砖块预显示

分数统计

砖块移动和游戏暂停的实现

游戏难度选择

播放背景音乐

游戏设置与帮助

静态文本超链接颜色和鼠标样式

双缓冲机制

背景位图的插入

字体颜色大小以及字体背景的删掉

砖块三维化

工具栏菜单栏状态栏的隐藏最大化按钮的禁用窗口大小的设定

俄罗斯方块双人版的实现

位图按钮的创建

测试与实现
整个游戏运行界面图

游戏设置与帮助页面图

总结

题目要求

参考VC++程序设计实验指导书,将上次编写的dos版双人俄罗斯方块改写成VC界面版。

功能需求

①实现双人俄罗斯方块

②隐藏菜单栏、工具栏、状态栏

③实现难度可以选择

④实现下一个砖块预测功能

⑤实现总分统计功能,和每步消除所得分数显示

⑥实现下、左、右、旋转、暂停功能

⑦实现背景音乐播放功能

⑧添加游戏帮助菜单

⑨实现砖块三维化

附加:实现双缓冲避免屏幕闪烁。设置窗口大小,禁用最大化按钮,禁止鼠标拖动改变窗口大小。

总体设计

系统模块



系统业务处理流程



详细设计

砖块预显示:

砖块预显示的原理就是在第一次生成砖块的时候,一次生成2个砖块。然后将第2个砖块预显示,第1个砖块掉下来。接着将预显示的砖块掉下来,新随机生成的砖块预显示。这样的话,我们用flag1标记是不是第一次产生砖块。

//预显示砖块生成部分代码:
if (1 == flag1)//1
{
bType1 = (rand() % NUM_BRICK_TYPES) + 1;//rand()%7:随机产生0-6的数字。
iOrient1 = (unsigned int) (rand() % 4);//随机状态0-3
flag1 = 0;
}

brickType = bType1;//rand()%7:随机产生0-6的数字。
initOrientation = iOrient1;//随机状态0-3

if (0 == flag1)//2
{
bType1 = (rand() % NUM_BRICK_TYPES) + 1;//rand()%7:随机产生0-6的数字。
iOrient1 = (unsigned int) (rand() % 4);//随机状态0-3

if (bType1 == 1)
activeBrickY1 = new CIBrick;  //动态多态性的体现
else if (bType1 == 2)
activeBrickY1 = new CLBrick;
else if (bType1 == 3)
activeBrickY1 = new CSBrick;
//...省略4,5,6,7
activeBrickY1->setColour((unsigned char)bType1);//设置砖块颜色
activeBrickY1->putAtTop(iOrient1, binWidth/2);//置顶
binY1->getImage(outputImageY1); //将固定块拷贝到临时数组
activeBrickY1->operator>>(outputImageY1); //将刚产生的加入
}


分数统计:

这里实现了,显示每次消除所得的分数和游戏总共所得的分数。由于,我们有一个返回消除了多少行的函数removeFullLines(),所以我们将这个返回值乘以积分规则里面的值,就实现了显示每次消除所得的分数。然后游戏总分就是累加每次得到的分数值。因为砖块最多就是消除4行,所以这里用个switch()语句来实现。m_num2是一个成员变量,保存的就是removeFullLines()的返回值。

//分数统计部分代码:
switch(m_num2)
{
case 0:
{
m_num2 = m_num2*0;
numLines2 += m_num2;
}break;
case 1:
{
m_num2 = m_num2*50;
numLines2 += m_num2;
}break;
//…省略2,3
case 4:
{
m_num2 = m_num2*1000;
numLines2 += m_num2;
}break;
default:break;
}


砖块移动和游戏暂停的实现:

这里原本是需要响应键盘按下事件的,所以需要添加WM_KEYDOWN消息响应。然后将函数传进来的nChar,也就是你按的键盘值,和你游戏中设定的移动按键比较。从而执行相应的操作。同时这里也要熟悉一下虚拟按键码。

但是由于我们添加了位图按钮,导致键盘按键事件响应不了,我们这里就重载了PreTranslateMessage函数,在这个函数里面拦截按键消息,从而响应。

①一键下落:这里用了一个循环,直到下落到砖块与边界或者其他砖块产生冲突才停止下落。

②空格暂停:暂停的原理就是关闭定时器,KillTimer(1);//关闭定时器

③空格继续:SetTimer(0,difficulty,NULL);//继续 恢复原来的下落速度

游戏难度选择:

默认的难度是简单,就是在构造函数中将difficulty初始化为简单,所以难度选择就是改变difficulty的值,从而改变下落速度。

//具体代码如下:
void CTetrisView::OnDiffEasy() //容易
{
difficulty = 500;
OnGameStart();//开始游戏
}
//…省略中级,高级


播放背景音乐:

添加头文件:#include

//具体播放音乐和关闭音乐的代码如下:
//①
PlaySound((LPCTSTR)IDR_WAVE1, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC);//播放音乐,添加在 开始函数里面
//②
PlaySound(NULL,NULL,SND_FILENAME);//终止音乐


游戏设置与帮助:

这里就是调用了一个对话框,用来做一些解释说明。实现步骤:

① 插入一个对话框

② 给对话框关联一个类

③ 在需要弹出对话框的函数里面定义一个对话框的对象,然后DoModal().弹出模态对话框。

//…代码省略


静态文本超链接颜色和鼠标样式:

由于静态文本是不能响应鼠标点击事件的,所以我们要现将静态文本的ID改掉,如:IDC_STATIC_BK然后在样式中将通知勾选上。这样就可以响应鼠标点击事件了。

接着就是要实现鼠标移动到超链接上面显示一个手的形状,这里我们需要载入一个手型的光标。本来是载入这个:IDC_HAND就可以了,但是当我载入它的时候,编译,提示IDC_HAND未定义。查了一下原来是版本的问题,不支持手型的。

后来谷歌上找到了一个可以载入手型的方法:

m_hCursor = ::LoadCursor(NULL, MAKEINTRESOURCE(32649));

就是用一个全局的载入光标函数来载入。光标载入好之后,添加一个鼠标移动事件的响应,当鼠标移动到指定区域,显示手型光标。

然后就是连接我自己的博客了ShellExecute这个函数就是用来实现超链接的。本来还做了一个字体下划线,后来我又给删掉了。原因是我不知道怎么获取系统默认字体的大小,导致我设置的字体型号和控件区域不匹配。

//具体代码如下:
BOOL Chelp::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
//  ScreenToClient(&(pMsg->pt));
CRect rect;
GetDlgItem(IDC_STATIC_BK)->GetWindowRect(rect);
if (pMsg->message == WM_MOUSEMOVE)
{
if (pMsg->pt.x > rect.left && pMsg->pt.x < rect.right && pMsg->pt.y > rect.top && pMsg->pt.y < rect.bottom)
{
SetCursor(m_hCursor);
}
}
if (pMsg->message == WM_LBUTTONDOWN)
{
//  ScreenToClient(&rect);
if (pMsg->pt.x > rect.left && pMsg->pt.x < rect.right && pMsg->pt.y > rect.top && pMsg->pt.y < rect.bottom)
{
ShellExecute(NULL,"open",TEXT("http://blog.csdn.net/qingdujun"),NULL,NULL, SW_SHOWNORMAL);
}
}
return CDialog::PreTranslateMessage(pMsg);
}


双缓冲机制:

为了避免刷屏过快而导致的屏幕闪烁问题,这里引进了双缓冲实现机制。这里主要就是将屏幕映射到内存设备,然后在屏幕上作图就相当于在内存里面作图,然后全部做好之后,通过BitBlt()函数将位图从内存中拷贝到屏幕上来,刷新一次屏幕从而显示出来。避免了屏幕闪烁。

//详细代码如下:
void CTetrisView::OnDraw(CDC* pDC)
{
CTetrisDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
//////  双缓冲  //////
int m_nWidth,m_nHeight;
CDC m_memDC;
CBitmap m_memBmp;
//1.用于映射屏幕的内存设备环境
//获取游戏窗口的大小,用于下面设置内存位图的尺寸
CRect windowsRect;
GetClientRect(&windowsRect);

m_nWidth = windowsRect.Width();
m_nHeight = windowsRect.Height();
//内存设备环境与屏幕设备环境关联(兼容)
m_memDC.CreateCompatibleDC(pDC);
//内存位图与屏幕关联(兼容),大小为游戏窗口尺寸大小
m_memBmp.CreateCompatibleBitmap(pDC,m_nWidth,m_nHeight);
//  m_memDC.FillSolidRect(windowsRect,RGB(0,0,0));
//内存设备环境与内存位图关联,以便于通过m_memDC在内存为图上作画
m_memDC.SelectObject(&m_memBmp);
DrawImage(bin1,bin2,binY1,binY2,outputImage1,outputImage2,outputImageY1,outputImageY2,&m_memDC);
//把内存DC上的图形拷贝到电脑屏幕上
pDC->BitBlt(0,0,m_nWidth,m_nHeight,&m_memDC,0,0,SRCCOPY);
//  pDC->FillRect(windowsRect,&m_brushBackground);
m_memDC.DeleteDC();//删除DC
m_memBmp.DeleteObject();//删除位图
}


背景位图的插入:

这个就是界面美化神器了,就是因为这张背景整个游戏才变得比原来的美观了几倍。第一次做的单文档,白色背景,是在是太不友好了。这个背景图片是我从4399上面截图下来的,然后用ps制作了一下,使其符合我的要求。

//插入背景图片的代码
CBitmap bmp;
bmp.LoadBitmap(IDB_BITMAP1); ///加载位图
m_brushBackground.CreatePatternBrush(&bmp);    ///创建位图画刷

CRect rect;
GetClientRect(&rect);//获得客户区大小
pDC->FillRect(rect,&m_brushBackground);//将矩形区域用位图填充


字体颜色、大小以及字体背景的删掉:

注意:font1要定义为成员函数。CreatePointFont()只需要两个参数,用起来方便。

font1.CreatePointFont(180,_T("华文彩云"));
font2.CreatePointFont(300,_T("华文彩云"));
pDC->SetBkMode(TRANSPARENT);//去掉背景色
pDC->SetTextColor(RGB(251,163,1));//红色


砖块三维化:

这里用到函数Draw3dRect()实现砖块三维化,其中需要3个参数

参数:1.矩形区域; 参数2:RGB(); 参数3:RGB().

void Draw3dRect(LPCRECT lpRect,COLORREF clrTopLeft,COLORREF clrBottomRight);


其中后面两个参数,由下面这两个函数返回。具体代码如下:

COLORREF CTetrisView::GetLightColor(COLORREF m_crBody)
{
BYTE r = GetRValue(m_crBody);
BYTE g = GetGValue(m_crBody);
BYTE b = GetBValue(m_crBody);
r = r + COLOR_CHANGE>255?255:r+COLOR_CHANGE;
g = g + COLOR_CHANGE>255?255:g+COLOR_CHANGE;
b = b + COLOR_CHANGE>255?255:b+COLOR_CHANGE;
return RGB(r,g,b);
}
COLORREF CTetrisView::GetDarkColor(COLORREF m_crBody)
{
BYTE r = GetRValue(m_crBody);
BYTE g = GetGValue(m_crBody);
BYTE b = GetBValue(m_crBody);
r = r - COLOR_CHANGE<0?0:r-COLOR_CHANGE;
g = g - COLOR_CHANGE<0?0:g-COLOR_CHANGE;
b = b - COLOR_CHANGE<0?0:b-COLOR_CHANGE;
return RGB(r,g,b);
}


工具栏、菜单栏、状态栏的隐藏,最大化按钮的禁用,窗口大小的设定

将这些用不着的东西隐藏掉,看着碍眼。这个有框架有关的东西,全部在框架类中进行修改。

//具体代码如下:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
//...省略以上代码
SetMenu(NULL);//菜单栏的隐藏
ShowControlBar(&m_wndToolBar,FALSE,FALSE);//工具栏的隐藏
ShowControlBar(&m_wndStatusBar,FALSE,FALSE);//状态栏的隐藏
return 0;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
//  the CREATESTRUCT cs
cs.cx = 790;//设定窗口的额大小
cs.cy = 615;
cs.style &= ~WS_THICKFRAME;//使窗口不能用鼠标改变大小
cs.style &= ~WS_MAXIMIZEBOX; //禁止窗口最大化
return TRUE;
}


俄罗斯方块双人版的实现:

差点把最重要的双人版实现给漏写了,其实实现双人版的很简单,就是将FillRect()填充区域右移一定距离就实现了双人版的。当然,是在你将第二个游戏的代码也编写好的前提下,不然只是把左边玩家的界面再显示一次,那没得意思。同理,预显示也就是这么实现的,就是调整了一下砖块显示的位置。

//玩家1和玩家2游戏面板显示的代码:
for (i = 0; i < height; ++i)//一行一行的画砖块
{
for (j = 0; j < width; ++j)
{
rc1 = CRect(j*nSize+149, i*nSize+70, (j+1)*nSize+149, (i+1)*nSize+70);
rc2 = CRect(j*nSize+435, i*nSize+70, (j+1)*nSize+435, (i+1)*nSize+70);
//绘制面板
//界面1player
if (0 != image1[i][j])
{
pDC->FillRect(rc1, &CBrush(BrickColor[image1[i][j]]));//画临时砖块(运动中)
pDC->Draw3dRect(rc1,GetLightColor(BrickColor[image1[i][j]]),GetDarkColor(BrickColor[image1[i][j]]));
}
//界面2player
if (0 != image2[i][j])
{
pDC->FillRect(rc2, &CBrush(BrickColor[image2[i][j]]));//画临时砖块(运动中)
pDC->Draw3dRect(rc2,GetLightColor(BrickColor[image2[i][j]]),GetDarkColor(BrickColor[image2[i][j]]));
}
}
}


位图按钮的创建:

这里用到CBitmapButton类,位图按钮。位图按钮创建的步骤:

为按钮创建1到4个位图。

构造CBitmapButton对象。

调用Create函数创建Windows按钮控件,并把它加到CBitmapButton对象上。

调用成员函数LoadBitmaps加载位图资源。

具体创建请参见我的另一篇博文:[戳这里]

//部分代码如下:
int CTetrisView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;

// TODO: Add your specialized creation code here
//开始1按钮
m_bt1.Create("1",WS_VISIBLE|WS_CHILD|BS_OWNERDRAW,CRect(136,480,360,529),this,IDB_BT_START1);
m_bt1.LoadBitmaps(IDB_BT_START1,IDB_BT_START2,IDB_BT_START1,IDB_BT_START1);
m_bt1.SizeToContent();  //使按钮适应图片大小
//...
return 0;
}


测试与实现

整个游戏运行界面图:

其中Next是预显示,Point是每次固定块后所得的分数,XO用于播放/暂停背景音乐,a,b,c用于选择游戏难度。START点击开始游戏。下面还有个总分统计栏。



游戏设置与帮助页面图:



总结

这次编写双人版俄罗斯方块,主要参照了VC++实验指导书,界面主要就是插入了一个位图背景,图是从4399上面截图下来的,然后ps成自己需要的大小并转化成.bmp格式。插入后,再就是在适当的地方显示砖块和分数统计,这样一个界面就做好了。界面上的按钮,我是用位图按钮CBitmapButton实现的,这里有个小插曲,就是4张图片的大小问题,我本来的意思是想让鼠标点击的时候就显示一张小一点的图片,从而达到按钮的效果,所以我就做了4张大小不同的位图,导致了后来的白边的产生。另外,由于添加了位图按钮导致了按键事件WM_KEYDOWN和鼠标点击事件WM_LBUTTONDOWN不能响应,我也不知道是为什么。好在MFC中消息都要经过翻译后再发送,所以我就在PreTranslateMessage实现了按键和鼠标点击事件,开始不知道pMsg->pt传送进来的是相对于桌面的坐标。导致响应不成功,后来找到了桌面与客户区坐标转换函数ScreenToClient()。另外还有一个问题就是关于CRect的问题,如:我定义了一个CRect rect(10,10,100,100);然后我将rect.top, rect.left…转换成CString类型,然后pDC->TextOut输出出来确是(0,0,99,99)不明白这是什么原因。总之,这次小游戏的编写,让我自己学到了不少的东西,对MFC的操作也更加熟练了,同时也感觉到了MSDN的重要性。

源代码下载:链接:http://pan.baidu.com/s/1zR4ce 密码: lnry
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: