现在实现地图贴图。
因为没有拿到手绘板,所以用随便做了两个材质用于测试:
再次看源文件 PhantomAndCrimsonSolitaire。其消息循环使用的是 GetMessage
:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
除了使用 GetMessage
,还有一个进行消息循环的方法是使用 PeekMessage
. GetMessage
在消息队列没有消息时会一直等待消息,直到有消息了才返回一个值;而 PeekMessage
则只会判断一下现在有没有消息,如果没有,它也不会等待,而是直接返回值表示没有取到消息。对于一个游戏程序,消息处理是特别频繁的。使用 PeekMessage
可以帮我们更好地异步处理消息。所以将其修改为:
PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); // 初始化
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg); // 获取消息
DispatchMessage(&msg); // 分配消息并响应
}
}
要显示地图,先想一想我们的基本步骤:
- 将材质文件加载到一个内存 DC 中;
- 将地图指定到一个内存 DC 中,然后用材质刷上去;
- 将地图的内存 DC 贴到设备环境句柄 hdc 中.
由此可见,除了 mdc 这个内存 DC 句柄,我们还需要创建一个句柄. 故为地图创建一个位图句柄.
目光返回到全局变量声明的部分,所有的全局句柄如下:
HBITMAP hBackGround; // 背景位图句柄
HBITMAP hMap; // 地图句柄
HDC hdc, mdc; // 设别环境句柄与内存设备环境句柄
HDC mMapDC; // 为地图指定的内存DC
我的游戏应该使用一个 12x12 的二维方块地图,每个单元为一张 16x16 的贴图。那么占用的像素就是 12x16 = 192. 不过这也太小了,所以不妨设每个单元格的大小为50,那么地图大小为 600x600. 为方便以后再修改,把单元格大小也加到宏定义中。
#define MAX_LOADSTRING 100
#define WINDOW_WIDTH 1280 // 窗口宽度
#define WINDOW_HEIGHT 720 // 窗口高度
#define CELL_SIZE 50 // 单元格边长
在工程目录下新建一个 res 文件夹,将 stone.bmp 和 ground.bmp 放进去,重命名为 ground0.bmp 和 ground1.bmp.
现在开始设计函数 Map_Paint()
函数:
首先别忘记声明函数:
VOID Map_Paint(HWND hwnd);
具体如何将贴图一个一个贴上去呢?
现在这里有两个贴图 ground0.bmp 和 ground1.bmp,我们想按自己的想法在 600x600 地图的各个单元格填上这两个贴图。
既然两个贴图都要被载入,不妨定义一个位图句柄数组 HBITMAP hTexture[2]
.
为了按自己的想法绘出地图,给出一个地图数组,用来表示所绘制材质的下标:
unsigned int myMap[12][12] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1
};
接下来就好做了:
先创建 DC:
hdc = GetDC(hwnd);
mdc = CreateCompatibleDC(hdc);
mMapDC = CreateCompatibleDC(hdc);
hMap = CreateCompatibleBitmap(hdc, CELL_SIZE * 12, CELL_SIZE * 12);
分别载入这两个材质,并绘制到地图上:
SelectObject(mMapDC, hMap);
// STEP 1: 载入材质
for (int i = 0; i < 2; ++i) {
wchar_t filename[20];
wsprintf(filename, L"res/ground%d.bmp", i);
hTexture[i] = (HBITMAP)LoadImage(NULL, filename, IMAGE_BITMAP, CELL_SIZE, CELL_SIZE, LR_LOADFROMFILE);
}
// STEP 2: 绘制好地图
for (int i = 0; i < 12; ++i) { // 行
for (int j = 0; j < 12; ++j) { // 列
SelectObject(mdc, hTexture[myMap[i][j]]);
BitBlt(mMapDC, i * CELL_SIZE, j * CELL_SIZE, CELL_SIZE, CELL_SIZE, mdc, 0, 0, SRCCOPY);
}
}
剩下的一步,就和贴上背景图片一样了:
// STEP 3: 将地图贴到窗口里
SelectObject(mMapDC, hMap);
BitBlt(hdc, 10, 10, CELL_SIZE * 12, CELL_SIZE * 12, mMapDC, 0, 0, SRCCOPY);
完整代码:
VOID Map_Paint(HWND hwnd)
{
hdc = GetDC(hwnd);
mdc = CreateCompatibleDC(hdc);
mMapDC = CreateCompatibleDC(hdc);
hMap = CreateCompatibleBitmap(hdc, CELL_SIZE * 12, CELL_SIZE * 12);
SelectObject(mMapDC, hMap);
HBITMAP hTexture[2]; // 材质句柄
// STEP 0: 地图样式
unsigned int myMap[12][12] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1
};
// STEP 1: 载入材质
for (int i = 0; i < 2; ++i) {
wchar_t filename[20];
wsprintf(filename, L"res/ground%d.bmp", i);
hTexture[i] = (HBITMAP)LoadImage(NULL, filename, IMAGE_BITMAP, CELL_SIZE, CELL_SIZE, LR_LOADFROMFILE);
}
// STEP 2: 绘制好地图
for (int i = 0; i < 12; ++i) { // 行
for (int j = 0; j < 12; ++j) { // 列
SelectObject(mdc, hTexture[myMap[i][j]]);
BitBlt(mMapDC, i * CELL_SIZE, j * CELL_SIZE, CELL_SIZE, CELL_SIZE, mdc, 0, 0, SRCCOPY);
}
}
// STEP 3: 将地图贴到窗口里
SelectObject(mMapDC, hMap);
BitBlt(hdc, 10, 10, CELL_SIZE * 12, CELL_SIZE * 12, mMapDC, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hdc); // 别忘了释放D
}
是不是很简单呢?
现在,在窗口过程函数 WndProc
中调用它:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
BackGround_Paint(hWnd);
Map_Paint(hWnd);
EndPaint(hWnd, &ps);
}
break;
绘制效果:
注意到图中的第一列是数组中的第一行。这样子好像行列反了。将第二步的 hTexture[myMap[i][j]]
改为 hTexture[myMap[j][i]]
.
现在的显示和数组中的一样了:
是不是非常有上古的 2D RPG 的材质感觉了?: )
小憩
这次终于迈出了一大步:绘出游戏地图。下一节或许可以尝试创建一个可以控制移动的主角,并将图片绘制和第一章提出的类关联起来。