添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
使用Windows API制作贪吃蛇小游戏(二)绘制图片

使用Windows API制作贪吃蛇小游戏(二)绘制图片

1.准备图片

需要准备表示食物、蛇的头部和身体的图片,图片的格式为bmp。

down.bmp
2K
·
百度网盘
food.bmp
2K
·
百度网盘
left.bmp
2K
·
百度网盘
right.bmp
2K
·
百度网盘
up.bmp
2K
·
百度网盘

以下是将图片加载到当前工程的方法

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文件是在第一步完成后系统自动生成的,包含了加载的资源的信息。

红色框中的是加载的图片的ID

通过打开后缀为.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,				//目标矩形的高度