Introduction
Lots of people asked me to write an introductory article about DirectDraw programming and Spriting so that people can understand the basic concepts and start discovering the other things about DirectX from samples (MSDN and others available here). For all those that asked me the introductory article, here it is.
WinMain and Message Loop - The Starting point
Since we are working with a DirectX application, there is no need to use the MFC library in our program. Not that the use of MFC in a DirectX application is prohibited, but MFC has a lot of code aimed to desktop apps and not graphic intensive ones, so its better to stick on plain Windows API and STL. We will start our basic DirectDraw program by selecting the "Windows Application" option in the Visual C++ interface. At the first screen we will select the option "Simple Win32 Application" to allow Visual C++ to create a WinMain
function for us. The code generated by the wizard will look like this:
#include "stdafx.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
return 0;
}
Now that we have the main function of our program, we need to create a main window for the program so that we can allow Windows OS to send messages to our application. Even if you work with a full screen DirectX application you'll still need a main window in the background, so that your program can receive the messages that the system sends to it. We will put the window initialization routine in another function of our program, this function will be called InitWindow
.
HWND InitWindow(int iCmdShow)
{
HWND hWnd;
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = g_hInst;
wc.hIcon = LoadIcon(g_hInst, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH )GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = TEXT("");
wc.lpszClassName = TEXT("Basic DD");
RegisterClass(&wc);
hWnd = CreateWindowEx(
WS_EX_TOPMOST,
TEXT("Basic DD"),
TEXT("Basic DD"),
WS_POPUP,
0,
0,
GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN),
NULL,
NULL,
g_hInst,
NULL);
ShowWindow(hWnd, iCmdShow);
UpdateWindow(hWnd);
SetFocus(hWnd);
return hWnd;
}
The first thing that this function does is register a window class in windows environment (this is needed for the window creation process). In the window class we need to pass some information about the window to the RegisterClass
function. All this parameters are contained in WNDCLASS
structure. Notice that in many places I use the variables g_hInst
. This variable will have global scope, and will hold the instance handle of our application. We will need another global variable to hold the handle of our main window (that we are about to create). To create this global variables, simply declare them above the winmain
definition, like this:
HWND g_hMainWnd;
HINSTANCE g_hInst;
Don't forget that you need to fill the content of this variables at the very begging of your program so, at our winmain
function, we'll add this code:
g_hInst = hInstance;
g_hMainWnd = InitWindow(nCmdShow);
if(!g_hMainWnd)
return -1;
Notice that we are assigning the result of our InitWindow
function to the main window global variable, because this function will return a handle to our newly created window. There's an extra information at the window creating function that we haven't discussed yet, the lpfnWndProc
. In this parameter we need to assign a reference to a function that will be our main window procedure. This procedure is responsible for receiving the messages that Windows sends to our application. This function will be called by the system (not by you) every time your application receives a message (like a key pressed, a painting message, a mouse move and so on...). Here is the basic definition of our WndProc
function:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Ok, our windows application is almost set, we are only missing an important code: the message loop. In order to allow windows to send messages to our program, we need to call a function to check if our program has received any messages. If we receive this messages we need to call a function so that our WndProc
can process the message. If we didn't receive any system message, we can use this "spare time" of our application to do some background processing and even do some DirectX stuff. This process is called Idle Processing. We need to insert our message loop right after the initialization of our global variables.
while( TRUE )
{
MSG msg;
if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
break;
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
ProcessIdle();
}
}
In our message loop, the first thing we do is check the message queue for messages to our application. This is accomplished by calling the PeekMessage
function. If the function returns true, we call TranslateMessage
and DispatchMessage
so that the messages received by our program are processed. If we have no message, we'll call another function called ProcessIdle
. This function will be created in our program and we'll use it to update the graphics of our screen. Here is a simple definition of the function:
void ProcessIdle()
{
}
Ok, our basic windows application is set. If you compile and run the application you will see an entirely black window that covers all your desktop.
Initializing DirectX and DirectDraw
Now we are going to work on the initialization of the DirectDraw in our application. Before your start to modify the code, I need to present you some concepts (surfaces and page flipping). All the drawing created by DirectDraw are based on structures called surfaces. Surfaces are memory regions that contains graphics that can be used in your application. Everything we need to drawn on the screen needs to be created on a surface first. Let's assume that we are creating a space invaders game ( like the one I wrote). For this you'll probably need a graphic buffer that will hold the space ships, the UFOs, the shots.
All this graphics will be stored in memory in this structures that we'll call surfaces. In fact, for DirectDraw applications, the area that displays what we are seeing on the screen is considered a surface too, and it's called the FrontBuffer. Attached to this FrontBuffer surface, we have another surface called the BackBuffer. This surface stores the information of what will be showed to the user in the next frame of our application. Lets say that the user is currently seeing an UFO on the screen at position (10,10) and the user's ship is at position (100,100). Since the objects are moving, we need to move our UFO to the position (12,10) and our ship to position (102, 10). If we draw this to the front buffer directly we can have some kind of synchronization problems (ie. the user can see the UFO move first and them the ship, but they need to move both at the same time). To solve this we draw everything we need to show to the user in the next frame in the backbuffer. When we finish it, we move all the information contained in the backbuffer to the frontbuffer. This process is called page flipping and is very similar to the process of creating cartoons (where we use lots of paper sheets to animated a drawing).
What really happens in the background is that DirectDraw changes the pointer of backbuffer with the pointer of frontbuffer, so that next time the video card send the video data to the monitor it uses the backbuffered content and not the old frontbuffer. When we do a page flip, the content of the backbuffer becomes the content of the previously showed frontbuffer, and not the same content of the drawn backbuffer as you might think.
Now that you have some idea of the concepts of DirectDraw, we will start coding the DirectX part of the program. The first thing you need to do is insert the #include
of DirectDraw in your main source file. Just insert the line below in the top of your file.
#include <ddraw.h>
You need to inform the library files related to DirectDraw too. Go to the Project Menu, submenu Settings. Select the link tab and put the following lib files in the "Object/Library Modules"
kernel32.lib user32.lib ddraw.lib dxguid.lib gdi32.lib
Now we are going to create a new function in our program. This function will be called InitDirectDraw and it will be used to start the main DirectDraw object and create the main surfaces that we are going to use (the front and back buffer surfaces).
int InitDirectDraw()
{
DDSURFACEDESC2 ddsd;
DDSCAPS2 ddscaps;
HRESULT hRet;
hRet = DirectDrawCreateEx(NULL, (VOID**)&g_pDD, IID_IDirectDraw7,
NULL);
if( hRet != DD_OK )
return -1;
hRet = g_pDD->SetCooperativeLevel(g_hMainWnd,
DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
if( hRet != DD_OK )
return -2;
hRet = g_pDD->SetDisplayMode(640, 480, 16, 0, 0);
if( hRet != DD_OK )
return -3;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
hRet = g_pDD->CreateSurface(&ddsd, &g_pDDSFront, NULL);
if( hRet != DD_OK )
return -1;
ZeroMemory(&ddscaps, sizeof(ddscaps));
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
hRet = g_pDDSFront->GetAttachedSurface(&ddscaps, &g_pDDSBack);
if( hRet != DD_OK )
return -1;
return 0;
}
Notice that in this function we are using some other variables with the "g_
" (global) prefix. Since we are going to use the backbuffer and front buffer reference through all our code, we are goinf to store this two surface handles in global variables. The other variable that we are storing as global is the main DirectDraw object (g_pDD
). This object will be use to create all the DirectDraw related objects. So, at the top of our code, add the following global variables.
LPDIRECTDRAW7 g_pDD = NULL;
LPDIRECTDRAWSURFACE7 g_pDDSFront = NULL;
LPDIRECTDRAWSURFACE7 g_pDDSBack = NULL;
Now lets get back to our InitDirectDraw
function. The first thing we are doing at the function is the creation of the DirectDraw object. To create this object we use the DirectDrawCreate
function that is defined in the ddraw.h header. There are two important parameters in this function, the second and the third one. The second parameter passes a reference to the variable where we want to store the DirectDraw object variable (in our case the g_pDD
variable). In third parameter we need to pass the version of DirectDraw object we are trying to get. This allow you to work with older version of DirectDraw even if you install the new version of the SDK. In my case I�m using the objects of DirectX 7, but using DX SDK 8.1.
Notice that I�m testing the result of the function for DD_OK
, that is the Ok result for all DirectDraw functions. It�s important to test every return code of the DirectDraw function calls. If we receive a value different from DD_OK
we return a negative value to the function. If you have some error at this point of the program you can assume that the user probably doesn't have the correct version of DirectX installed on his computer, so you can give him some friendly message (we will see this later).
The second function call is the SetCooperativeLevel
. This function is used to tell DirectX how we are going to work with the display, if we are going to use full-screen mode or windowed mode and some other options. You can check the available options at the DirectX documentation. We test the result of this function as we have done in the first function call.
The third function called is the SetDisplayMode
. This function is responsible for selecting the resolution we are going to use with our application. In this case we are creating an 640x480 full screen. The third parameter represent the color depth that we are using. That will depend on the number of colors you want to use with your app.
After starting the display, we need to create the two surfaces that we will use to draw our graphics on the screen. First we need to initialize the front buffer (the one that the user is seeing). When we want to create a surface with DirectDraw we need to initialize the DDSURFACEDESC2
structure that have some parameter for the creation of the surface. Its important to cleanup the structure first with a ZeroMemory
or memset
(or you can have some problems in some calls). Since we are creating the front buffer we need to fill the dwflags
parameter with the value DDSD_BACKBUFFERCOUNT
, so that the creation function recognizes that out frontbuffer will have an associated backbuffer. At the ddsCaps.dwCaps parameter we need to inform that we are creating the front buffer surface (or primary surface) with the DDSCAPS_PRIMARYSURFACE
parameter. Since we are going to work with a flipping surface, we need to inform the DDSCAPS_FLIP
and the DDSCAPS_COMPLEX
parameters.
After setting up the DDSURFACEDESC2 structure we need to call the CreateSurface
function from our DirectDraw global object, passing as parameter the surface description structure and the global object that will hold the DirectDraw frontbuffer surface.
After creating the frontbuffer surface we need to get the backbuffer associated with this frontbuffer. We can do that by calling the GetAttachedSurface
of the frontbuffer surface. As a parameter we need to pass a DDSCAPS2
structure, so that the function know that we are trying to get the backbuffer.
Now that our function is created, we need to call it from the main function. Here is how we are going to call it:
if(InitDirectDraw() < 0)
{
CleanUp();
MessageBox(g_hMainWnd,
"Could start DirectX engine in your computer."
"Make sure you have at least version 7 of "
"DirectX installed.",
"Error", MB_OK | MB_ICONEXCLAMATION);
return 0;
}
Notice that we are testing for a negative result. If we receive a negative result we tell the user that he probably didn�t installed the correct version of DirectX.
We have an extra function call here, the Cleanup function. The Cleanup function will be responsible for deleting all the objects created by DirectX. All the objects are destroyed by calling the Release
method of each instance. Here is the function definition:
void CleanUp()
{
if(g_pDDSBack)
g_pDDSBack->Release();
if(g_pDDSFront)
g_pDDSFront->Release();
if(g_pDD)
g_pDD->Release();
}
Before we compile and run the code again, insert the following code to the WndProc
function, at the switch statement that handles the messages.
case WM_KEYDOWN:
if(wParam == VK_ESCAPE)
{
PostQuitMessage(0);
return 0;
}
break;
With this code you'll be able to get out of the application by pressing the ESCAPE key. Now, compile and run the application and notice that you'll enter in the 640x480 fullscreen mode.
Blitting Graphics
Now we are going to draw some things in our backbuffer so that we can flip the surfaces and produce some animation. We are going to use a bitmap with some tiles of a race car that produce an animation. To create an sprite in DirectDraw we need to store this bitmap in another surface (that we will call tile or offscreen surface) so that we can blit (print) this surface in the backbuffer and produce the animation. We are going to create a class called cSurface to help us to manage our tile surfaces. Right click in the ClassView of Visual C++ and select the Create New Class option. As class type, select Generic Class and for the name use cSurface
.
Let�s start by creating the member variables of our class. The main variable will be of LPDIRECTDRAWSURFACE7
type and will hold a reference to the DirectDraw Surface object associated with our class. We are going to store the width and height of our surface too. We will have another member called m_ColorKey
that I�ll explain later. Here is the definition of our member variables.
protected:
COLORREF m_ColorKey;
UINT m_Height;
UINT m_Width;
LPDIRECTDRAWSURFACE7 m_pSurface;
The first function we are going to insert in our class is the Create
function. This function will be used to create the DirectX surface object for our bitmap. Here is the Create
function code:
BOOL cSurface::Create(LPDIRECTDRAW7 hDD, int nWidth, int nHeight,
COLORREF dwColorKey)
{
DDSURFACEDESC2 ddsd;
HRESULT hRet;
DDCOLORKEY ddck;
ZeroMemory( &ddsd, sizeof( ddsd ) );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
ddsd.dwWidth = nWidth;
ddsd.dwHeight = nHeight;
hRet = hDD->CreateSurface(&ddsd, &m_pSurface, NULL );
if( hRet != DD_OK )
{
if(hRet == DDERR_OUTOFVIDEOMEMORY)
{
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN |
DDSCAPS_SYSTEMMEMORY;
hRet = hDD->CreateSurface(&ddsd, &m_pSurface, NULL );
}
if( hRet != DD_OK )
{
return FALSE;
}
}
if((int)dwColorKey != -1)
{
ddck.dwColorSpaceLowValue = dwColorKey;
ddck.dwColorSpaceHighValue = 0;
m_pSurface->SetColorKey(DDCKEY_SRCBLT, &ddck);
}
m_ColorKey = dwColorKey;
m_Width = nWidth;
m_Height = nHeight;
return TRUE;
}
Notice that the creation process used to create the tile surface is very similar to the creation process of the front buffer surface. The different is at the information assigned to the DDSURFACE2 structure. At the dwFlags
parameter we inform that the dwCaps
, dwWidth
and dwHeight
will have information that needs to be used to create the surface. In the dwCaps
parameter we inform that this surface is and offscreen surface (tile surface) by using the DDSCAPS_OFFSCREENPLAIN
flag. We combine with this value with the DDSCAPS_VIDEOMEMORY
value, that tells the function that we are trying to create this function in video memory.
At the error test we are testing if the return value of the function is DDERR_OUTOFVIDEOMEMORY
so that if the user has an old video card if just a few MB of memory, we can change the DDSURFACEDESC2
parameter to DDSCAPS_SYSTEMMEMORY
and try to create the surface on RAM instead of VIDEO memory. The process of blitting surfaces from SYSTEM_MEMORY
to VIDEO_MEMORY
is much slower then the VIDEO MEM
to VIDEO MEM
process but is needed in case of the user doesn�t have enough memory.
At the last portion of the function we have the dwColoKey
parameter test. This is used if we are working with a colorkeyed surface. A colorkeyed surface is a surface where we don't want to display a certain color. Let's say that I want to blit a spaceship in a starfield background. When I blit the spaceship I don't want to display the black background of the bitmap just the ship itself so I can associate a color key to the ship to display just the ship picture and not the background. You need to take care when you create your tile bitmaps and make sure to not use antialised backgrounds in the sprite bitmaps (lots of application allows you to removed the antialiased background so that you can have high quality sprites).
Now we will create another function to load a bitmap file into the DirectX surface object. For this we are going to use some basic GDI functions. Since we are going to load this just once, this will probably not impact much on the performance of the drawing process. Here is the LoadBitmap
function:
BOOL cSurface::LoadBitmap(HINSTANCE hInst, UINT nRes, int nX, int nY,
int nWidth, int nHeight)
{
HDC hdcImage;
HDC hdc;
BITMAP bm;
DDSURFACEDESC2 ddsd;
HRESULT hr;
HBITMAP hbm;
hbm = (HBITMAP) LoadImage(hInst, MAKEINTRESOURCE(nRes),
IMAGE_BITMAP, nWidth, nHeight, 0L);
if (hbm == NULL || m_pSurface == NULL)
return FALSE;
m_pSurface->Restore();
hdcImage = CreateCompatibleDC(NULL);
if (!hdcImage)
return FALSE;
SelectObject(hdcImage, hbm);
GetObject(hbm, sizeof(bm), &bm);
if(nWidth == 0)
nWidth = bm.bmWidth;
if(nHeight == 0)
nHeight = bm.bmHeight;
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
m_pSurface->GetSurfaceDesc(&ddsd);
if ((hr = m_pSurface->GetDC(&hdc)) == DD_OK)
{
StretchBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage,
nX, nY, nWidth, nHeight, SRCCOPY);
m_pSurface->ReleaseDC(hdc);
}
DeleteDC(hdcImage);
m_srcInfo.m_hInstance = hInst;
m_srcInfo.m_nResource = nRes;
m_srcInfo.m_iX = nX;
m_srcInfo.m_iY = nY;
m_srcInfo.m_iWidth = nWidth;
m_srcInfo.m_iHeight = nHeight;
return TRUE;
}
This function is very easy to understand if you know a little bit of GDI programming, anyway I'll explain all the code. The first thing we need to do is to call the restore method of our m_Surface
internal variable. This will restore the memory allocated to the surface object of DirectDraw, in case DirectDraw deallocates the memory (if this situation happens, any function call referring the m_Surface
object will return DERR_SURFACELOST
). After restoring the memory we create a GDI dc and load the bitmap passed as a parameter from the resource. The bitmap is them selected in the DC and blittled in the surface using StretchBlt
function. Notice that I'm saving the bitmap information in a m_srcInfo
structure. This structure is used when we have a surface lost problem, this way we can restore the surface with its original data.
The last function we are going to present here is the Draw function that is used to Draw a portion of the surface in to another surface. In most of the cases you�ll draw the surface in the backbuffer but you can use this Draw method with any other kind of surface.
BOOL cSurface::Draw(LPDIRECTDRAWSURFACE7 lpDest, int iDestX, int iDestY,
int iSrcX, int iSrcY, int nWidth, int nHeight)
{
RECT rcRect;
HRESULT hRet;
if(nWidth == 0)
nWidth = m_Width;
if(nHeight == 0)
nHeight = m_Height;
rcRect.left = iSrcX;
rcRect.top = iSrcY;
rcRect.right = nWidth + iSrcX;
rcRect.bottom = nHeight + iSrcY;
while(1)
{
if((int)m_ColorKey < 0)
{
hRet = lpDest->BltFast(iDestX, iDestY, m_pSurface,
&rcRect, DDBLTFAST_NOCOLORKEY);
}
else
{
hRet = lpDest->BltFast(iDestX, iDestY, m_pSurface,
&rcRect, DDBLTFAST_SRCCOLORKEY);
}
if(hRet == DD_OK)
break;
if(hRet == DDERR_SURFACELOST)
{
Restore();
}
else
{
if(hRet != DDERR_WASSTILLDRAWING)
return FALSE;
}
}
return TRUE;
This function is extremely simple. The first thing we do is create a rect variable and fill it with the source bitmap position and size that we want to blit in the destination surface. After that, we call the BltFast
method of the surface to blit the content in the destination surface. Notice that we're making a test to see if the surface have a color key or not. Blitting surfaces without colorkey is much faster than surfaces that have colorkey, so create colorkey only when needed. You can see that the drawing code is inside and infinite loop. This is created because the drawing function can return a surface lost error. If this error is returned we need to restore the surface and try to blit it again until we got the surface restored.
Another important function is the Destroy function that is responsible for releasing the DirectDraw resources related to this objects. Its basically a call to the Release method of the m_Surface
variable.
void cSurface::Destroy()
{
if(m_pSurface != NULL)
{
m_pSurface->Release();
m_pSurface = NULL;
}
}
In the source code you�ll find some other methods in this class but basically, for this article, you'll only need this four. Compile the code to see if you have no errors.
Drawing in the BackBuffer using the cSurface Class
The next step is the creation of an instance of our cSurface
class so that we can blit this information on the backbuffer. To do this, we need to insert an include statement in the file that contains out WinMain
function.
#include "csurface.h"
After including the header of our class, create a new global variable that will hold our instance. You can create it below the declaration of the other global variables.
cSurface g_surfCar;
After proceeding with the coding, add the bitmap resource to the object so that we can use it to blit the surface in the backbuffer. The resource is a bitmap file called bmp_bigcar_green.bmp . This bitmap is used in my new game (RaceX) that will be posted here in CP pretty soon. You can create a resource ID for the bitmap with the "IDB_GREENCAR
" name.
Now that we have the surface class instance declared, we need to call the create and loadbitmap method to create the DirectXobject inside the class. This code can be inserted after the call of InitDirectDraw
.
g_surfCar.Create(g_pDD, 1500, 280);
g_surfCar.LoadBitmap(g_hInst, IDB_GREENCAR, 0, 0, 1500, 280);
Before we proced, remember that you need to destroy this object in the case you create it during the code execution. For this you need a call to the Destroy method. You can put this in the CleanUp
function.
void CleanUp()
{
g_surfCar.Destroy();
if(g_pDDSBack)
g_pDDSBack->Release();
if(g_pDDSFront)
g_pDDSFront->Release();
if(g_pDD)
g_pDD->Release();
}
Now that we have created, initialized and added the destruction code of our surface class we just need to draw the picture in the backbuffer and flip the surface in the ProcessIdle
function.
void ProcessIdle()
{
HRESULT hRet;
g_surfCar.Draw(g_pDDSBack, 245, 170, 0, 0, 150, 140);
while( 1 )
{
hRet = g_pDDSFront->Flip(NULL, 0 );
if( hRet == DD_OK )
{
break;
}
if( hRet == DDERR_SURFACELOST )
{
g_pDDSFront->Restore();
}
if( hRet != DDERR_WASSTILLDRAWING )
{
break;
}
}
}
This code draws the first picture of the car in the middle of the backbuffer and flips the backbuffer with the front one every time we have an idle processing call. Let�s change the code a little bit so that we can blit the animation of the car.
void ProcessIdle()
{
HRESULT hRet;
static int iX = 0, iY = 0;
static iLastBlit;
if(GetTickCount() - iLastBlit < 50)
{
return;
}
g_surfCar.Draw(g_pDDSBack, 245, 170, iX, iY, 150, 140);
while( 1 )
{
hRet = g_pDDSFront->Flip(NULL, 0 );
if( hRet == DD_OK )
{
break;
}
if( hRet == DDERR_SURFACELOST )
{
g_pDDSFront->Restore();
}
if( hRet != DDERR_WASSTILLDRAWING )
{
break;
}
}
iX += 150;
if(iX >= 1500)
{
iX = 0;
iY += 140;
if(iY >= 280)
{
iY = 0;
}
}
iLastBlit = GetTickCount();
}
We create 3 static variables. The first 2 will be used to change the position of the blitted portion of the source bitmap. This way we can create the animation of the car by going from frame 1 to frame 20. Notice that we have an extra variable called iLastBlit
that holds the result of a GetTickCount
function call. This is used to allow that each frame stays on the screen at least 50 miliseconds, this way the animation will go pretty smooth. You can remove this code to see what happens (on my machine the car spins too fast).
This was a brief introduction on how to create a basic C++ program that uses the DirectX DirectDraw library. If you have any questions or comments just post them in!