从零开始用 Windows C++ 桌面程序制作方舟同人游戏(二)

现在实现地图贴图。

因为没有拿到手绘板,所以用随便做了两个材质用于测试:


ground0.bmp

ground1.bmp

再次看源文件 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); // 分配消息并响应
        }
    }

要显示地图,先想一想我们的基本步骤:

  1. 将材质文件加载到一个内存 DC 中;
  2. 将地图指定到一个内存 DC 中,然后用材质刷上去;
  3. 将地图的内存 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 的材质感觉了?: )

小憩

这次终于迈出了一大步:绘出游戏地图。下一节或许可以尝试创建一个可以控制移动的主角,并将图片绘制和第一章提出的类关联起来。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值