这一节来演示一种绘图的方法,让我们的程序真正的看得见~ 哈哈哈,而不再是命令行的黑框框。 采用的是二重缓冲的方式
先说一下当前工程的启动过程,省略一部分,直接看到我们的ChildView.cpp这个文件,里边有两个函数BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
和 void CChildView::OnPaint()
,可以简单的理解为,前者是窗口出现之间,程序所做的事情,所以你可以把游戏的一些初始化的操作放在这个函数里边,后者就是绘制这个窗口,每执行一次这个函数,就会重新绘制一下当前的窗口,你所看到的动画啊,游戏啊 都是通过一直执行这个函数实现的动画的效果的~,好啦~ 说了这么一堆话了,来开始贴上我们的飞机们把~
第一步:
找到ChildView.h 这个头文件,在里边添加一些我们所需要的属性,
// 特性
public:
//绘图相关
CRect m_client;//记录客户区大小
CDC m_cacheDC; //缓冲DC
CBitmap m_cacheCBitmap;//缓冲位图
然后转到ChildView.cpp这个文件,修改OnPaint函数
void CChildView::OnPaint()
{
CPaintDC dc(this); // 用于绘制的设备上下文
// TODO: 在此处添加消息处理程序代码
// 不要为绘制消息而调用 CWnd::OnPaint()
//获取窗口DC指针
CDC *cDC = this->GetDC();
//获取窗口大小
GetClientRect(&m_client);
//创建缓冲DC
m_cacheDC.CreateCompatibleDC(NULL);
m_cacheCBitmap.CreateCompatibleBitmap(cDC, m_client.Width(), m_client.Height());
m_cacheDC.SelectObject(&m_cacheCBitmap);
//————————————————————开始绘制——————————————————————
//把要绘制的内容都贴到m_cacheDC当中就可以了
Manager.Draw(&m_cacheDC);
//最后将缓冲DC内容输出到窗口DC中
cDC->BitBlt(0, 0, m_client.Width(), m_client.Height(), &m_cacheDC, 0, 0, SRCCOPY);
//————————————————————绘制结束—————————————————————
//在绘制完图后,使窗口区有效
ValidateRect(&m_client);
//释放缓冲DC
m_cacheDC.DeleteDC();
//释放对象
m_cacheCBitmap.DeleteObject();
//释放窗口DC
ReleaseDC(cDC);
ReleaseDC(cDC);
}
我这段代码当中的Manager.Draw(&m_cacheDC);
就是把所有的内容都贴到缓冲DC当中(m_cachaeDC),Manager是一个类的实例对象,该类可以存储多个物体(比如:飞机,敌机,导弹等等),可以通过该类的Draw方法一次绘制所有的内容。 看到这里你可能说 哎呀 我不知道怎么写Manager这个类呀,我还是不知道怎么贴图啊,别着急~ 接下来我就给你们演示如何贴图~
我们试着新建一个类,就叫他Plane 类吧, 一个飞机有什么属性呢,嗯~ 有他的外观(贴图),长,宽,在游戏世界中还有一个属性是坐标,好,我们的类就长这个样子:
#pragma once
#include
#include
class Plane
{
private:
CImage Image_; //物体的贴图
int Height_; //物体的高度
int Width_; //物体的宽度
CPoint Postion_; //物体的位置坐标
public:
Plane(){};
~Plane(){};
};
CImage Image_ ; 就是储存物体的模样(外观)的,我们把一张图片放进去就好了~ 来看看怎么放一张图片呢
一般来讲是通过 CImage的load方法加载图片的,比如Image_.Load("Plane.png");
,然而我们想要最后生成的exe文件可以独立的运行就需要把图片加载到我们的资源当中,那么如何从我们的资源文件中加载图片呢,是通过这样的一个函数做到的
//从资源加载图片 第一个是资源的名称,第二个是资源的类型
BOOL LoadImageFromResource(UINT nResID, LPCTSTR lpTyp)
{
/*Image_.Destroy();*/
// 查找资源
HRSRC hRsrc = ::FindResource(AfxGetResourceHandle(), MAKEINTRESOURCE(nResID), lpTyp);
if (hRsrc == NULL) return false;
// 加载资源
HGLOBAL hImgData = ::LoadResource(AfxGetResourceHandle(), hRsrc);
if (hImgData == NULL)
{
::FreeResource(hImgData);
return false;
}
// 锁定内存中的指定资源
LPVOID lpVoid = ::LockResource(hImgData);
LPSTREAM pStream = NULL;
DWORD dwSize = ::SizeofResource(AfxGetResourceHandle(), hRsrc);
HGLOBAL hNew = ::GlobalAlloc(GHND, dwSize);
LPBYTE lpByte = (LPBYTE)::GlobalLock(hNew);
::memcpy(lpByte, lpVoid, dwSize);
// 解除内存中的指定资源
::GlobalUnlock(hNew);
// 从指定内存创建流对象
HRESULT ht = ::CreateStreamOnHGlobal(hNew, TRUE, &pStream);
if (ht != S_OK)
{
GlobalFree(hNew);
}
else
{
// 加载图片
Image_.Load(pStream);
GlobalFree(hNew);
}
// 释放资源
::FreeResource(hImgData);
return true;
}
如果是PNG图片的话,在通过这个函数把图片转为透明的(原本透明的部分)
//转换PNG图片为透明
static void TransparentPNG(CImage *png)
{
if (png->GetBPP() == 32)
{
for (int i = 0; i < png->GetWidth(); i++)
{
for (int j = 0; j < png->GetHeight(); j++)
{
unsigned char* pucColor = reinterpret_cast<unsigned char*>(png->GetPixelAddress(i, j));
pucColor[0] = pucColor[0] * pucColor[3] / 255;
pucColor[1] = pucColor[1] * pucColor[3] / 255;
pucColor[2] = pucColor[2] * pucColor[3] / 255;
}
}
}
}
最后 我们封装一个接口方便使用
//从资源中加载图片
void LoadImage(UINT nResID, LPCTSTR lpTyp = _T("PNG"))
{
LoadImageFromResource(nResID, lpTyp);
if (lpTyp == _T("PNG"))
TransparentPNG(&Image_);
}
//对象的绘制方法
void Draw(CDC *cDC)
{
Image_.Draw(*cDC, Postion_.x, Postion_.y, Width_, Height_);
}
好啦,现在生成一个实例对象去试试吧~
//————————————————————开始绘制——————————————————————
//把要绘制的内容都贴到m_cacheDC当中就可以了
plane.Draw(&m_cacheDC);
//plane是Plane类的实例对象,记得先给他加载一张图片哦~
//最后将缓冲DC内容输出到窗口DC中
cDC->BitBlt(0, 0, m_client.Width(), m_client.Height(), &m_cacheDC, 0, 0, SRCCOPY);
完成后运行一下看看吧,是不是小飞机出现在屏幕当中了呢
最后附上一个完整的飞机类
#pragma once
#include
#include
class Plane
{
private:
CImage Image_; //物体的贴图
int Height_; //物体的高度
int Width_; //物体的宽度
CPoint Postion_; //物体的位置坐标
public:
Plane()
{
LoadImage(IMG_Plane); // 让对象初始化的时候自动加载图片
Height_ = 30;
Width_ = 30;
Postion_ = CPoint(Game_Width / 2, Game_Height);
};
~Plane(){};
//转换PNG图片为透明
static void TransparentPNG(CImage *png)
{
if (png->GetBPP() == 32)
{
for (int i = 0; i < png->GetWidth(); i++)
{
for (int j = 0; j < png->GetHeight(); j++)
{
unsigned char* pucColor = reinterpret_cast<unsigned char*>(png->GetPixelAddress(i, j));
pucColor[0] = pucColor[0] * pucColor[3] / 255;
pucColor[1] = pucColor[1] * pucColor[3] / 255;
pucColor[2] = pucColor[2] * pucColor[3] / 255;
}
}
}
}
//从资源加载图片 第一个是资源的名称,第二个是资源的类型
BOOL LoadImageFromResource(UINT nResID, LPCTSTR lpTyp)
{
/*Image_.Destroy();*/
// 查找资源
HRSRC hRsrc = ::FindResource(AfxGetResourceHandle(), MAKEINTRESOURCE(nResID), lpTyp);
if (hRsrc == NULL) return false;
// 加载资源
HGLOBAL hImgData = ::LoadResource(AfxGetResourceHandle(), hRsrc);
if (hImgData == NULL)
{
::FreeResource(hImgData);
return false;
}
// 锁定内存中的指定资源
LPVOID lpVoid = ::LockResource(hImgData);
LPSTREAM pStream = NULL;
DWORD dwSize = ::SizeofResource(AfxGetResourceHandle(), hRsrc);
HGLOBAL hNew = ::GlobalAlloc(GHND, dwSize);
LPBYTE lpByte = (LPBYTE)::GlobalLock(hNew);
::memcpy(lpByte, lpVoid, dwSize);
// 解除内存中的指定资源
::GlobalUnlock(hNew);
// 从指定内存创建流对象
HRESULT ht = ::CreateStreamOnHGlobal(hNew, TRUE, &pStream);
if (ht != S_OK)
{
GlobalFree(hNew);
}
else
{
// 加载图片
Image_.Load(pStream);
GlobalFree(hNew);
}
// 释放资源
::FreeResource(hImgData);
return true;
}
//从资源中加载图片
void LoadImage(UINT nResID, LPCTSTR lpTyp = _T("PNG"))
{
LoadImageFromResource(nResID, lpTyp);
if (lpTyp == _T("PNG"))
TransparentPNG(&Image_);
}
//对象的绘制方法
void Draw(CDC *cDC)
{
Image_.Draw(*cDC, Postion_.x, Postion_.y, Width_, Height_);
}
};