游戏最终界面如图:

目录:

  • 题目要求
  • 功能需求
  • 总体设计
    • 系统模块
    • 系统业务处理流程
  • 详细设计
    • 砖块预显示
    • 分数统计
    • 砖块移动和游戏暂停的实现
    • 游戏难度选择
    • 播放背景音乐
    • 游戏设置与帮助
    • 静态文本超链接颜色和鼠标样式
    • 双缓冲机制
    • 背景位图的插入
    • 字体颜色大小以及字体背景的删掉
    • 砖块三维化
    • 工具栏菜单栏状态栏的隐藏最大化按钮的禁用窗口大小的设定
    • 俄罗斯方块双人版的实现
    • 位图按钮的创建
  • 测试与实现
    • 整个游戏运行界面图
    • 游戏设置与帮助页面图
  • 总结

题目要求

参考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-3flag1 = 0;
}brickType = bType1;//rand()%7:随机产生0-6的数字。
initOrientation = iOrient1;//随机状态0-3if (0 == flag1)//2
{bType1 = (rand() % NUM_BRICK_TYPES) + 1;//rand()%7:随机产生0-6的数字。iOrient1 = (unsigned int) (rand() % 4);//随机状态0-3if (bType1 == 1)activeBrickY1 = new CIBrick;  //动态多态性的体现else if (bType1 == 2)activeBrickY1 = new CLBrick;else if (bType1 == 3)activeBrickY1 = new CSBrick;//...省略4,5,6,7activeBrickY1->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();//删除DCm_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 cscs.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);//绘制面板//界面1playerif (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]]));}//界面2playerif (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

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

  1. VC++ MFC单文档应用程序SDI下调用glGenBuffersARB(1, pbo)方法编译通过但执行时出错原因分析及解决办法:glewInit()初始化的错误...

    1.问题症状 在VC++环境下,利用MFC单文档应用程序SDI下开发OpenGL程序,当调用glGenBuffersARB(1, &pbo)方法编译通过但执行时出错,出错代码如下: OpenG ...

  2. MFC基于单文档实现绘图功能(线段,矩形,椭圆,圆,铅笔,橡皮),保姆式教程

    MFC基于单文档实现绘图功能(线段,矩形,椭圆,圆,铅笔,橡皮),保姆式教程 需要手写的代码都在图片下面,实际内容不多------嘻嘻. 1.打开VS–新建项目–选择mfc应用程序 2.选择单文档,m ...

  3. MFC应用程序类型:单文档、多文档与基于对话框

    单文档:记事本类程序的标准模式,有菜单栏.工具栏等,只能进行一份文档的操作,即不能同时在同一个应用程序中打开两个文件 多文档:WORD类或浏览器程序的标准模式,可多个窗口显示不同的信息,进行不同的任务 ...

  4. 基于MFC单文档的画图程序(小项目)

    基于MFC单文档的画图程序 前言 学习了一年多的MFC和Qt,对其基本用法已经熟练掌握,迫不及待的想实战一波,但是心急吃不了热豆腐,我还是踏踏实实的找一些既能复习以前知识的单子又不消耗我太多时间(最近 ...

  5. html单文档编辑器,用VC++MFC做文本编辑器(单文档模式)

    原来做过一个用对话框实现的文本编辑器,其实用MFC模板里面的单文档模板也可以做,甚至更加方便,适合入门级的爱好者试试,现介绍方法如下: < xmlnamespace prefix =" ...

  6. 基于单文档的MFC程序

    单文档 XXXX = CMy02_MFCTest class XXXXApp : public CWinAppEx 应用程序类//程序入口地址 CMainFrame:CFrameWnd : 框架类 X ...

  7. MFC中 给基于CFormView的单文档添加背景图片

    关于基于CFormView的单文档应用程序,添加一个图片背景的方法之一如下: 下面是利用LoadImage实现.(先在程序目录中添加背景图片back.bmp) 1.在view类中添加类成员变量:(为C ...

  8. 利用MFC向导生成单文档应用程序框架

    HelloWorld 点击文件(File)->新建(New)->项目(Project).弹出新建项目(New Project). 如果安装完VS2010以后第一启动时已经设置为VC++,则 ...

  9. VS2010/MFC编程入门之二(利用MFC向导生成单文档应用程序框架)

    原文:http://www.jizhuomi.com/software/141.html 解决方案与工程 鸡啄米在VS2010的使用介绍中已经讲了解决方案与工程的概念,这里再重提一下.每个应用程序都作 ...

最新文章

  1. 批量替换sqlserver数据库TEXT字段类型的数据
  2. 报告 | 2017年云商业智能市场分析:云计算比大数据更重要
  3. 红帽集群套件RHCS四部曲(维护篇)
  4. Lucky7(hdu5768)
  5. egg.js java 生产数据_Egg 2.15.0 发布,阿里开源的企业级 Node.js 框架
  6. vbs 连oracle 负载均衡,(原)QTP中用VBS调用PLSQL并执行相关语句
  7. LeetCode 1450. 在既定时间做作业的学生人数
  8. SPOJ-LCS Longest Common Substring
  9. n个不同对象聚类为k个类别有多少种可能性? 李航博士,统计学习方法2nd.公式14.21的修正.
  10. [angular1.6]Error: “transition superseded“ ui-router 在angular1.6 报错误问题解决
  11. 做一个最简单的上位机
  12. 这是一则招聘贴——招聘区块链系统开发实习生
  13. 我国AIS信息服务平台上线 东方通大数据处理亿级报文
  14. 小白重装系统教程_小白U盘重装原版win7系统教程
  15. 带上紧箍咒,特斯拉辅助驾驶系统即将更新
  16. 悟空辅助显示连接服务器失败,悟空辅助的使用方法 第三期宠物助手
  17. python交互界面实例_什么是“面向对象”程序设计以Python为例
  18. 【100个 Unity实用技能】☀️ | Unity 将秒数转化为00:00:00时间格式
  19. 达人评测r7 7700x和i7 12700K选哪个好
  20. 黑马程序员21——交通灯管理系统

热门文章

  1. 如何在并行安装中更改默认的SOLIDWORKS版本?| SOLIDWORKS教程
  2. ADAMS 文件导入的相关研究
  3. PTA 7-33 空心字母金字塔(StringBuilder应用实例)
  4. 制作庆祝元宵节html5,庆祝元宵节活动方案
  5. 恐怖7680×4320!次时代TV技术全解析
  6. MFC-Toolbar(24位真彩色)
  7. Android AlertDialog之七十二变
  8. 我要把这些新互联网装X词汇一顿爆吐**
  9. 怎么用计算机求排名,电脑跑分软件哪个好用?哪个比较权威?电脑跑分软件排行榜2017年...
  10. TokenInsight BTC永续合约流动性实时数据 | TokenInsight