VC/MFC 俄罗斯方块双人版(基于MFC单文档)
2017-01-05 15:58
253 查看
游戏最终界面如图:
目录:
题目要求
功能需求
总体设计
系统模块
系统业务处理流程
详细设计
砖块预显示
分数统计
砖块移动和游戏暂停的实现
游戏难度选择
播放背景音乐
游戏设置与帮助
静态文本超链接颜色和鼠标样式
双缓冲机制
背景位图的插入
字体颜色大小以及字体背景的删掉
砖块三维化
工具栏菜单栏状态栏的隐藏最大化按钮的禁用窗口大小的设定
俄罗斯方块双人版的实现
位图按钮的创建
测试与实现
整个游戏运行界面图
游戏设置与帮助页面图
总结
②隐藏菜单栏、工具栏、状态栏
③实现难度可以选择
④实现下一个砖块预测功能
⑤实现总分统计功能,和每步消除所得分数显示
⑥实现下、左、右、旋转、暂停功能
⑦实现背景音乐播放功能
⑧添加游戏帮助菜单
⑨实现砖块三维化
附加:实现双缓冲避免屏幕闪烁。设置窗口大小,禁用最大化按钮,禁止鼠标拖动改变窗口大小。
但是由于我们添加了位图按钮,导致键盘按键事件响应不了,我们这里就重载了PreTranslateMessage函数,在这个函数里面拦截按键消息,从而响应。
①一键下落:这里用了一个循环,直到下落到砖块与边界或者其他砖块产生冲突才停止下落。
②空格暂停:暂停的原理就是关闭定时器,KillTimer(1);//关闭定时器
③空格继续:SetTimer(0,difficulty,NULL);//继续 恢复原来的下落速度
① 插入一个对话框
② 给对话框关联一个类
③ 在需要弹出对话框的函数里面定义一个对话框的对象,然后DoModal().弹出模态对话框。
接着就是要实现鼠标移动到超链接上面显示一个手的形状,这里我们需要载入一个手型的光标。本来是载入这个:IDC_HAND就可以了,但是当我载入它的时候,编译,提示IDC_HAND未定义。查了一下原来是版本的问题,不支持手型的。
后来谷歌上找到了一个可以载入手型的方法:
m_hCursor = ::LoadCursor(NULL, MAKEINTRESOURCE(32649));
就是用一个全局的载入光标函数来载入。光标载入好之后,添加一个鼠标移动事件的响应,当鼠标移动到指定区域,显示手型光标。
然后就是连接我自己的博客了ShellExecute这个函数就是用来实现超链接的。本来还做了一个字体下划线,后来我又给删掉了。原因是我不知道怎么获取系统默认字体的大小,导致我设置的字体型号和控件区域不匹配。
参数:1.矩形区域; 参数2:RGB(); 参数3:RGB().
其中后面两个参数,由下面这两个函数返回。具体代码如下:
为按钮创建1到4个位图。
构造CBitmapButton对象。
调用Create函数创建Windows按钮控件,并把它加到CBitmapButton对象上。
调用成员函数LoadBitmaps加载位图资源。
具体创建请参见我的另一篇博文:[戳这里]
源代码下载:链接:http://pan.baidu.com/s/1zR4ce 密码: lnry
目录:
题目要求
功能需求
总体设计
系统模块
系统业务处理流程
详细设计
砖块预显示
分数统计
砖块移动和游戏暂停的实现
游戏难度选择
播放背景音乐
游戏设置与帮助
静态文本超链接颜色和鼠标样式
双缓冲机制
背景位图的插入
字体颜色大小以及字体背景的删掉
砖块三维化
工具栏菜单栏状态栏的隐藏最大化按钮的禁用窗口大小的设定
俄罗斯方块双人版的实现
位图按钮的创建
测试与实现
整个游戏运行界面图
游戏设置与帮助页面图
总结
题目要求
参考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
相关文章推荐
- 简单而强大的多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)
- 简单而强大的多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)
- 多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)
- 简单而强大的多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)
- VC环境下基于MFC框架的OpenGL的编程环境的配置
- 基于MFC文档/视结构的OpenGL实现
- VC环境下基于MFC框架的OpenGL的编程环境的配置
- VC中在基于单文档(SDI)程序中应用MSCOMM串口通讯控件编程详细说明(附源程序)
- 在VC 5.0中实现基于MFC的组件的本地化
- 基于Scintilla开发的MFC多文档源代码编辑器CodeEditor(二)
- 在MFC下使用OpenGL的一个简单的例子(基于单文档程序)
- 孙鑫VC学习笔记:第十五讲 编写一个基于MFC对话框的聊天程序
- 基于Scintilla开发的MFC多文档源代码编辑器CodeEditor(一)
- 基于VC++和MFC的上位机与PLC的通讯系统
- 基于MFC多文档多视图结构的OGRE指北针程序
- 基于MFC单文档SDI的OpenGL图形程序的基本框架
- 在VC 5.0中实现基于MFC的组件的本地化
- 基于MFC文档/视/框架程序之利剑
- 接触VC之三:MFC基于对话框程序
- 基于MFC文档/视/框架程序之利剑