Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SpaceWarrior - 2D DirectDraw Game - Part III

0.00/5 (No votes)
3 Jan 2007 1  
An article on creating a simple 2D DirectDraw game (the DirectX).

Introduction

I will speak here about the heart of the book, Tricks of the Windows Game Programming Gurus, by Andre LaMothe - the DirectX things. This is the place where the Game_Init() and Game_Shutdown() functions (from the part I of this tutorial) will be explained.

The DirectX sub-system

The DirectX has a few (in)dependent components:

  • DirectDraw
  • DirectSound
  • DirectSound3D
  • DirectMusic
  • DirectPlay
  • DirectInput
  • Direct3DIM
  • Direct3DRM
  • DirectSetup

and it is placed just above the hardware we need. Each of these components is connected to some hardware piece inside the computer. DirectDraw represents the graphics card. DirectSound plays digital sounds (WAV files etc.). DirectMusic deals with MIDI files. DirectInput handles input devices like keyboard, mouse, joystick etc. DirectPlay is used to create network (multiplayer) games. Direct3DIM and Direct3DRM support hardware accelerated rendering of 3D computer graphics.

In our game, we will need just the first DirectX component, for now. Later, we'll add sounds and music.

DirectX initialization

It is very easy to initialize the DirectX sub-system. See below (the code should be inside the Game_Init() function):

// Required DirectX includes

#include "ddraw.h"



LPDIRECTDRAW g_lpdd;

// Create base IDirectDraw interface

if (FAILED(DirectDrawCreate(NULL, &g_lpdd, NULL)))
{
     return (0);
}


// Query for IDirectDraw4 interface (or some other newer interface)

LPDIRECTDRAW g_lpdd4;
if (FAILED(g_lpdd->QueryInterface(IID_IDirectDraw4, (LPVOID*)&g_lpdd4)))
{
     return (0);
}

You have initialized your video card now. Let's set some params:

// Set cooperative level to fullscreen

if (FAILED(g_lpdd4->SetCooperativeLevel(g_hMainWnd, 
    DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX | 
    DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)))
{
    return (0);
}

// Set display mode to SCREEN_WIDTH x SCREEN_HEIGHT x SCREEN_BPP

if (FAILED(g_lpdd4->SetDisplayMode(SCREEN_WIDTH, 
           SCREEN_HEIGHT, SCREEN_BPP, 0, 0)))
{
    return (0);
}

Now, we have created a full screen window with params: SCREEN_WIDTH x SCREEN_HEIGHT x SCREEN_BPP. If there is an error, the window will not be created and the function will return the value 0.

Now, we need the so called DirectX surface. There are three types of DirectX surfaces: primary, secondary, and offscreen surface. The primary surface is a primary raster display (monitor screen). The secondary surface is attached to the primary, and is used for flicker-free drawing on the screen. The offscreen surfaces hold sprites in the video memory. Here are the primary and the secondary surfaces:

// Fill primary surface descriptor

DDSURFACEDESC2 g_ddsd;
memset(&g_ddsd, 0, sizeof(DDSURFACEDESC2));
g_ddsd.dwSize = sizeof(DDSURFACEDESC2);
g_ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
g_ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | 
                        DDSCAPS_COMPLEX | DDSCAPS_FLIP;
g_ddsd.dwBackBufferCount = 1;

// Create IDirectDrawSurface4 (primary surface) interface

LPDIRECTDRAWSURFACE4 g_lpddsPrimary;
if (FAILED(g_lpdd4->CreateSurface(&g_ddsd, &g_lpddsPrimary, NULL)))
{
     return (0);
}
    
// Query for attached back-buffer surface

g_ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
LPDIRECTDRAWSURFACE4 g_lpddsBack;
if (FAILED(g_lpddsPrimary->GetAttachedSurface(&g_ddsd.ddsCaps, 
                                              &g_lpddsBack)))
{
     return (0);
}

Finally, we must set the drawing-clipping region for the secondary surface. No drawing will be allowed outside this clipping region.

// Attach clipper to the surface

RECT <CODE>rect_list[1] = {{0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}};
LPDIRECTDRAWCLIPPER g_lpddClipper;
if (!(g_lpddClipper=Attach_Clipper(g_lpddsBack, 1, rect_list)))
{
    return (0);
}

DirectX rendering

After we have initialized the DirectX sub-system, we can perform the drawing on the DirectX back-buffer surface, the attached surface of the primary surface. In the book, there are many useful functions defined to complete this job, like:

  • Pixel drawing
  • Line drawing
  • Polygon drawing
  • Text drawing
  • Polygon filling
  • Bitmap blitting

Here are the definitions of the drawing functions:

void Plot_Pixel(int x, int y, UCHAR color, 
     LPVOID* pBuffer, int mempitch);
void Plot_Pixel(int x, int y, UCHAR red, 
     UCHAR green, UCHAR blue, UCHAR alpha, LPVOID* pBuffer, int mempitch);
void Blit_Bitmap(BITMAP_FILE_PTR bitmap, int x, int y, 
     int surfaceWidth, int surfaceHeight, LPVOID* pVideoBuffer, int mempitch);
