为何我用双缓冲 不能避免 图形的闪烁?

Pluser 2011-10-31 10:57:22
我是一个新手,刚学习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 printing

BOOL 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 handlers

void 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!
...全文
102 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
QQ515311445 2011-10-31
  • 打赏
  • 举报
回复

OnEraseBkgnd里面,直接返回TRUE
Pluser 2011-10-31
  • 打赏
  • 举报
回复
还希望 前辈们能给我一些的资料给我看下 这样我能更懂一些。
谢谢!
Pluser 2011-10-31
  • 打赏
  • 举报
回复
对 是的 谢谢7楼。!

诶呦 2011-10-31
  • 打赏
  • 举报
回复
比如说你的贪食蛇,控制区域和游戏区域肯定不是一起的,那么你刷新的时候肯定只刷新游戏区,InvalidateRect()刷新固定的区域。这个就是举个例子,你在想想
Pluser 2011-10-31
  • 打赏
  • 举报
回复
确实 我看了一些资料是如2楼 那样可以解决!
但是 貌似不是那么简单把?

我敲了一些代码但是还是不能解决! 哎悲剧

太菜了 还希望前辈指点!
Pluser 2011-10-31
  • 打赏
  • 举报
回复
如楼上所说,外面的能不刷新就不刷新 那怎么弄哦,我是新手哦!

有什么资料么?可以介绍给我看下嘛。

在如4楼所说,说原因是逻辑问题,这我就有点囧啊!

玩简单的基本上没闪烁,但是快的就闪烁现象越严重。

你说逻辑问题我不知道我逻辑哪里有点问题 希望你能指出。!

感谢大家的指点!
healer_kx 2011-10-31
  • 打赏
  • 举报
回复
闪烁的原因是你绘制上去的东西,还是出现了很大的反差,你检查一下代码的逻辑了。
诶呦 2011-10-31
  • 打赏
  • 举报
回复
双缓冲只能解决一部分闪烁,就是你的图上的文字什么的都不会闪了,但是你还有背景及图片以外的其他地方的重绘。所以背景解决方法如二楼,图片以外的地方能不刷新就不要刷新了。
stef3390 2011-10-31
  • 打赏
  • 举报
回复
我一般是选择在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);
}
fandh 2011-10-31
  • 打赏
  • 举报
回复
OnEraseBkgnd里面,直接返回TRUE
Pluser 2011-10-31
  • 打赏
  • 举报
回复
恩。3Q
dfkjsdhfks 2011-10-31
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 fandh 的回复:]
OnEraseBkgnd里面,直接返回TRUE
[/Quote]
不让它重画背景
或者用invalidaterect(不要用invalidate,如果你界面上只有极少部分地方发生改变的话)传false指明不让它画背景,你只需要绘制发生改变的区域
以上两个方法你可以试下
fandh 2011-10-31
  • 打赏
  • 举报
回复
奇怪,我说了,居然貌似没有看到。。。。。

15,979

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 界面
社区管理员
  • 界面
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