我是一个新手,刚学习MFC,看了一些图形的知识做了一个简单的贪吃蛇游戏。
游戏设置分三级别:
简单: 0.5s 刷新一次图,即调用OnDraw(CDC* pDC)函数;
中等: 0.2s 刷新一次图,即调用OnDraw(CDC* pDC)函数;
难:   0.05s  刷新一次图,即调用OnDraw(CDC* pDC)函数;但是选择玩中等和难的界面的闪烁效果很明显啊!后来才知道可以利用双缓冲来避免图形的闪烁,在网上看了一些双缓冲的资料,就自己弄了下,但是还是闪烁 不知道为啥。我绘制图形在veiw中画的,先贴出这部分重要的代码,希望前辈能帮我看下,到底要如何来利用双缓冲来避免图形闪烁。代码:
void CMfc_snackeView::OnDraw(CDC* pDC)   //画界面
{
CMfc_snackeDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc); /*
static CBrush brush(RGB(255, 255, 255));
SetClassLong(this->m_hWnd, GCL_HBRBACKGROUND, 
(LONG)(HBRUSH)brush);
*/
//////////////////////////////////////////////////////////////////////////
    CDC MemDC;   //首先定义一个显示设备对象
CBitmap MemBitmap;   //定义一个位图对象
    //随后建立与屏幕显示兼容的内存显示设备
MemDC.CreateCompatibleDC(pDC);
//这时还不能绘图,因为没有地方画
//下面建立一个于屏幕显示兼容的位图,至于位图的大小嘛
//可以用窗口的大小
CRect rect;
GetClientRect(&rect);
    MemBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
//将位图选入到内存显示设备中
//只要有选入了位图的内存显示设备才有地方地方绘图,画到指定的位图上
CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);
//先用背景色将位图清除干净,这里我用的是白色作为背景
//你也可以用自己应该用的颜色
MemDC.FillSolidRect(0, 0, rect.Width(), rect.Height(), RGB(255, 255, 255)); //绘图//////////////////////////////////////////////////////////////////////////
//draw border of game
MemDC.Rectangle(CRect(39, 39, 601, 401)); //Records cstring show
CString s;
s.Format("当前用时: %d", m_UseTime/(1000/m_speed));
MemDC.TextOut(620, 60, s);
s.Format("当前得分: %d", m_Count);
MemDC.TextOut(620, 100, s);
s.Format("版权所有: [菜菜]咸鱼");
MemDC.TextOut(620, 140, s);
s.Format("时间: 2011-10-22");
MemDC.TextOut(620, 180, s); //draw snake
//draw head of snake
CPoint h;
h = m_body.GetAt(0);
MemDC.SelectStockObject(DKGRAY_BRUSH);
switch(m_NowDir)
    {
    case 4: MemDC.Pie(CRect(h.x, h.y, h.x+20, h.y+20), CPoint(h.x+5, h.y), CPoint(h.x+15, h.y));
        MemDC.SelectStockObject(WHITE_BRUSH );  //蛇的眼睛
    MemDC.Ellipse(h.x+13, h.y+5, h.x+18, h.y+10);
    break;

case 3: MemDC.Pie(CRect(h.x, h.y, h.x+20, h.y+20), CPoint(h.x+20, h.y+5), CPoint(h.x+20, h.y+15));
    MemDC.SelectStockObject(WHITE_BRUSH );
    MemDC.Ellipse(h.x+10, h.y+2, h.x+15, h.y+7);
    break;
   
case 2: MemDC.Pie(CRect(h.x, h.y, h.x+20, h.y+20), CPoint(h.x+15, h.y+20), CPoint(h.x+5, h.y+20));
    MemDC.SelectStockObject(WHITE_BRUSH );
    MemDC.Ellipse(h.x+13, h.y+10, h.x+18, h.y+15);
    break;

case 1: MemDC.Pie(CRect(h.x, h.y, h.x+20, h.y+20), CPoint(h.x, h.y+15), CPoint(h.x, h.y+5));
    MemDC.SelectStockObject(WHITE_BRUSH );
    MemDC.Ellipse(h.x+5, h.y+2, h.x+10, h.y+7);
    break;
}    //draw body of snake
CPoint b;
MemDC.SelectStockObject(GRAY_BRUSH);
for(int i = 1; i <= m_body.GetUpperBound(); i++)
{
b = m_body.GetAt(i);
MemDC.Rectangle(CRect(b.x, b.y, b.x+20, b.y+20));
} //draw a food
CBrush br;
br.CreateSolidBrush(RGB(0, 255, 0));
MemDC.SelectObject(br);
MemDC.FillRect(CRect(m_food.x, m_food.y, m_food.x+20, m_food.y+20), &br);
    MemDC.Rectangle(CRect(m_food.x, m_food.y, m_food.x+20, m_food.y+20));

