使用Windows API制作贪吃蛇小游戏(二)绘制图片
1.准备图片
需要准备表示食物、蛇的头部和身体的图片,图片的格式为bmp。
以下是将图片加载到当前工程的方法
2.加载图片并创建位图句柄
在window系统中几乎所有的资源都可以用句柄表示和操作。
在加载位图之前还需要创建一个类
//创建了一个名称为snack.h的文件
#ifndef _SNACK_H_
#define _SNACK_H_
#include<Windows.h>
class snack
public:
void loadbpm(HINSTANCE hInstance);//加载图片的自定义函数
private:
//保存了需要图片的句柄
HBITMAP Hbitmap_Food;
HBITMAP Hbitmap_SnackHead_Up;
HBITMAP Hbitmap_SnackHead_Down;
HBITMAP Hbitmap_SnackHead_Left;
HBITMAP Hbitmap_SnackHead_Right;
HBITMAP Hbitmap_SnackBody;
#endif
实现loadbpm函数
//创建snack.cpp文件实现类中声明的函数
#include"snack.h"
#include"resource.h"
void bitMap::loadbpm(HINSTANCE hInstance)
Hbitmap_Food = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP3));//加载图片的顺序不同对应的ID也会不同
Hbitmap_SnackHead_Up = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP6));
Hbitmap_SnackHead_Down = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP2));
Hbitmap_SnackHead_Left = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP4));
Hbitmap_SnackHead_Right = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP5));
Hbitmap_SnackBody = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1));
resource.h文件是在第一步完成后系统自动生成的,包含了加载的资源的信息。
通过打开后缀为.rc的文件就可以进入资源试图看到ID对应的图片。
加载图片使用LoadBitmap()函数,这个函数的原型如下:
HBITMAP LoadBitmap(
HINSTANCE hInstance,//包涵位图资源的模块实例句柄,通常就是当前实例句柄
LPCWSTR lpBitmapName//位图资源的名称,是一个字符串,这里就要使用MAKEINTRESOURCE这个宏
//因此loadbpm这个函数要给一个参数表示句柄
MAKEINTRESOURCE这个宏可以将整数的ID转化成一个字符串。
使用LoadBitmap()函数加载位图后需要使用DeleteObject()函数释放位图资源以避免内存泄漏。
//snack类的析构函数
~snack()
DeleteObject(Hbitmap_Food);
DeleteObject(Hbitmap_SnackHead_Up);
DeleteObject(Hbitmap_SnackHead_Down);
DeleteObject(Hbitmap_SnackHead_Left);
DeleteObject(Hbitmap_SnackHead_Right);
DeleteObject(Hbitmap_SnackBody);
DeleteObject()函数的功能就是删除一个GDI对象,GDI是Windows系统中用于表示图形设备资源的内部数据结构,包括物理对象和逻辑对象。逻辑对象就是程序中和图形相关的对象,比如画笔、画刷、字体等。这些对象的属性值(颜色、填充方式等)通过程序设置,GDI会将起转化为物理对象发送给显示设备进行绘制。这里加载的位图是属于物理对象。不在使用GDI对象时要用DeleteObject()函数删除,这个函数的参数就是要删除的对象的句柄。
3.获取目标窗口的设备上下文
//在窗口过程函数中
case WM_PAINT:
PAINTSTRUCT ps;//这个结构体包含了和绘图有关的一些信息
HDC hdc=BeginPaint(hwnd, &ps);
//这里是绘图的代码
EndPaint(hwnd, &ps);
BeginPaint函数执行成功后就会返回目标窗口的设备上下文句柄,所以在使用前需要定义一个HDC 类型的变量,以此保存目标窗口的句柄。在设备上下文中执行完绘图操作后调用EndPaint函数释放设备上下文。
4.创建与目标窗口兼容的内存设备上下文
内存设备上下文的输出结果不会直接显示在屏幕上,而是存储在内存中。这种方式的优点是可以避免绘图过程中屏幕闪烁的问题。使用CreateCompatibleDC(HDC hdc)这个函数就可以了。
HDC hMemDC=CreateCompatibleDC(hdc);
除了创建一个类,还需要创建一个结构体保存蛇的信息,实现和蛇有关的功能的函数。
#ifndef _SNACK_H_
#define _SNACK_H_
typedef struct NODE
int x;
int y;
struct NODE *pLast;
struct NODE *pNext;
}Snake, Apple;
static Apple apple={5,5,NULL,NULL};
class snack
public:
void addnode(int x, int y);
void show_food(HDC hdc);
void show_snack(HDC hdc, HWND hwnd);
private:
Snake*pHead{ nullptr };
Snake*pEnd{ nullptr };
#endif
使用一个双向链表保存蛇的位置信息。addnode函数用来增加蛇的长度。
#include "snake.h"
void mySnake::addnote(int x, int y)
Snake*temp = new Snake;
temp->x = x;
temp->y = y;
temp->pLast = nullptr;
temp->pNext = nullptr;
if (pHead == nullptr)
pHead = pEnd = temp;
pEnd->pNext = temp;
temp->pLast = pEnd;
pEnd = temp;
delete temp;
5.将位图对象选择到内存设备上下文
使用SelectObject函数将位图对象选择到内存设备上下文中。
HCDIOBJ SelectObject(HDC hdc ,HGDIOBJ h);
//hdc是目标设备上下文句柄,这里是内存设备上下文句柄
//h 是位图对象句柄
6.将位图绘制到内存设备上下文中,在将内存设备上下文复制到设备上下文
可以使用StretchBlt或者BitBlt这两个函数。StretchBlt函数是将源图形进行缩放并输出到目标设备上下文中,BitBlt直接将源图像的像素数据复制到目标设备的制定区域。这个里使用BitBlt函数
BOOL BitBlt(
[in] HDC hdc,//指向目标设备环境的句柄
[in] int x,//指定目标矩形区域左上角的X轴和Y轴逻辑坐标,将图片贴在那个位置
[in] int y,
[in] int cx,//指定源和目标矩形区域的逻辑宽度和逻辑高度,这里是要贴的图片的宽和高
[in] int cy,
[in] HDC hdcSrc,//指向源设备环境的句柄
[in] int x1,//指定源矩形区域左上角的X轴和Y轴逻辑坐标。
[in] int y1,
[in] DWORD rop//指定光栅操作代码
);
[in] int x1, [in] int y1,但我们想要将整张位图复制到制定区域的时候这个两个参数通常为0。原因是默认情况下位图是从左上角开始绘制的。因此如果源矩形区域的左上角坐标为0,0时就可以将整张图复制到目标设备上下文中。如果要复制位图的一部分,通过改变x1和y1的值就可以了。比如要复制右下角100*100像素的区域,只需要将x1和y1设置为位图宽度和高度减100就可以了。
//显示食物
void snack::show_food(HDC hdc)
HDC hdcMem = CreateCompatibleDC(hdc);
SelectObject(hdcMem, Hbitmap_Food);
BitBlt(hdc, food.x * 25, food.y * 25, 25, 25, hdcMem, 0, 0, SRCCOPY);//图片是25*25
DeleteDC(hdcMem);
使用完兼容性DC后要记得将它删除。
//void snack::show_snack(HDC hdc, HWND hwnd)
RECT rect;
GetClientRect(hwnd, &rect);
HDC hdcMem1 = CreateCompatibleDC(hdc);//创建兼容性DC
HBITMAP hBmp = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);//创建兼容性位图
HBITMAP hOldBmp = (HBITMAP)SelectObject(hdcMem1, hBmp);
//绘制头
switch (fx)
case up:
SelectObject(hdcMem1, Hbitmap_SnackHead_Up);
break;
case down:
SelectObject(hdcMem1, Hbitmap_SnackHead_Down);
break;
case right:
SelectObject(hdcMem1, Hbitmap_SnackHead_Right);
break;
case left:
SelectObject(hdcMem1,Hbitmap_SnackHead_Left);
break;
BitBlt(hdc, pHead->x * 25, pHead->x * 25, 25, 25, hdcMem1, 0, 0, SRCCOPY);
Snake*temp = pHead->pNext;
//绘制身体
while (temp)
SelectObject(hdcMem1, Hbitmap_SnackBody);
BitBlt(hdc, temp->x * 25, temp->y * 25, 25, 25, hdcMem1, 0, 0, SRCCOPY);
temp = temp->pNext;
BitBlt(hdc, 0, 0, 0, 0, hdcMem1, 0, 0, SRCCOPY);
DeleteObject(hOldBmp);
DeleteObject(hBmp);
DeleteDC(hdcMem1);
不同的方向蛇头对应的图片也不一样,所以需要一个switch......case......结构进行选择,这就需要定义一个和方向有关的枚举
enum direction
left,
down,
right
*static enum direction fx = right;
这里在绘制图片的时候不仅创建了一个兼容性DC还创建了兼容性位图。创建兼容性位图的原因是在绘图的过程中我们要使用双缓冲技术
双缓冲绘图
因为有些时候绘图和屏幕的刷新是不同步的,绘图还没有结束但又开始刷新了,这就会使屏幕一闪一闪的。为了解决这个问题就要使用双缓冲绘图(如果双缓冲不行就三缓冲,多级缓冲)。
假设你要在窗口中绘制A,B,C三幅图,
普通绘图过程是这样的:
把A绘制到窗口中;把B绘制到窗口中;把C绘制到窗口中;
而兼容位图是这样的:
先在内存中开辟一块区域存放图片,把A绘制到内存中;把B绘制到内存中;把C绘制到内存中;然后把内存中绘好的图片绘制到窗口中。
普通绘图要绘3次,而兼容位图只要绘一次。
双缓冲绘图的大致思路是先在内存中创建一快画布,将要绘制的图片先绘制在这块画布上,然后再将这个画布一次性贴在设备DC上就可以了。
//例子
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
HDC hDC, hDCMem;
HBITMAP hBmpMem, hPreBmp;
switch (message)
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
/* 创建双缓冲区 */
// 创建与当前DC兼容的内存DC
hDCMem = CreateCompatibleDC(hDC);
// 创建一块指定大小的兼容位图
hBmpMem = CreateCompatibleBitmap(hDC, rect.right, rect.bottom);
// 将该位图选入到内存DC中,默认是全黑色的
hPreBmp = SelectObject(hDCMem, hMemBmp);
/* 在双缓冲中绘图 */
// 加载背景位图
hBkBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
hBrush = CreatePatternBrush(hBkBmp);
GetClientRect(hWnd, &rect);
FillRect(hDCMem, &rect, hBrush);
DeleteObject(hBrush);
/* 将双缓冲区图像复制到显示缓冲区 */
BitBlt(hDC, 0, 0, rect.right, rect.bottom, hDCMem, 0, 0, SRCCOPY);
/* 释放资源 */
SelectObject(hDCMem, hPreBmp);
DeleteObject(hMemBmp);
DeleteDC(hDCMem);
EndPaint(hWnd, &ps);
break;
使用 Win32 版本时注意释放资源,释放顺序与创建顺序相反
透明贴图
窗口的背景颜色是白色的,图片的背景颜色也是白色的,因此可以不用处理就将图片贴在窗口上。但是如果窗口的背景不是白色,那么直接贴图片显示的结果就有点奇怪了。
在这种情况下就要使用透明贴图。
使用透明贴图大致有两种方法
- 透明遮罩法
- 透明色彩法
透明遮罩法
利用BitBit函数中的Raster光栅值的运算,来将图片中不希望出现的部分处理掉
- SRCAND:通过使用AND操作符来将源和目标矩形区域内的颜色合并(贴掩码图)
- SRCPAINT:通过使用布尔类型OR操作符将来源和目标矩形区域的颜色合并(贴源图)
透明色彩法
使用TransparentBlt函数将图片的背景颜色设置为透明
需要加上这行代码#pragma comment(lib,"Msimg32.lib")
WINGDIAPI BOOL WINAPI TransparentBlt(
_In_ HDC hdcDest, //目标环境设备的句柄
_In_ int xoriginDest, //目标矩形左上角的X轴坐标
_In_ int yoriginDest, //目标矩形左上角的y轴坐标
_In_ int wDest, //目标矩形的宽度
_In_ int hDest, //目标矩形的高度