qingdujun

关注人工智能!

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

游戏最终界面如图:

这里写图片描述

目录:

题目要求

参考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

阅读更多
想对作者说点什么? 我来说一句

利用MFC做的双人简单俄罗斯方块

2011年11月29日 1.93MB 下载

mfc写的俄罗斯方块

2014年03月21日 1.02MB 下载

俄罗斯方块MFC版源码

2009年08月31日 910KB 下载

MFC俄罗斯方块制作过程

yjn43422757 yjn43422757

2009-08-21 22:24:00

阅读数:6909

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