1: LPDIRECTINPUTDEVICE8 m_pdInputMouse;
Like in the last tutorial, we will have to make sure the DirectInput Interface is created before we can bind the mouse.
1: LPDIRECTINPUTDEVICE8 m_pdInputMouse;
1: LPDIRECTINPUT8 m_pdInput;
11: CreateKeyboard();
12: ...
13: }
1: void cInput::CreateKeyboard()
2: {
3: // create the keyboard device
4: m_pdInput->CreateDevice(GUID_SysKeyboard,&m_pdInputKeyboard, NULL);
5:
6: // set the data format to keyboard format
7: m_pdInputKeyboard->SetDataFormat(&c_dfDIKeyboard);
8:
9: // set the control over the keyboard
10: m_pdInputKeyboard->SetCooperativeLevel(m_hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
11: DetectKeys();
12: }
1: void cInput::LockKey( const DWORD dwKey )
2: {
3: m_bLockedKeys[dwKey] = true;
4: }
1: void cInput::DetectKeys()
2: {
3: m_pdInputKeyboard->Acquire();
4:
5: m_pdInputKeyboard->GetDeviceState(256, (LPVOID)m_cKeyState);
6:
7: for (unsigned int iKey=0;iKey<256;iKey++)
8: {
9: if (m_cKeyState[iKey] & 0x80)
10: {
11: // key is pressed if it isnt locked
12: m_bPressedKeys[iKey] = !(m_bLockedKeys[iKey]);
13: }
14: else
15: {
16: m_bPressedKeys[iKey] = false;
17: m_bLockedKeys[iKey] = false;
18: }
19: }
20: }
1: void cInput::Cleanup()
2: {
3: m_pdInputKeyboard->Unacquire();
4: m_pdInputKeyboard->Release();
5:
6: m_pdInput->Release();
7: }
Its been ages since the game dev related post so I thought writing a tutorial for displaying the sprites should remedy that.
The Sprite class is a wrapper around the D3dXSprite class and will contain all the code necessary to display a sprite on screen.
The member variables required for implementing the sprite class are shown below.
1: LPD3DXSPRITE m_pSprite; 2: LPDIRECT3DTEXTURE9 m_pTexture; 3: UINT m_uiHeight; 4: UINT m_uiWidth; 5: D3DXVECTOR3 m_vScale; 6: D3DXVECTOR3 m_vPosition; 7: D3DXMATRIX m_mScaleMatrix;
Now, the first step is to initialize the sprite. This can be done in the following manner.
1: void cSprite::Init( LPDIRECT3DDEVICE9 const pDevice, LPCTSTR strFilename )
2: { 3: if (m_pSprite)
4: { 5: Cleanup(); 6: }
7: // Create the Sprite
8: if (FAILED( D3DXCreateSprite(pDevice, &m_pSprite)))
9: { 10: //error
11: }
13: // Create the texture associated with this sprite
14: if(FAILED(D3DXCreateTextureFromFile(pDevice, strFilename, &m_pTexture)))
15: { 16: MessageBox(NULL, strFilename, _T("Texture creation failed"), MB_OK ) ;
17: PostQuitMessage(0); 18: }
20: D3DXIMAGE_INFO imageInfo; // contents of the image file
21: 22: // get the contents of the image file
23: D3DXGetImageInfoFromFile(strFilename, &imageInfo); 24: 25: //get the image height and width
26: m_uiHeight = imageInfo.Height; 27: m_uiWidth = imageInfo.Width; 28: }
1: void cSprite::DrawSprite( LPDIRECT3DDEVICE9 const pDevice, const D3DXVECTOR3& vPosition, const DWORD dwFlags /*= NULL*/, const D3DCOLOR& tint /*= WHITE*/, const RECT* pSrcRect /*= NULL*/ )
2: { 3: 4: // get the new position and create the transform matrix
5: if (m_vPosition != vPosition)
6: { 7: D3DXMATRIX transMatrix; 8: D3DXMatrixTranslation(&transMatrix, vPosition.x, vPosition.y, vPosition.z); 9: D3DXMatrixMultiply(&transMatrix, &m_mScaleMatrix, &transMatrix); 10: m_vPosition = vPosition ; 11: m_pSprite->SetTransform(&transMatrix); 12: } 13: 14: // draw the sprite
15: m_pSprite->Begin(dwFlags); 16: m_pSprite->Draw(m_pTexture, pSrcRect, NULL, NULL, tint); 17: m_pSprite->End(); 18: }
The last part remaining is to release the resources when we are done using them. This is as follows
1: void cSprite::Cleanup()
2: { 3: // release the texture
4: SAFE_RELEASE(m_pTexture); 5: 6: // release the sprite
7: SAFE_RELEASE(m_pSprite); 8: }
A timing class would help in creating a constant experience over multiple systems. We can also use our timing class to calculate the framerate of our demos.
The timer class will keep track of the total running time, time elapsed between Update calls and the application's framerate.
// Timer.cpp
cTimer::cTimer()
: m_iCurrentTime(0)
, m_iLastTime(0)
, m_iLastFPSUpdate(0)
, m_iNumFrames(0)
, m_fFPS(0.0f)
, m_fRunningTime(0.0f)
, m_fTimeElapsed(0.0f)
, m_bTimerStopped(true)
{
QueryPerformanceFrequency( (LARGE_INTEGER *)&m_iTicksPerSecond );
m_iFPSUpdateInterval = m_iTicksPerSecond >> 1;
}
void cTimer::Start()
{
if ( !m_bTimerStopped )
{
// Already started
return;
}
QueryPerformanceCounter( (LARGE_INTEGER *)&m_iLastTime );
m_bTimerStopped = false;
}
void cTimer::Stop()
{
if ( m_bTimerStopped )
{
// Already stopped
return;
}
INT64 iStopTime = 0;
QueryPerformanceCounter( (LARGE_INTEGER *)&iStopTime );
m_fRunningTime += (float)(iStopTime - m_iLastTime) / (float)m_iTicksPerSecond;
m_bTimerStopped = true;
}
void cTimer::Update()
{
if ( m_bTimerStopped )
{
return;
}
// Get the current time
QueryPerformanceCounter( (LARGE_INTEGER *)&m_iCurrentTime );
m_fTimeElapsed = (float)(m_iCurrentTime - m_iLastTime) / (float)m_iTicksPerSecond;
m_fRunningTime += m_fTimeElapsed;
// Update FPS
m_iNumFrames++;
if ( m_iCurrentTime - m_iLastFPSUpdate >= m_iFPSUpdateInterval )
{
float fCurrentTime = (float)m_iCurrentTime / (float)m_iTicksPerSecond;
float fLastTime = (float)m_iLastFPSUpdate / (float)m_iTicksPerSecond;
m_fFPS = (float)m_iNumFrames / (fCurrentTime - fLastTime);
m_iLastFPSUpdate = m_iCurrentTime;
m_iNumFrames = 0;
}
m_iLastTime = m_iCurrentTime;
}
To calculate the elapsed time since the last update, we need to get the difference between the current counter value and the counter value from the last update.To convert the result into seconds, we divide it by the number of ticks the counter fires per second. The total running time is simply updated each frame with the current elapsed time value.
Calculating the frames per second involves a few steps. First, to prevent the FPS from updating every frame, we need to store an FPS update interval. If we updated the FPS every frame, it would change too fast for us to see any single value. The update interval is set to half the counter frequency. This means the timer will perform the FPS calculation every half second. Second, we need a variable that counts the number of times the Update method is called. If we call Update every frame, it will represent the number of frames rendered. To calculate the FPS, we divide this frame counter by the time passed since the last time the FPS was calculated. Once the FPS is calculated, we reset the frame counter to 0 and repeat.// MainWindow.cpp
void cMainWindow::OnRender()
{
HRESULT hr;
// update the game timer
m_pGameTimer->Update();
hr = cDXBase::GetInstance().BeginRender();
if (SUCCEEDED(hr))
{
m_pGameApp->Render(m_pGameTimer->GetElapsedTime());
cDXBase::GetInstance().EndRender(hr);
}
}
After creating the window, we need to give it DirectX capabilities. To get access to DirectX graphics, we need to include the header file d3d9.h. We also need to add the header file d3dx9.h, as it contains a lot of useful functions and macros.
Following the OOP methodology, I created a class which deals with the creation and release of the devices, handling lost devices and the rendering.
// DxBase.cpp
void cDXBase::Init( const HWND hWnd )
{
m_Hwnd = hWnd;
DirectxInit() ;
#ifdef WINDOWED
SetParameters(false) ;
#else
SetParameters(true) ;
#endif
CreateDirectxDevice() ;
}
void cDXBase::DirectxInit()
{
//create the Direct3d Object
m_pD3D = Direct3DCreate9(D3D_SDK_VERSION) ;
if(m_pD3D == NULL)
{
MessageBox(NULL, _T("Direct3d object creation failed!"), _T("Error!"), MB_ICONEXCLAMATION | MB_OK) ;
}
// get the display mode
m_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &m_displayMode );
// get the device caps
m_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &m_Caps) ;
}
// DxBase.cpp
void cDXBase::SetParameters(const BOOL bFullScreen)
{
ZeroMemory(&m_d3dpp, sizeof(m_d3dpp)) ;
m_d3dpp.BackBufferCount = 1 ;
m_d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE ;
m_d3dpp.MultiSampleQuality = 0 ;
m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD ;
m_d3dpp.hDeviceWindow = m_Hwnd ;
m_d3dpp.Flags = 0 ;
m_d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT ;
m_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE ;
m_d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8 ; //pixel format
m_d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8 ; // depth format
m_d3dpp.EnableAutoDepthStencil = true ;
if(bFullScreen)
{
// if its a full screen app
m_d3dpp.BackBufferWidth = m_displayMode.Width ;
m_d3dpp.BackBufferHeight = m_displayMode.Height ;
m_d3dpp.Windowed = false; // fullscreen
m_d3dpp.FullScreen_RefreshRateInHz = m_displayMode.RefreshRate;
}
else
{
// if its a windowed app
m_d3dpp.Windowed = true ;
m_d3dpp.EnableAutoDepthStencil = TRUE ;
m_d3dpp.AutoDepthStencilFormat = D3DFMT_D16 ;
}
}
//DxBase.cpp
void cDXBase::CreateDirectxDevice()
{
int vp = 0 ; // the typeof vertex processing
if(m_Caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
{
// hardware vertex processing is supported.
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING ;
}
else
{
// use software vertex processing.
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING ;
}
// Create the D3DDevice
if(FAILED(m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
m_Hwnd,
vp,
&m_d3dpp,
&m_pd3dDevice)))
{
MessageBox(NULL, _T("Direct3d m_pd3dDevice creation failed!"), _T("Error!"),MB_ICONEXCLAMATION | MB_OK) ;
PostQuitMessage(0) ;
DestroyWindow(m_Hwnd) ;
}
}
// Dxbase.cpp
HRESULT cDXBase::BeginRender()
{
HRESULT hr;
// check if the device is available
hr = IsAvailable() ;
if(hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET)
{
HandleLostDevice(hr) ;
}
else
{
if(FAILED(hr))
{
PostQuitMessage(0) ;
}
}
if(SUCCEEDED(hr))
{
// clear the frame
m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,m_BkColor, 1.0f, 0) ;
hr = m_pd3dDevice->BeginScene() ;
}
return hr;
}
//DxBase.inl
inline HRESULT cDXBase::IsAvailable()
{
return(m_pd3dDevice->TestCooperativeLevel()) ;
}
inline void cDXBase::EndRender( const HRESULT hr )
{
if(SUCCEEDED(hr))
{
m_pd3dDevice->EndScene() ;
}
m_pd3dDevice->Present(NULL, NULL, NULL, NULL) ;
}
// DxBase.cpp
void cDXBase::Release()
{
// release the Direct3d device
SAFE_RELEASE(m_pd3dDevice) ;
// release the Direct3d object
SAFE_RELEASE(m_pD3D) ;
}
// DxBase.cpp
void cDXBase::HandleLostDevice(HRESULT hr)
{
if(hr == D3DERR_DEVICELOST)
{
Sleep(500) ;
}
else
{
if(hr == D3DERR_DEVICENOTRESET)
{
//The m_pd3dDevice is ready to be Reset
hr = ResetDevice() ;
}
}
}
HRESULT cDXBase::ResetDevice()
{
if (m_pd3dDevice)
{
HRESULT hr ;
hr = m_pd3dDevice->Reset(&m_d3dpp) ;
return hr ;
}
return 0;
}
Now we just need to make a few changes to our MainWindow to integrate DirectX.
// MainWindow.cpp
GRAPHIC_API HWND cMainWindow::Init( const HINSTANCE &hInstance, const int &nCmdShow, LPCTSTR lpWindowTitle,const int iFullScreenWidth, const int iFullScreenHeight, cBaseApp* const pGameApp )
{
// earlier stuff
m_iFullScreenWidth = iFullScreenWidth ;
m_iFullScreenHeight = iFullScreenHeight ;
// earlier stuff
// initialize DirectX
cDXBase::GetInstance().Init(hWnd);
return hWnd;
}
HWND cMainWindow::CreateMyWindow( const int &nCmdShow, LPCTSTR lpWindowTitle )
{
// earlier stuff
#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,
m_iFullScreenWidth,m_iFullScreenHeight,
NULL,
NULL,
m_hInstance,
this) ;
#endif
// earlier stuff
}
LRESULT CALLBACK cMainWindow::WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
// earlier stuff
case WM_SIZE:
case WM_MOVE:
OnReset();
return 0 ;
case WM_KEYDOWN:
switch(wParam)
{
case VK_ESCAPE:
DestroyWindow(hwnd) ;
break ;
}
return 0 ;
// earlier stuff
case WM_DESTROY:
OnDestroy();
return 0 ;
// earlier stuff
}
}
void cMainWindow::Run()
{
// earlier stuff
//No message to process?
// Then do your game stuff here
OnRender();
}
}
}
void cMainWindow::OnRender()
{
HRESULT hr;
hr = cDXBase::GetInstance().BeginRender();
if (SUCCEEDED(hr))
{
cDXBase::GetInstance().EndRender(hr);
}
}
void cMainWindow::OnDestroy()
{
// release the graphic object
cDXBase::GetInstance().Release();
ReleaseCapture() ;
PostQuitMessage(0) ;
}
void cMainWindow::OnReset()
{
GetWinRect() ;
cDXBase::GetInstance().ResetDevice();
}
Before we create our game ,we first need to know how to create a window. This involves the following basic steps
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) ;
}
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.
Code