//将内存中图拷贝到屏幕上进行显示
pDC->BitBlt(0, 0, 650, 450, &MemDC, 0, 0, SRCCOPY);
//绘图完成后的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();
}/////////////////////////////////////////////////////////////////////////////
// CMfc_snackeView printingBOOL CMfc_snackeView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default preparation
return DoPreparePrinting(pInfo);
}void CMfc_snackeView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add extra initialization before printing
}void CMfc_snackeView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add cleanup after printing
}/////////////////////////////////////////////////////////////////////////////
// CMfc_snackeView diagnostics#ifdef _DEBUG
void CMfc_snackeView::AssertValid() const
{
CView::AssertValid();
}void CMfc_snackeView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}CMfc_snackeDoc* CMfc_snackeView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMfc_snackeDoc)));
return (CMfc_snackeDoc*)m_pDocument;
}
#endif //_DEBUG/////////////////////////////////////////////////////////////////////////////
// CMfc_snackeView message handlersvoid CMfc_snackeView::Initial_Game()   //游戏地图的初始化
{
m_body.RemoveAll();

//initial direciton 
m_NowDir = m_PreDir = 3;   //inital head of snake
CPoint head;
head.x = 100;  head.y = 100;
m_body.Add(head); //create rand food
CMfc_snackeView::Create_food();
}void CMfc_snackeView::Create_food() 
{
CPoint t;
    int x, y, prove;    while(1)
{
x = rand()%L + 4;
        y = rand()%H + 4; if(x%2 == 0 && y%2 == 0)  //其实坐标是x=(40,580), y=(40,380)
{                         //每一个图标如蛇头的high和length是占20, 20
prove = 0;

x *= 10;  y *= 10;
            //食物不能是蛇的地方
for(int i = 0; i <= m_body.GetUpperBound(); i++)
{
t = m_body.GetAt(i);
if(t.x == x && t.y == y)
{
prove = 1;
        break;
}
}

if(!prove)
break;
}
}
m_food = CPoint(x, y);
}void CMfc_snackeView::GameStart()  //开始游戏哦
{   //玩家时间和得分数的初始化
m_Count = m_UseTime = 0; m_start++;
if(m_start > 1)
  CMfc_snackeView::Initial_Game();
 
//每隔m_speed时间发送WM_TIMER消息
    Timer = SetTimer(1, m_speed, NULL);
}void CMfc_snackeView::OnTimer(UINT nIDEvent) //WM_TIMER消息
{
// TODO: Add your message handler code here and/or call default
m_UseTime++;  //时间记录
    //蛇移动
CMfc_snackeView::Move(); CView::OnTimer(nIDEvent);
}void CMfc_snackeView::Move() //蛇移动函数
{
    if(m_PreDir == m_NowDir || abs(m_PreDir-m_NowDir) == 2)
m_NowDir = m_PreDir;
else
m_PreDir = m_NowDir; CPoint head;
head = m_body.GetAt(0);

//4个方向
switch(m_NowDir)
{
case 4: head.y -= 20;
    break;

case 3: head.x += 20; 
    break;

case 2: head.y += 20; 
    break;
    
case 1: head.x -= 20;
    break;
}

int over = 0;
    CPoint t;
    //蛇头是否吃到自己
for(int i = 0; i <= m_body.GetUpperBound(); i++)
{
t = m_body.GetAt(i);
if(head.x == t.x && head.y == t.y)
{
over = 1;
break;
}
}

if(!Check(head) || over)
{ //结束Timer,并弹出一个结束的对话框
KillTimer(Timer);
MessageBox("Game is over! You are fail!");
}
else
{
m_body.InsertAt(0, head);
        //蛇头吃到食物
if(head.x == m_food.x && head.y == m_food.y)
{
m_Count++;
Create_food();
}
else
m_body.RemoveAt(m_body.GetUpperBound());
}
     
//使整个窗口客户区无效,意味着需要重绘,即调用OnDraw函数
Invalidate();
}
//键盘按键判断
void CMfc_snackeView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
// TODO: Add your message handler code here and/or call default
    if(m_start)
{
switch(nChar)
{
    case 38:  //up
m_NowDir = 4;
    break; case 39:  //right
    m_NowDir = 3;
    break;     case 40:  //down
    m_NowDir = 2;
    break;     case 37:  //left
    m_NowDir = 1;
    break;
default:
m_NowDir = m_PreDir;
break;
}
} CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
//判断是否出界
int CMfc_snackeView::Check(CPoint head)
{
if(head.x < 40 || head.x > 580 
|| head.y < 40 || head.y > 380)
    return 0;
else
        return 1;
}
void CMfc_snackeView::OnEasy() 
{
// TODO: Add your command handler code here
m_speed = 500;
    CMfc_snackeView::GameStart();
}void CMfc_snackeView::OnMedium() 
{
// TODO: Add your command handler code here
m_speed = 200;
    CMfc_snackeView::GameStart();
}void CMfc_snackeView::OnHard() 
{
// TODO: Add your command handler code here
m_speed = 50;
    CMfc_snackeView::GameStart();
}////////////////////////////////////////////////////////////////
由于刚接触到MFC和双缓冲,所以希望前辈们帮下我,像我这种多少秒刷新一次图的程序,怎么来避免图形的闪烁呢,给一些资料和建议都可以,再次非常的3Q!

