[原创]小游戏编程中的画面闪烁及解决方法
之前在做小游戏时经常会遇到画面出现闪烁的情况,相信很多朋友在学习VC的路上都做过类似Windows画图的程序,在设计橡皮线时候也会遇到闪烁。究其原因,是贴图的方法欠妥造成的。在此将介绍一下如何使用缓存方法来消除烦人的闪烁现象。
游戏中的画面动作原理就是按照预先设定的帧频(如20帧/秒),将游戏中的背景及子画面全部按照各自的坐标进行重绘。当按键或操作使得子画面坐标改变,一帧一帧的重绘累加出来的效果就是子画面的移动。最常使人想到的方法就是在游戏窗体上直接进行绘制:按照先画背景图然后子画面(移动的物体)的顺序直接将图片绘制在窗体上,这样就会产生恼人的闪烁。就好象在一部电影里每隔一帧插入一个空白图像,即使电影播放速度够快也一样会看到明显的闪烁。
经过查阅资料,发现使用一种缓存的方法能够有效消除闪烁。关键点就是不直接在窗体上作画,而是首先将背景图、子画面...等等所有这一帧要画的东西全部画在内存中定义好的一个空白Bmp图像上,再将已经画好的这一帧图像从内存中转移到屏幕上。如此,画面不再闪烁!
请看下面的例子:可以受键盘控制的UFO。(片段)
***************************前言***************************************************
为了方便Bitmap操作,假定我先有一个类名为Bitmap,定义如下:
(不用深究此类,粗略看,只注意Draw函数)
class Bitmap
{
protected:
HBITMAP m_hBitmap;
int m_iWidth, m_iHeight;
void Free();
public:
// Constructor(s)/Destructor
Bitmap();
Bitmap(HDC hDC, LPTSTR szFileName);
Bitmap(HDC hDC, UINT uiResID, HINSTANCE hInstance);
Bitmap(HDC hDC, int iWidth, int iHeight, COLORREF crColor = RGB(0, 0, 0));
virtual ~Bitmap();
// General Methods
BOOL Create(HDC hDC, LPTSTR szFileName);
BOOL Create(HDC hDC, UINT uiResID, HINSTANCE hInstance);
BOOL Create(HDC hDC, int iWidth, int iHeight, COLORREF crColor);
void Draw(HDC hDC, int x, int y, BOOL bTrans = FALSE, COLORREF crTransColor = RGB(255, 0, 255));
int GetWidth() { return m_iWidth; };
int GetHeight() { return m_iHeight; };
};
//注意其中的Draw函数,其实就是封装了BitBlt() 与TransparentBlt()函数
void Bitmap::Draw(HDC hDC, int x, int y, BOOL bTrans, COLORREF crTransColor)
{
if (m_hBitmap != NULL)
{
// 创建要贴图像的缓存。
HDC hMemDC = CreateCompatibleDC(hDC);
// 将图像选入内存DC
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, m_hBitmap);
// 将Bmp位图绘制到所给DC上(hDC)
if (bTrans)
TransparentBlt(hDC, x, y, GetWidth(), GetHeight(), hMemDC, 0, 0,
GetWidth(), GetHeight(), crTransColor);
else
BitBlt(hDC, x, y, GetWidth(), GetHeight(), hMemDC, 0, 0, SRCCOPY);
// 释放资源
SelectObject(hMemDC, hOldBitmap);
DeleteDC(hMemDC);
}
}//这是普通的Bitmap贴图方法。
此例中有两个Bitmap*类型的变量:
Bitmap* m_pUFO; Bitmap* m_bBackground;
**************************************************************************************
*******************************正题*************************************************
游戏利用Timer来设定帧频,并在每次收到WM_TIMER消息时执行重绘:
void CGameEngine::PaintGame()
{
HDC hdc=GetDC(hwnd);//窗口DC,即直接绘制在窗体上。
m_bBackground->Draw(hdc,0,0,FALSE,NULL); //背景
m_pUFO->Draw(hdc,m_pUFO->GetXPos(),m_pUFO->GetYPos(),TRUE,RGB(255,0,255));
return;
}
结果是画面闪烁。使用缓存的方法,将代码改写:
首先定义两个全局变量:
HDC g_hMemDC; //内存DC缓冲,消除图像闪烁;
HBITMAP g_hMemBitmap;//用于缓冲的内存位图;
并在初始化函数中对这两个变量进行初始化:
g_hMemDC=CreateCompatibleDC(GetDC(hwnd)); //创建内存DC
g_hMemBitmap=CreateCompatibleBitmap(GetDC(hwnd),m_iWidth,m_iHeight);
//创建内存位图,m_iWidth,m_iHeight表示窗体大小。
SelectObject(g_hMemDC,g_hMemBitmap); //将内存位图选入内存DC中。
将重绘的函数改写:
void CGameEngine::PaintGame()
{
m_bBackground->Draw(g_hMemDC,0,0,FALSE,NULL);//UFO画在内存中
//UFO画在内存中
m_pUFO->Draw(g_hMemDC,m_pUFO->GetXPos(),m_pUFO->GetYPos(),TRUE,RGB(255,0,255));
//将内存中已经做好的这一帧位图转移到窗体上:
HDC hdc=GetDC(hwnd);//创建窗口DC
BitBlt(hdc,0,0,m_iWidth,m_iHeight,g_hMemDC,0,0,SRCCOPY);
return;
}
//如此一来,画面闪烁的现象消除了!
*******************************结语*************************************************
应该注意,其实缓存是使用了两次!即Draw函数中使用了一次,将文件中的位图选入内存,第二次是将已选入内存的位图画入另一个内存中的位图。最后再绘制到屏幕上。整个过程可作如下描述:
如有位图a,与位图b.都要绘制到屏幕上,首先将a读入内存memA,将b读入内存memB。再将memA,与memB分别画在内存memFrame上,最后将memFrame图像转移到窗体上即可。