最近非常喜欢游戏开发中的渲染流水管线,因此,特地网上看了很多资料,闲暇时间想利用C++的桌面开发将其慢慢实现它,废话不多说,下面就来个开胃的,直接贴代码,图片资料放在了文末
ps:本篇主要是创建一个窗口,然后利用双缓冲技术实现一个简单的移动游戏,后续会慢慢更新
#include <iostream>
#include <windows.h>
#pragma comment(lib, "Msimg32.lib")
//定义一些窗口常量
#define _CLIENT_PW 640 //屏幕像素宽
#define _CLIENT_PH 480 //屏幕像素高
#define _FRAME_PER_SECOND 40 //屏幕每秒刷新帧数
#define _FRAME_SLEEPTIME (1000/_FRAME_PER_SECOND) //每帧休息时间
//全局窗口句柄
HWND g_hWnd = nullptr;
//窗口活动
BOOL g_Active = FALSE;
//函数声明
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void GameInit();
void GameRun();
void GameEnd();
//程序入口点
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//填充窗口类别结构体
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = nullptr;
wc.lpszClassName = __TEXT("My3D");
//注册窗口类别结构体
RegisterClass(&wc);
//根据客户区矩形计算窗口矩形
int screen_pw = GetSystemMetrics(SM_CXSCREEN);
int screen_ph = GetSystemMetrics(SM_CYSCREEN);
RECT r =
{
(screen_pw - _CLIENT_PW) / 2,
(screen_ph - _CLIENT_PH) / 2,
r.left + _CLIENT_PW,
r.top + _CLIENT_PH
};
//校准窗口矩形
AdjustWindowRect(&r, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);
//创建窗口
g_hWnd = CreateWindow(
wc.lpszClassName,
__TEXT(""),
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
r.left, r.top, r.right - r.left, r.bottom - r.top, //窗口左上角位置和宽高
HWND_DESKTOP,
NULL,
wc.hInstance,
NULL);
//更新窗口
UpdateWindow(g_hWnd);
//显示窗口
ShowWindow(g_hWnd, nCmdShow);
//游戏初始化
GameInit();
//消息循环
MSG msg = {};
while (WM_QUIT != msg.message)
{
//下面的代码表示,一旦有消息就调用消息处理函数来处理消息
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//当没有消息且窗口激活那么就执行游戏循环
else if (TRUE == g_Active)
{
//游戏运行
GameRun();
}
//当没有消息且窗口未激活就等待消息
else
WaitMessage();
}
//游戏结束
GameEnd();
return (int)msg.wParam;
}
//消息回调函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_ACTIVATEAPP:
{
g_Active = static_cast<BOOL>(wParam);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(1);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//主显示设备
HDC g_MainDC = nullptr;
//后备显示设备
HDC g_BackDC = nullptr;
//位图设备
HDC g_BmpDC[2];
//英雄位置
int x = _CLIENT_PW / 2, y = _CLIENT_PH / 2;
void GameInit()
{
//得到主显示设备
g_MainDC = GetDC(g_hWnd);
//创建后备显示设备
g_BackDC = CreateCompatibleDC(g_MainDC);
//创建和主显示设备兼容的位图
HBITMAP hbmp = CreateCompatibleBitmap(g_MainDC, _CLIENT_PW, _CLIENT_PH);
//将兼容位图选入兼容设备,删除老的位图
DeleteObject(SelectObject(g_BackDC, hbmp));
//创建位图
for (int i = 0; i < 2; ++i)
{
char fn[32];
sprintf_s(fn, "%c.bmp", i + 'a');
HBITMAP hbmp = static_cast<HBITMAP>(LoadImageA(nullptr, static_cast<LPCSTR>(fn), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE));
g_BmpDC[i] = CreateCompatibleDC(g_MainDC);
DeleteObject(SelectObject(g_BmpDC[i], hbmp));
}
}
void GameRun()
{
//得到从操作系统启动一瞬间到目前的毫秒数
ULONGLONG begin_time = GetTickCount64();
//BitBlt()将一幅位图从一个设备场景复制到另一个。源和目标DC相互间必须兼容
BitBlt(
g_BackDC,
0, 0, _CLIENT_PW, _CLIENT_PH,
g_BmpDC[0],
0, 0,
SRCCOPY);
//位图透明化
TransparentBlt(
g_BackDC,
x - 50, y - 50, 100, 100,
g_BmpDC[1],
0, 0, 100, 100,
RGB(0xff, 0xff, 0xff));
if (GetAsyncKeyState('W') & 0x8000)
y -= 5;
if (GetAsyncKeyState('S') & 0x8000)
y += 5;
if (GetAsyncKeyState('A') & 0x8000)
x -= 5;
if (GetAsyncKeyState('D') & 0x8000)
x += 5;
if (GetAsyncKeyState(VK_LBUTTON) & 1)
{
//得到当前光标相对于桌面的坐标
POINT p;
GetCursorPos(&p);
//将桌面的坐标转换到客户区的坐标
ScreenToClient(g_hWnd, &p);
x = p.x;
y = p.y;
}
//后备显示设备颜色传输给主显示设备
BitBlt(
g_MainDC,
0, 0, _CLIENT_PW, _CLIENT_PH,
g_BackDC,
0, 0,
SRCCOPY);
//得到本次游戏循环总的消耗时间
ULONGLONG frame_time = GetTickCount() - begin_time;
//进行本次游戏循环休息
if (_FRAME_SLEEPTIME > frame_time)
Sleep(_FRAME_SLEEPTIME - frame_time);
else
Sleep(1); //必须休息一下
}
void GameEnd()
{
for (int i = 0; i < 2; ++i)
DeleteDC(g_BmpDC[i]);
//删除后备设备,释放主设备
DeleteDC(g_BackDC);
ReleaseDC(g_hWnd, g_MainDC);
}
效果图
a图片
b图片