ZERO 、
>本章是主要任务是不使用外置图形库,做一个自己的窗口
一、使用环境
使用VS2022,需要包含windows桌面应用程序扩展,
因为我们不使用图形库graphics.h。
二、说明以及预览图
说明:demo做得可能有点粗糙,因为使用的只有C/C++的内置函数,但是基本功能还是实现了。
本来还想加入一些道具元素,但能力有限最后还是鸽了。
写这篇文章的目的其实还是复习一下,然后也是希望在写文章的过程中找到错误,也希望读者能够给我一些建议。
下面是预览图
三、制作教程
1、打开VS,新建项目,找到windows应用程序模板,如下图所示。
进入之后点击调试,你就会看到一个窗口,当然是白色的,什么也没有。
2、修改模板
1)、CreateWindowW 函数
这里我会给大家简单说明一下模板代码中一些比较重要的部分,包括以及要修改的部分。
首先是模板自定义的InitInstance(HINSTANCE, int) 函数,你会发现这个函数里面包含着CreateWindowW() 函数,下面简单介绍一下,搞清这个函数再来下一个注册函数。
_In和_opt顾名思义,一个输入,一个输出; LPCTSTRlpClassName 和LPCTSTRlpWindowName 是窗口类名称和标题,文件开头就有定义,赋值的时候使用的是MyRegisterClass 函数,我们下面再讲; 然后是句柄的含义,详情你们可以去官方API上面寻找,我个人的理解其实就是一个特定的标识符,浅一点来说就是跟人们的身份证雷同。
HWND WINAPI CreateWindow(
_In_opt_ LPCTSTRlpClassName,
_In_opt_ LPCTSTRlpWindowName,
_In_ DWORD dwStyle,
_In_ int x,
_In_ int y,
_In_ int nWidth,
_In_ int nHeight,
_In_opt_ HWNDhWndParent,
_In_opt_ HMENUhMenu,
_In_opt_ HINSTANCEhInstance,
_In_opt_ LPVOIDlpParam
);
前面几个参数很简单,没必要讲; 1.父窗口句柄我们没有,设置为NULL; 2.窗口菜单句柄我们也没有,为NULL; 3.程序实例句柄,可有可无,但我们一般设置为主函数的第一个参数,也就是hInstance 。 4.最后一个没什么用,设置为NULL。 5.最后我们使用的窗口函数就是这样: 6.HWND hwnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WIND_WIDTH, WIND_HEIGHT, nullptr, nullptr, hInstance, nullptr); 其中WIND_WIDTH, WIND_HEIGHT 是你自己定义的参数,表示窗口的大小,其他就是windows自带的类型,具体可以使用CTRL+鼠标左键看源码。
之所以要使用这个函数,除了创造窗口之外,我们还需要函数返回的句柄hwnd,这个相当于唯一标识窗口,以后修改窗口时就需要它传参,因此我们把直接把函数InitInstance(HINSTANCE, int) 去掉,然后把内容复制到主函数即可,贴代码。
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_MODSDEMO, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
hInst = hInstance;
HWND hwnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, WIND_WIDTH, WIND_HEIGHT, nullptr, nullptr, hInstance, nullptr);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
if (!hwnd)
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MODSDEMO));
MSG msg = { 0 };
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
2)、模板创建的MyRegisterClass 的函数
开始我们就看到一个名为wcex 的变量,这里的wcex 并不标识唯一的窗口,标识符还是得靠我们上面提到的hwnd。 下面来介绍一些各个参数
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MODSDEMO));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MODSDEMO);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
1.cbSize :表示窗口大小,使用sizeof即可; 2.style :窗口类型,自行查阅; 3.lpfnWndProc :窗口过程函数,非常重要,举个小栗子,接受到的消息经过翻译是要传到这个函数来的,使用这个函数接受到消息才会引发相应的操作,比如我们的键盘和鼠标输入依靠的就是此函数,模板中应该也写出了,看不懂没关系,有一些我也看不懂,跟着我来 4.cbClsExtra 和cbWndExtra 额外参数,没什么用,设置为0; 5.hInstance :句柄,填主函数第一个参数即可; 6.hIcon :应用程序图标,你们的左上角的东西,你也可以使用如下代码使用自己喜欢的.ico 图标
(HICON)LoadImage(NULL,L"Nitro.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE);
7.hCursor :鼠标的形式,可以有很多取值; 8.hbrBackground :背景颜色,此处设置的为白色,所以我们窗口一片白,我们还可以使用如下函数让窗口灰或白
(HBRUSH)GetStockObject(GRAY_BRUSH);
9.lpszMenuName 和lpszClassName 上面以及解释,这里正好给他俩所在的参数赋值 10.hIconSm :与hIcon 不同的是,它是加载窗口图像。 然后是返回的RegisterClassExW 的窗口注册函数,这一步必不可少。
3)、GetMessage 消息接受函数
这个也是重点,上面我们提到了窗口过程函数,根据接受的消息做出相应的操作,那么我们也还就需要特定的函数用来接受和翻译消息。 GetMessage 函数的特点是,接受消息的时候才会动,同时接受消息,没有消息的时候他是不会动的,但是作为游戏来说,时时刻刻都在运行着,并且每时每刻都要准备接受消息,因此本函数不适合使用在游戏中。 我们使用另外一个消息接受函数PeekMessage ,此函数每时每刻都在接受消息,没有消息就返回false;
BOOL PeekMessage(
LPMSG IpMsg,
HWND hWnd,
UINT wMSGfilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg
);
因此我们这么使用它
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
如果直接用while 里面加PeekMessage ,那么如果没收到消息返回false ,这时候就会退出while 循环导致主函数直接返回,窗口闪一下就会结束,因此我们只能while 里面检测是否是WM_QUIT 退出消息,然后使用if 判断,这样就能一直把PeekMessage 锁在循环里了。 其他代码就不需要动了。
#include "framework.h"
#include "ModsDemo.h"
#define WIND_WIDTH 800
#define WIND_HEIGHT 680
#define MAX_LOADSTRING 100
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
ATOM MyRegisterClass(HINSTANCE hInstance);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_MODSDEMO, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
hInst = hInstance;
HWND hwnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, WIND_WIDTH, WIND_HEIGHT, nullptr, nullptr, hInstance, nullptr);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
if (!hwnd)
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MODSDEMO));
MSG msg = { 0 };
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MODSDEMO));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MODSDEMO);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
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;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
四、End…
一个简单的窗口就做好了,不复杂,但是东西多,记住下面这几点
我文件名字是ModDemo ,其中MAKEINTRESOURCE 中的参数与你的文件名字有关,所以不要直接copy代码,比如我的函数就是MAKEINTRESOURCE(IDC_MODSDEMO) 参数正是IDC_{大写的文件名} 。
用浅墨的规划来说: 窗口创建四部曲:
- 窗口设计
MyRegisterClass 。 - 窗口注册
MyRegisterClass 中的RegisterClassExW - 窗口正式创建
CreateWindowW - 窗口显示和更新
ShowWindow ,UpdateWindow ;
如果有可能第二章就一起实现Game_Init 和Game_Paint 。
|