Before we create our game ,we first need to know how to create a window. This involves the following basic steps
- Define and register a window class that describes the window that we need to make
- Create the window
- Create the message loop to update the window based on input or game logic
- Create an event handler to responds to the events sent by window
Luckily, the code just needs to be written just once. I have also taken a slightly Object Oriented approach.
// Main.cpp
int WINAPI WinMain(const HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd ;
CheckForMemoryLeaks() ;
//Initialize the window class
hwnd = cMainWindow::GetInstance().Init( hInstance, nCmdShow, _T("Test Game"));
if(hwnd == NULL)
{
PostQuitMessage(0) ;
}
cMainWindow::GetInstance().Run();
Cleanup() ;
return 0;
}
// MainWindow.cpp
GRAPHIC_API HWND cMainWindow::Init( const HINSTANCE &hInstance, const int &nCmdShow, LPCTSTR lpWindowTitle )
{
m_hInstance = hInstance;
//m_WndProc = WndProc;
//Register the Window Class
RegisterWin();
//Create the Window
return(CreateMyWindow(nCmdShow, lpWindowTitle)) ;
}
void cMainWindow::RegisterWin()
{
WNDCLASSEX wc ;
wc.cbSize = sizeof(WNDCLASSEX) ;
wc.style = 0 ;
wc.lpfnWndProc = (WNDPROC)cMainWindow::StaticWndProc ;
wc.cbClsExtra = 0 ;
wc.cbWndExtra = 0 ;
wc.hInstance = m_hInstance ;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION) ;
wc.hCursor = LoadCursor(NULL, IDC_ARROW) ;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1) ;
wc.lpszMenuName = NULL ;
wc.lpszClassName = _T("Window") ;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION) ;
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, _T("Window Registration Failed!"), _T("Error!"),MB_ICONEXCLAMATION | MB_OK) ;
exit(0) ;
}
}
HWND cMainWindow::CreateMyWindow( const int &nCmdShow, LPCTSTR lpWindowTitle )
{
#ifdef WINDOWED
// create the window in windowed mode
m_Hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
_T("Window"),
lpWindowTitle,
WS_OVERLAPPEDWINDOW ,
0, 0,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
m_hInstance,
this) ;
#else
// create the window in full screen mode
m_Hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
_T("Window"),
lpWindowTitle,
WS_EX_TOPMOST | WS_POPUP | WS_VISIBLE,
0, 0,
1280,764,
NULL,
NULL,
m_hInstance,
this) ;
#endif
if(m_Hwnd == NULL)
{
MessageBox(NULL, _T("Window Creation Failed!"), _T("Error!"),MB_ICONEXCLAMATION | MB_OK) ;
return NULL ;
}
GetWinRect() ;
ShowWindow(m_Hwnd, nCmdShow) ;
UpdateWindow(m_Hwnd) ;
return m_Hwnd ;
}
void cMainWindow::MoveWin()
{
MoveWindow(m_Hwnd,m_iLeftPos,m_iTopPos,m_iClientWidth,m_iClientHeight,true) ;
}
void cMainWindow::GetWinRect()
{
RECT clientRect, windowRect ;
GetClientRect(m_Hwnd,&clientRect) ;
GetWindowRect(m_Hwnd,&windowRect) ;
m_iClientWidth = (clientRect.right - clientRect.left) ;
m_iClientHeight = (clientRect.bottom - clientRect.top) ;
m_iTopPos = (windowRect.top - clientRect.top) ;
m_iLeftPos = (windowRect.left - clientRect.left) ;
}
First, we define the window class by filling out a WNDCLASSEX structure. This structure contains all the properties of the window that we want to create. The window is registered by calling the function RegisterClassEx.
With the window class registered, we then create the window with a call to the function CreateWindowEx. This is where we specify the size and position of the window along with the Window Styles. Note that we pass this as the last parameter to CreateWindowEx. The reason for this will be explained in just some time. The method to create Windowed and FullScreen application differs. The #ifdef WINDOWED above is used to do just that.
// MainWindow.cpp
void cMainWindow::Run()
{
MSG Msg ;
PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE) ;
// run till completed
while (Msg.message!=WM_QUIT)
{
// is there a message to process?
if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
// dispatch the message
TranslateMessage(&Msg) ;
DispatchMessage(&Msg) ;
}
else
{
//No message to process?
// Then do your game stuff here
}
}
}
// MainWindow.cpp
LRESULT CALLBACK cMainWindow::WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
static BOOL bLtButtonPressed = false ;
PAINTSTRUCT ps ;
HDC hdc ;
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_SIZE:
case WM_MOVE:
cMainWindow::GetInstance().GetWinRect() ;
return 0 ;
case WM_KEYDOWN:
switch(wParam)
{
case VK_ESCAPE:
DestroyWindow(hwnd) ;
break ;
}
return 0 ;
case WM_CLOSE:
DestroyWindow(hwnd) ;
return 0 ;
case WM_DESTROY:
ReleaseCapture() ;
PostQuitMessage(0) ;
return 0 ;
default:
return DefWindowProc(hwnd, msg, wParam, lParam) ;
}
}
LRESULT CALLBACK cMainWindow::StaticWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
if ( msg == WM_CREATE )
{
SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG)((CREATESTRUCT *)lParam)->lpCreateParams );
}
cMainWindow *targetApp = (cMainWindow*)GetWindowLongPtr( hwnd, GWLP_USERDATA );
if ( targetApp )
{
return targetApp->WndProc( hwnd, msg, wParam, lParam );
}
return DefWindowProc( hwnd, msg, wParam, lParam );
}
One feature to notice is that there are two window procedures: WndProc and StaticWndProc. When we fill out the WNDCLASSEX.lpfnWndProc member of the window class, we need to specify a pointer to a function that has a specific function declaration:
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )When the program is compiled, another parameter is added to all non-static member functions, a this pointer, which changes the function declaration so it is incompatible with what is required for the lpfnWndProc member. Static functions on the other hand, do not receive this extra parameter, which is why we set the lpfnWndProc member to StaticWndProc. However, static functions can only access static member variables. Since all the other variables are non-static, we need a way to access them.
If you look at the CreateWindowEx function, the last parameter, lpParam, is defined as a “Pointer to a value to be passed to the window through the CREATESTRUCT structure passed in the lpParam parameter of the WM_CREATE message.” So we can store any type of pointer we want here, such as a this pointer. This pointer, which will be accessible during a WM_CREATE message, could be used to send messages meant for our application to a non-static window procedure, which would allow us to access the non-static data of our class. But if this pointer is only accessible during a WM_CREATE message, we have to store it with the window when the WM_CREATE message arrives so that all future messages will find their way to our non-static window procedure. We can store our this pointer in the user-defined attribute using the SetWindowLongPtr function with the GWLP_USERDATA offset flag. With the this pointer now stored with our window, we can access it in all subsequent messages with the GetWindowLongPtr function. Once the pointer is retreived, we can cast the pointer to a cMainWindow pointer and access all the non-static functions of the class, such as the non-static window procedure. Using this, we route all messages to their corresponding non-static window procedure.That's all there is to creating a window.
CodeBinary
2 comments :
Nice tutorials buddy. :) Ive just started following them.
Keep it up.
Cheers
Hey... Thanks... good to finally get a comment... Even though I havent posted for a while... I guess I will start again
Post a Comment