GDI
GDI 有一些基本函数和图形定义。总之,其就是一些数据结构、函数和宏组成的整体。而各种各样的函数都逃不开一个概念——设备环境 DC.
4.2 设备环境
一个设备环境,就是程序中一个可以进行绘图的地方。也就是“客户区”。
在 GDI 图像输出时,我们只需要关心设备环境的类型。
而要确定设备环境的类型,创建一个相应类型的句柄即可。(不得不说 C++ 的句柄设计的挺好的)
4.2.2 获取 HDC (设备环境句柄)
-
使用
BeginPaint
与EndPaint
在微软文档中查到这两个函数的原型:
HDC BeginPaint( [in] HWND hWnd, [out] LPPAINTSTRUCT lpPaint );
BOOL EndPaint( [in] HWND hWnd, [in] const PAINTSTRUCT *lpPaint );
分别是窗口句柄和一个 lpPaint.
一个实例:
HDC g_hdc; // 注意匈牙利命名法的g_前缀表示全局变量. ... case WM_PAINT: g_hdc = BeginPaint(hwnd, &paintStruct); Game_Paint(); EndPaint(hwnd, &paintStruct); ValidateRect(hwnd, NULL); break; ...
-
直接使用
GetDC
,并使用ReleaseDC
进行 DC 释放.HDC g_hdc; g_hdc = GetDC(hWnd); ... ReleaseDC(hWnd, g_hdc);
4.3
4.4
写出一个 GDI 程序通用框架,只是一个框架,将上一节的 GameCore 中的 main 拷贝过来进行修改得到:
//-------------------【程序说明】-------------------
// 程序名称:GDIdemoCore
// Learn from 浅墨 orz
// 2022 年 2 月 Created by Eke
// 描述:实现 GDI 游戏开发所需的核心程序
//--------------------------------------------------
//-----------------【头文件部分】---------------------
// 描述:程序依赖的头文件
//--------------------------------------------------
#include <Windows.h>
//-----------------【宏定义部分】--------------------
// 描述:定义辅助宏
//--------------------------------------------------
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
constexpr auto WINDOW_TITLE = L"谁准备在滑索上飞行?我!"; // 新版本中使用constexpr比define更安全.
//----------------【全局函数声明部分】----------------
// 描述:全局函数声明.
//--------------------------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL Game_Init(HWND hwnd); // 资源初始化
VOID Game_Paint(HWND hwnd); // 绘图代码书写
BOOL Game_CleanUp(HWND hwnd); // 资源清理
//----------------【全局变量声明部分】----------------
// 描述:全局变量声明.
//--------------------------------------------------
HDC g_hdc = NULL;
//----------------【WinMain( )函数】-----------------
// 描述:程序主入口
//--------------------------------------------------
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) // 需要将 _In_, _In_opt_ 依照函数原型加上,否则会报警告. 不过为啥呢?
{
// 四部曲
// 1. Design (Oh 11 paras!)
WNDCLASSEX wndClass = { 0 }; // 定义一个窗口类
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = (HICON)::LoadImage(NULL, L"favicon.ico", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // 试了下没有RED\BLUE 的brush,qwq
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop";
// 2. Register
if (!RegisterClassEx(&wndClass)) return -1;
// 3. Create
HWND hwnd = CreateWindow(L"ForTheDreamOfGameDevelop", WINDOW_TITLE,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);
// 4. Move, show and update
MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
if (!Game_Init(hwnd))
{
MessageBox(hwnd, L"资源初始化失败", L"消息", 0);
return FALSE;
}
// 5. msg loop
MSG msg = { 0 };
while (msg.message != WM_QUIT);
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// 6. unregister
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance);
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paintStruct;
switch (message)
{
case WM_PAINT:
g_hdc = BeginPaint(hwnd, &paintStruct);
Game_Paint(hwnd);
EndPaint(hwnd, &paintStruct);
ValidateRect(hwnd, NULL);
break;
case WM_KEYDOWN:
if (wParam == VK_ESCAPE) DestroyWindow(hwnd);
break;
case WM_DESTROY:
Game_CleanUp(hwnd);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
BOOL Game_Init(HWND hwnd)
{
g_hdc = GetDC(hwnd);
Game_Paint(hwnd);
ReleaseDC(hwnd, g_hdc);
return TRUE;
}
VOID Game_Paint(HWND hwnd)
{
}
BOOL Game_CleanUp(HWND hwnd)
{
return TRUE;
}
ok. 留作备用.
4.5 GDI 基本几何绘图
看似不太重要的一节。
大概浏览了一下,也是相似的一些接口函数。先不在笔记里记录。
(也没什么难点)
主要是要搞清楚画笔和画刷:
画笔是用于勾勒线条、形状轮廓以及颜色的;
画刷用于创建实心形状和呈现文本.
就是说,画笔决定颜色和形状,而画刷在画笔画好的图形上“绣花”.
4.6 随机数
没啥看点,熟悉。
熟悉完随机之后,写一个示例程序 GDIdemo1,一个画笔与画刷的使用演示程序。
其中还有背景音乐的播放,不过不是使用的 PlaySound() 函数,而是使用了 MCI:
//-------------------【程序说明】-------------------
// 程序名称:GDIdemoCore
// Learn from 浅墨 orz
// 2022 年 2 月 Created by Eke
// 描述:实现 GDI 游戏开发所需的核心程序
//--------------------------------------------------
//-----------------【头文件部分】---------------------
// 描述:程序依赖的头文件
//--------------------------------------------------
#include <Windows.h>
#include <time.h>
#include <mmsystem.h>
#pragma comment (lib, "winmm.lib") // 链接 winmm 库.
//-----------------【宏定义部分】--------------------
// 描述:定义辅助宏
//--------------------------------------------------
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
constexpr auto WINDOW_TITLE = L"谁准备在滑索上飞行?我!"; // 新版本中使用constexpr比define更安全.
//----------------【全局函数声明部分】----------------
// 描述:全局函数声明.
//--------------------------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL Game_Init(HWND hwnd); // 资源初始化
VOID Game_Paint(HWND hwnd); // 绘图代码书写
BOOL Game_CleanUp(HWND hwnd); // 资源清理
//----------------【全局变量声明部分】----------------
// 描述:全局变量声明.
//--------------------------------------------------
HDC g_hdc = NULL;
HPEN g_hPen[7] = { 0 };
HBRUSH g_hBrush[7] = { 0 };
int g_iPenStyle[7] = { PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT, PS_DASHDOTDOT, PS_NULL, PS_INSIDEFRAME };
int g_iBrushStyle[6] = { HS_VERTICAL, HS_HORIZONTAL, HS_CROSS, HS_DIAGCROSS, HS_FDIAGONAL, HS_BDIAGONAL };
//----------------【WinMain( )函数】-----------------
// 描述:程序主入口
//--------------------------------------------------
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) // 需要将 _In_, _In_opt_ 依照函数原型加上,否则会报警告. 为啥呢?
{
// 四部曲
// 1. Design (Oh 11 paras!)
WNDCLASSEX wndClass = { 0 }; // 定义一个窗口类
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = (HICON)::LoadImage(NULL, L"favicon.ico", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 试了下没有RED\BLUE 的brush,qwq
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop";
// 2. Register
if (!RegisterClassEx(&wndClass)) return -1;
// 3. Create
HWND hwnd = CreateWindow(L"ForTheDreamOfGameDevelop", WINDOW_TITLE,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);
// 4. Move, show and update
MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
/* 这里书中使用 PlaySound 函数来播放背景音乐,可惜这个函数只支持 .wav 格式.
所以我使用了 mci 来进行声音播放,功能强大,支持mp3. */
mciSendString(TEXT("open 打上花火.mp3 alias BackMusic"), NULL, 0, NULL); // 打开音乐
mciSendString(L"play BackMusic repeat", NULL, 0, NULL); // 开始播放
if (!Game_Init(hwnd))
{
MessageBox(hwnd, L"资源初始化失败", L"消息", 0);
return FALSE;
}
// 5. msg loop
MSG msg = { 0 };
while (msg.message != WM_QUIT);
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
mciSendString(L"stop BackMusic", NULL, 0, NULL); // 停止播放
mciSendString(L"close BackMusic", NULL, 0, NULL); // 关闭音乐
// 6. unregister
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance);
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paintStruct;
switch (message)
{
case WM_PAINT:
g_hdc = BeginPaint(hwnd, &paintStruct);
Game_Paint(hwnd);
EndPaint(hwnd, &paintStruct);
ValidateRect(hwnd, NULL);
break;
case WM_KEYDOWN:
if (wParam == VK_ESCAPE) DestroyWindow(hwnd);
break;
case WM_DESTROY:
Game_CleanUp(hwnd);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
BOOL Game_Init(HWND hwnd)
{
g_hdc = GetDC(hwnd);
srand((unsigned)time(NULL));
for (int i = 0; i <= 6; ++i) {
g_hPen[i] = CreatePen(g_iPenStyle[i], 1, RGB(rand() % 256, rand() % 256, rand() % 256));
if (i == 6) {
g_hBrush[i] = CreateSolidBrush(RGB(rand() % 256, rand() % 256, rand() % 256));
}
else {
g_hBrush[i] = CreateHatchBrush(g_iBrushStyle[i], RGB(rand() % 256, rand() % 256, rand() % 256));
}
}
Game_Paint(hwnd);
ReleaseDC(hwnd, g_hdc);
return TRUE;
}
VOID Game_Paint(HWND hwnd)
{
int y = 0;
for (int i = 0; i <= 6; ++i) {
y = (i + 1) * 70;
SelectObject(g_hdc, g_hPen[i]); // 选择好画笔. 这里选择的主体是谁呢? g_hdc,是我们的 DC.
MoveToEx(g_hdc, 30, y, NULL); // move cursor to (30, y)
LineTo(g_hdc, 100, y); // from pos to (100, y)
}
/* now y = 420 */
int x1 = 120, x2 = 190;
// 好,画笔画好了,现在需要用画刷填充画出来的矩形.
for (int i = 0; i <= 6; ++i) {
SelectObject(g_hdc, g_hBrush[i]);
Rectangle(g_hdc, x1, 70, x2, y);
x1 += 90, x2 += 90;
}
}
BOOL Game_CleanUp(HWND hwnd)
{
for (int i = 0; i <= 6; ++i) {
DeleteObject(g_hPen[i]);
DeleteObject(g_hBrush[i]);
}
return TRUE;
}
某次运行的效果:
(此处应有 DAOKO 与八爷的《打上花火》BGM…)
4.7 文字
最常用文字输出函数:TextOut
.
了解你的进阶输出函数:DrawText
.
这个函数在指定的矩形里写入格式化文本,并对文本进行格式化一些格式化.
SetTextColor
: 设置文字的颜色值. 注意其参数只有 DC 句柄和文本颜色. ( 天呐,这不就意味着一个设备环境只能有一个颜色了吗 )
SetBkMode
:设置文字背景模式. 通常设置为 TRANSPARENT(透明)。如果不调用这个函数,默认文字背景为白色。
CreateFont
:字体创建函数. 字体在创建后使用 SelectObject
选进设备环境即可。
第二个示例程序:我们只修改了 GamePaint
函数.
VOID Game_Paint(HWND hwnd)
{
HFONT hFont = CreateFont(30, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, L"微软雅黑"); // 创建一种字体
SelectObject(g_hdc, hFont);
SetBkMode(g_hdc, TRANSPARENT);
wchar_t text1[] = L"飞身乘上时代的风暴,今夜要成为台风之眼!";
wchar_t text2[] = L"この風に飛び乗って、今夜台風の目となって!";
wchar_t text3[] = L"------King GNU 《飛行艇》";
SetTextColor(g_hdc, RGB(50, 255, 50));
TextOut(g_hdc, 30, 150, text1, wcslen(text1));
SetTextColor(g_hdc, RGB(50, 50, 255));
TextOut(g_hdc, 30, 200, text2, wcslen(text2));
SetTextColor(g_hdc, RGB(255, 150, 50));
TextOut(g_hdc, 500, 250, text3, wcslen(text3));
DeleteObject(hFont);
}
运行效果:
4.8 位图
位图绘制四步曲:
- 从文件中加载位图对象(给位图句柄)
- 建立一个与窗口 DC 兼容的内存 DC.
- 选用位图对象
- 进行贴图. 将内存 DC 的内容贴到窗口 DC 中.
加载位图
使用 LoadImage
函数. 其作用就是将一个图片加载到位图句柄中.
建立兼容DC
使用 CreateCompatibleDC
函数. 其作用就是创建一个与窗口 DC 兼容的内存 DC.
内存 DC 在使用后释放,调用的是 DleteDC()
而不是 ReleaseDC()
, 可以参考下方链接:
DeleteDC() 与 ReleaseDC() 的区别 转 - vranger - 博客园 (cnblogs.com)
选用位图对象
SelectObject()
.
进行贴图
必须要熟悉好贴图函数,例如 BitBlt()
, TransparentBlt()
, StrentchBlt()
.
接下来,在 GameInit()
, GamePaint()
中加上这四步,就大功告成了。别忘了释放。释放在 Game_CleanUp()
中.
BOOL Game_Init(HWND hwnd)
{
g_hdc = GetDC(hwnd);
// STEP1
g_hBitmap = (HBITMAP)LoadImage(NULL, L"11.bmp", IMAGE_BITMAP, 800, 600, LR_LOADFROMFILE);
// STEP2
g_mdc = CreateCompatibleDC(g_hdc);
Game_Paint(hwnd);
ReleaseDC(hwnd, g_hdc);
return TRUE;
}
VOID Game_Paint(HWND hwnd)
{
// STEP3
SelectObject(g_mdc, g_hBitmap);
// STEP4
BitBlt(g_hdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, g_mdc, 0, 0, NOTSRCCOPY);
HFONT hFont = CreateFont(30, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, L"微软雅黑"); // 创建一种字体
SelectObject(g_hdc, hFont);
SetBkMode(g_hdc, TRANSPARENT);
wchar_t text1[] = L"飞身乘上时代的风暴,今夜要成为台风之眼!";
wchar_t text2[] = L"この風に飛び乗って、今夜台風の目となって!";
wchar_t text3[] = L"------King GNU 《飛行艇》";
SetTextColor(g_hdc, RGB(50, 255, 50));
TextOut(g_hdc, 30, 150, text1, wcslen(text1));
SetTextColor(g_hdc, RGB(50, 50, 255));
TextOut(g_hdc, 30, 200, text2, wcslen(text2));
SetTextColor(g_hdc, RGB(255, 150, 50));
TextOut(g_hdc, 500, 250, text3, wcslen(text3));
DeleteObject(hFont);
}
BOOL Game_CleanUp(HWND hwnd)
{
DeleteObject(g_hBitmap);
DeleteDC(g_mdc);
return TRUE;
}
第三个示例程序:
总结
似乎讲到现在就是一系列的接口+函数。我觉得,使用 GDI 开发何尝不是如此呢?