视频地址https://www.youtube.com/watch?v=jHcz22MDPeE&list=PLv8DnRaQOs5-MR-zbP1QUdq5FL0FWqVzg
?????????首先看入口函数main代码:
#include<OGL3D/Game/OGame.h>
int main()
{
OGame game;
game.Run();
return 0;
}
????????这里交代个关于C++语法的问题,这里OGame game;这行语句实际上是已经构造了一个OGame对象,并且名字叫game,如果想要想C#里面一样使用new关键字的写法的话,应该是这样的:
OGame* game = new OGame();
game->Run();
????????是不是应该可以这样理解,new关键字实际上是新开辟了一个内存的意思,返回的是这块内存的指针,所以实际上是把值赋给了OGame*这个指向OGame类的指针。
????????C#里面的new应该也是一个意思,但是写法上让开发者以为是赋给了一个对象,而不是一个指针,嗯,应该是这样吧。
? ? ? ? 先贴代码:
????????OGame.h
#pragma once
#include<memory>
class OWindow;
class OGame
{
public:
OGame();
~OGame();
void Run();
void Quit();
protected:
bool m_isRunning = true;
std::unique_ptr<OWindow> m_display;
};
?????????OGame.cpp
#include<OGL3D/Game/OGame.h>
#include<OGL3D/Window/OWindow.h>
#include<Windows.h>
OGame::OGame()
{
m_display = std::unique_ptr<OWindow>(new OWindow());
}
OGame::~OGame()
{
}
void OGame::Run()
{
MSG msg;
while (m_isRunning && !m_display->isClosed())
{
if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Sleep(1);
}
}
void OGame::Quit()
{
m_isRunning = false;
}
????????先看一下std::unique_ptr智能指针:
std::unique_ptr<OWindow> m_display;
????????这行代码定义了一个名称为m_display的指向OWindow类型对象的智能指针,需要
#include<memory>;
????????据说这东西能智能释放内存,呵呵,先这么理解吧,将来看效果。
? ? ? ? 与PeekMessage类似的还有一个GetMessage函数,两者的不同在于在没有消息返回的情况下,PeekMessage会返回一个空值,而GetMessage会让程序休眠。还有就是GetMessage在从消息队列中取出消息后,一定会删除消息,但是PeekMessage可以通过最后参数来决定是否移除消息。GetMessage理解为“拿走”了消息,PeekMessage理解为“偷看”了消息,不一定“拿走”。我们这里把PeekMessage的最后一个参数设置为PM_REMOVE,就是“拿走”了的情形。
????????TranslateMessage函数是用来把键盘消息转换为字符消息,并将转换后的新消息重新投递到调用线程的消息队列中。
????????比如说当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数( wParam和 lParam)包含的是 虚拟键代码和扫描码等信息,而 我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage这个函数就可以将WM_KEYDOWN和WM_ KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息。
????????DispatchMessage这网上说得比较复杂,看着头大,我的理解是这个函数把消息发送给了窗口对象中lpfnWndProc属性所对应的函数,具体得分发机制慢慢研究,不过咱这程序里面就一个窗口,想发错了都没机会,没啥好说的。
????????先贴代码:
????????OWindow.h
#include<Windows.h>
class OWindow
{
public:
OWindow();
~OWindow();
void OnDestroy();
bool isClosed();
private:
HWND m_handle = nullptr;
};
????????OWindow.cpp
#include<OGL3D/Window/OWindow.h>
#include <Windows.h>
#include<assert.h>
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
{
OWindow* window = (OWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
window->OnDestroy();
break;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return NULL;
}
OWindow::OWindow()
{
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpszClassName = L"OGL3DWindow";
wc.lpfnWndProc = WndProc;
assert(RegisterClassEx(&wc));
RECT rc = { 0,0,1024,768 };
AdjustWindowRect(&rc, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, false);
m_handle = CreateWindowEx(NULL, L"OGL3DWindow", L"Parcode | OpenGL 3D Game", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT,
rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, NULL, NULL);
assert(m_handle);
SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)this);
ShowWindow(m_handle, SW_SHOW);
UpdateWindow(m_handle);
}
OWindow::~OWindow()
{
DestroyWindow(m_handle);
}
void OWindow::OnDestroy()
{
m_handle = nullptr;
}
bool OWindow::isClosed()
{
return !m_handle;
}
? ? ? ? 字符串前面为什么加L?表示奖ANSI字符串转换成unicode字符串,这样每个字符占用两个字节。
????????RegisterClassEx这个似乎就是用来注册窗口用的,大概就是将窗口的指针添加到一个列表里面,以方便遍历查找吧。RegisterClassEx依据编译环境来决定替换为RegisterClassExA或者RegisterClassExW。如果使用RegisterClassExA来注册窗口类,应用程序通知系统被注册类的窗回的消息使用ANSI字符集的文本和字符参数;如果使用RegisterClassExW来注册窗口类,应用程序需要系统以Unicode来传递消息的文本参数。也就是是在当前的环境下RegisterClassEx应该是等同于RegisterClassExW,这样要求消息字符串必须是unicode字符串,这就看出来前面说的消息字符串前面加字母L的必要性。
????????SetWindowLongPtr函数用来设置一个窗口的扩展风格或者额外数据。这个函数兼容32位和64位版本的Windows系统。这里是把指向OWindow对象的指针转化为LONG_PRT类型并保存在窗口数据偏移了GWLP_USERDATA这个位置。好吧,不是很理解,总之指向OWindow对象的指针被保存了,将来需要的时候能取出来,并以此来访问到OWindow对象就是了。我们可以从WinProc方法里面看到对应于SetWindowLongPtr函数的GetWindowLongPtr函数是如何取值并调用方法的:
OWindow* window = (OWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
window->OnDestroy();