解决方案 »

  1.   

    我一般是选择在OnTimer里面画图和贴到屏幕DC上, 可以控制刷新时间void CsurveyDlg::OnTimer(UINT_PTR nIDEvent)
    {
    // TODO: 在此添加消息处理程序代码和/或调用默认值 if (nIDEvent == 1)
    {
    CDC* dc = this->GetDC();
    CDC memDC;
    CBitmap bitmap;
    memDC.CreateCompatibleDC(dc);
    bitmap.CreateCompatibleBitmap(dc, IMAGEWIDTH, IMAGEHEIGHT);
    memDC.SelectObject(&bitmap); StretchDIBits(memDC.m_hDC, 0, 0, IMAGEWIDTH, IMAGEHEIGHT, 0 , 0,
    IMAGEWIDTH, IMAGEHEIGHT, &ImageTemp, &m_bitInfo, DIB_RGB_COLORS, SRCCOPY); memDC.SetBkMode(TRANSPARENT);
    memDC.SetTextColor(RGB(0, 0, 255)); CPen pen1(PS_SOLID, 1, RGB(0, 255, 255));
    memDC.SelectObject(&pen1);
    memDC.MoveTo(0, (int)zeroY);
    memDC.LineTo(IMAGEWIDTH, (int)zeroY);
    memDC.MoveTo((int)zeroX, 0);
    memDC.LineTo((int)zeroX, IMAGEHEIGHT); dc->BitBlt(0, 0, IMAGEWIDTH, IMAGEHEIGHT, &memDC, 0, 0, SRCCOPY); ReleaseDC(&memDC);
    ReleaseDC(dc);
    }
      

  2.   

    比如说你的贪食蛇,控制区域和游戏区域肯定不是一起的,那么你刷新的时候肯定只刷新游戏区,InvalidateRect()刷新固定的区域。这个就是举个例子,你在想想
      

  3.   


    OnEraseBkgnd里面,直接返回TRUE
      

  4.   

    不让它重画背景
    或者用invalidaterect(不要用invalidate,如果你界面上只有极少部分地方发生改变的话)传false指明不让它画背景,你只需要绘制发生改变的区域
    以上两个方法你可以试下