int Draw_Text(char* text, int x, int y, UCHAR color, 
    LPDIRECTDRAWSURFACE4 lpdds);
void Draw_Line(int x0, int y0, int x1, int y1, 
     DWORD color, LPVOID* pBuffer, int mempitch);
int Draw_Polygon(POLYGON2D_PTR poly, RECT clipRect, 
    LPVOID* pBuffer, int mempitch);
int Fill_Polygon(POLYGON2D_PTR poly, RECT clipRect, 
    LPVOID* pBuffer, int mempitch);
void Fill_Top_Triangle(int x1, int y1, int x2, int y2, int x3, 
     int y3, DWORD color, RECT clipRect, LPVOID* pBuffer, int mempitch);
void Fill_Bottom_Triangle(int x1, int y1, int x2, int y2, int x3, 
     int y3, DWORD color, RECT clipRect, LPVOID* pBuffer, int mempitch);
void Fill_Triangle(int x1, int y1, int x2, int y2, int x3, int y3, 
     DWORD color, RECT clipRect, LPVOID* pBuffer, int mempitch);

So, using these basic functions, we can perform high-speed rendering on DirectDraw surfaces. The other parts are transformation functions. See below:

int Translate_Polygon(POLYGON2D_PTR poly, int dx, int dy);
int Rotate_Polygon(POLYGON2D_PTR poly, int theta);
int Scale_Polygon(POLYGON2D_PTR poly, float sx, float sy);

There is a set of bitmap manipulation functions:

int Load_Bitmap(BITMAP_FILE_PTR bitmap, char* filename);
int Unload_Bitmap(BITMAP_FILE_PTR bitmap);

See the defined structs used above:

typedef struct _BITMAP_FILE_TAG
{
    BITMAPFILEHEADER bitmapFileHeader;
    BITMAPINFOHEADER bitmapInfoHeader;
    PALETTEENTRY palette[256];
    UCHAR* buffer;

} BITMAP_FILE, *BITMAP_FILE_PTR;


typedef struct VERTEX2DF_TYP
{
    float x, y;

} VERTEX2DF, *VERTEX2DF_PTR;


typedef struct POLYGON2D_TYP
{
    int state;
    int num_verts;
    int x0, y0;
    int xv, yv;
    DWORD color;
    VERTEX2DF* vlist;

} POLYGON2D, *POLYGON2D_PTR;

I have added a set of functions that deals with sprites:

int Sprite_Load(char* spriteBitmap, int spriteWidth, int spriteHeight);
int Sprite_Unload(int spriteID);

And also with objects:

int Object_Create(int spriteID, float posX, float posY, 
           float velX, float velY, int animationTime, 
           int startFrame, int endFrame, ANIMATION_TYPE animationType);
int Object_Destroy(int objectID);
int Object_GetSpeed(int objectID, float& velX, float& velY);
int Object_SetSpeed(int objectID, float velX, float velY);
int Object_GetPosition(int objectID, float& posX, float& posY);
int Object_SetPosition(int objectID, float posX, float posY);
int Object_SetAnimation(int objectID, int startFrame, 
           int endFrame, ANIMATION_TYPE animationType);
int Object_GetSprite(int objectID);

Here are the defined structures that hold sprite and object information:

typedef struct _SPRITE_TAG
{
     int spriteCols;
     int spriteWidth;
     int spriteHeight;
     LPDIRECTDRAWSURFACE4 lpddsSprite;

} SPRITE_TAG, *SPRITE_TAG_PTR;


typedef enum ANIMATION_TYPE
{
    AT_STOP = 0,
    AT_REPEAT,
    AT_DIE
};

typedef struct _OBJECT_TAG
{
    int spriteID;
    float posX;
    float posY;
    float velX;
    float velY;
    int animationTime;
    int currentTime;
    int startFrame;
    int endFrame;
    int currentFrame;
    ANIMATION_TYPE animationType;

} OBJECT_TAG, *OBJECT_TAG_PTR;

Most of these structs are defined just for this game, and shouldn't be considered as a general implementation but just as an example. The model is the following: the sprite is a unique object that holds a sprite tile set bitmap. It is created just once. However, you can create a number of objects that use the same sprite (like the asteroids in this game). Considering the animation, you can program whatever you like when it comes to this point.

DirectX shutdown

To shutdown the DirectX sub-system, do the following (the code should be inside the Game_Shutdown() function):

// Release IDirectDrawClipper interface

if (g_lpddClipper)
{
    g_lpddClipper->Release();
    g_lpddClipper = NULL;
}

// Release IDirectDrawSurface4 (primary surface) interface

if (g_lpddsPrimary)
{
    g_lpddsPrimary->Release();
    g_lpddsPrimary = NULL;
}

// Release IDirectDraw4 interface

if (g_lpdd4)
{
    g_lpdd4->Release();
    g_lpdd4 = NULL;
}

// Release base IDirectDraw interface

if (g_lpdd)
{
    g_lpdd->Release();
    g_lpdd = NULL;
}

After this code, you can go back safely to Windows.

Conclusion

You have seen here all the important parts of DirectX, and a library wrapper that makes things more simple when it comes to using DirectX in your applications (games).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here