《WINDOWS游戏编程之从零开始》第四章学习笔记

GDI

GDI 有一些基本函数和图形定义。总之,其就是一些数据结构、函数和宏组成的整体。而各种各样的函数都逃不开一个概念——设备环境 DC.

4.2 设备环境

一个设备环境,就是程序中一个可以进行绘图的地方。也就是“客户区”。

在 GDI 图像输出时,我们只需要关心设备环境的类型。

而要确定设备环境的类型,创建一个相应类型的句柄即可。(不得不说 C++ 的句柄设计的挺好的)

4.2.2 获取 HDC (设备环境句柄)

  1. 使用 BeginPaintEndPaint

    在微软文档中查到这两个函数的原型:

    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;
    ...
    
  2. 直接使用 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 位图

位图绘制四步曲:

  1. 从文件中加载位图对象(给位图句柄)
  2. 建立一个与窗口 DC 兼容的内存 DC.
  3. 选用位图对象
  4. 进行贴图. 将内存 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 开发何尝不是如此呢?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值