磨人的学习过程,快乐的学习结果——基于Win32API的一个切换图片小游戏

起因:

寻常的一天我依然在被大数据推送各种各样的教学视频,它们大多数都是培训机构骗钱的垃圾,但是也有不少富有诚意的介绍知识的作品,就是这一天前我看到了一个介绍OpenGL和DirectX的视频,萌生了进一步了解的想法,于是在b站上搜索一个介绍DirectX的视频,没想到那个老外开场就讲Win32创建窗口,讲得头头是道而且很有知识密度,飞快地就给我把创建窗口的过程给过过了。

然而最重要的是,他会指引你在微软官方文档 msdn 中查阅资料来学习。

配合着这些资料我掌握了Win32API的基础

DirectX3D 11 这个视频听不下去了是因为C++编程的技巧还没上来

一个超级啰嗦的Win32视频 巴不得揉碎了塞我嘴里,知识密度太低了,但是很细,也应该按照他的目录来学,静态库后面没学了,感觉用不上但是不知道自己错过了什么。

学了一些东西之后就想要把想法输出出来了,恰好之前玩游戏遇到一个**轮盘赌的场景。于是一个简单的小游戏雏形就有了:通过两个按钮猜硬币,猜对了就切换 CG

学习过程中解决的问题:

在客户区画图片

用到了 LoadBitmap() 函数,值得一提的是直接把 PNG 图片加入资源不可行,必须要用 BMP 格式,使用格式工厂转换后图片的占用内存变为原来的 230% 。

我写的一个画CG的函数,需要传入绘图设备句柄

void Draw_CG(HDC hdc)
{
    HBITMAP hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(rc_stages[STAGE]));//指定资源
    if (!hBmp) { OutputDebugString(L"LoadBitmap failed!"); return; }
    HDC hMemdc = CreateCompatibleDC(hdc);//创建一个内存DC,在虚拟地区绘图
    HGDIOBJ nOldBmp = SelectObject(hMemdc, hBmp);//将位图数据送给内存DC
    BitBlt(hdc, 0, 0, 1024, 768, hMemdc, 0, 0, SRCCOPY);//将虚拟区域中绘制好的图像成像到窗口中

    SelectObject(hMemdc, hBmp);
    DeleteObject(hBmp);
    DeleteDC(hMemdc);
}

画文本

和画位图是一个原理,应该写到一起。

值得一提的是填的是一个 wchar_t 类型的字符串。

void Draw_Text(HDC hdc)
{
    const wchar_t text[] = L"这里填字符串";
    RECT rc;
    rc = { WD_WIDTH - BT_WIDTH - 70, 200,WD_WIDTH-10,250 };
    DrawText(hdc, text, sizeof(text)/sizeof(wchar_t) - 1, &rc, DT_LEFT | DT_TOP);
}

画按钮,处理按钮事件

根据 MSDN 的文档,画按钮和画窗口一样,也要用 CreateWindow() 函数,处理事件则是要给他一个菜单 ID ,最后会在消息处理机制函数中得到一个 WM_COMMAND 消息,它的 wParam 参数用 LOWORD 函数处理后会变成我们想要的菜单 ID。在 switch 中处理就好。

刷新图片的方法

由于是在客户区用hdc绘图设备绘图,向导创建的代码只能在 WM_PAINT 消息时处理重绘的任务。然而这个消息的发送只在窗口大小改变和重现时,为了让他执行强制刷新,我们要先无效化客户区,然后自动就会发送 WM_PAINT 了。

RECT rect = { 0, 0, 1024, 768 };//一个结构体用来指定客户区区域
InvalidateRect(hWnd, &rect,TRUE);//无效化这个客户区,这时会发送WM_PAINT消息使其重画
UpdateWindow(hWnd);//不知道为什么去了这行也行

调试技术

使用 ostringstream ,这是C++的一个字符集操作模板类,定义在 sstream.h 头文件中。 ostringstream 类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓冲区,替代 sprintf 。

然而使用它需要一个把 string 转换成 LPWCSTR 的函数:

LPCWSTR stringToLPCWSTR(std::string orig)
{
	wchar_t* wcstring = 0;
	try
	{
		size_t origsize = orig.length() + 1;
		const size_t newsize = 100;
		size_t convertedChars = 0;
		if (orig == "")
		{
			wcstring = (wchar_t*)malloc(0);
			mbstowcs_s(&convertedChars, wcstring, origsize, orig.c_str(), _TRUNCATE);
		}
		else
		{
			wcstring = (wchar_t*)malloc(sizeof(wchar_t) * (orig.length() - 1));
			mbstowcs_s(&convertedChars, wcstring, origsize, orig.c_str(), _TRUNCATE);
		}
	}
	catch (std::exception e)
	{
	}
	return wcstring;
}

然后我们就可以这么写了

std::ostringstream oss;
oss << "now stage = " << STAGE << '\n';//在控制台中查看想调试的变量
OutputDebugString(stringToLPCWSTR(oss.str()));

在上面的那个老外的视频里,他给出了一个可以实时输出触发的消息类型的模块,有兴趣的可以去看看那个视频win32部分

编写窗口程序的流程

我的评价是直接用 Visual Studio 自动创建代码,然后改一改就行

创建、注册窗口类

会创建一个 WNDCLASS 的结构体变量,然后根据文档填上它的参数。

自动创建的话会是一个 WNDCLASSEXW 的加强版

然后使用 RegisterClass() 函数注册窗口类

创建、显示窗口

    HWND hWnd = CreateWindowW(
        szWindowClass, 
        szTitle, 
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, WD_WIDTH, WD_HEIGHT, //位置,宽高
        nullptr, 
        nullptr, 
        hInstance,
        nullptr);
创建按钮

直接抄的文档里面的示例代码改了改

    HWND hwndButton1 = CreateWindowW(
        L"BUTTON",                                              // Predefined class; Unicode assumed 
        L"猜正面",                                              // Button text 
        WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  // Styles 
        WD_WIDTH - BT_WIDTH - 40,                               // x position 
        WD_HEIGHT - 2 * BT_HEIGHT - 30 - 100,                   // y position 
        BT_WIDTH,                                               // Button width
        BT_HEIGHT,                                              // Button height
        hWnd,                                                   // Parent window
        (HMENU)IDB_LEFT,                                        // 用这个参数来设置控件的ID,这个宏是自己定义的,照猫画虎
        (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),
        NULL);                                                  // Pointer not needed.
显示并刷新窗口
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

消息处理机制

你需要编写一个函数,把它填到注册窗口类里面,自动创建的代码已经帮我们写好了

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    //static WindowsMessageMap mm;
    //OutputDebugString(stringToLPCWSTR( mm(message, lParam, wParam)));//外置调试

    roll_coin();
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 分析菜单选择:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            //处理两个按钮的点击事件,注意按钮是和菜单的事件一起的
            case IDB_LEFT:
            {
                Button_event(hWnd, 1);
                break;

            }
            case IDB_RIGHT:
            {
                Button_event(hWnd, 0);
                break;
            }


            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            Draw_CG(hdc);
            Draw_Text(hdc);
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

其他

无非就是一个随机数然后切换CG的机制,非常的简单。但是生成的伪随机数可能导致容易连续获胜或者连续输。
关于游戏美术资源:提取自兰斯1重制版,版权属于AliceSoft株式会社

成品效果:

被毙了,悲

代码

感觉传上来有点苕皮:下载地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值