sunlight

Sprites and Bitmaps

Introduction

In this part of the tutorial, we will cover:

  • Loading bitmaps into off-screen surfaces
  • Copying from surface to surface (blitting)
  • Colour keys

Previous View Code Download Code Next

Loading Bitmaps

For this step, you will need a bitmap file. Make it 640x480, 16-bit or 24-bit colour, call it 'a.bmp' and put it in your project directory.

You will also need some support code. DirectDraw has no functions to load bitmaps from disk, among other things, so Microsoft supplied a few functions in source form to do so. I have provided these functions in the Zip file of the project code above.

Copy these files to your project directory. Add DDUTIL.CPP to your project.

These files include several functions that are really useful for loading bitmaps. The most important one is DDLoadBitmap:

IDirectDrawSurface7 *DDLoadBitmap(IDirectDraw7 *pdd, LPCSTR szBitmap, int dx, int dy);

This function loads either a bitmap file or resource and gives us an off-screen surface. This is a surface that cannot be displayed directly on the screen. We will need a surface pointer declaration:

IDirectDrawSurface7 *lpA;

We will then add the following code to the end of our InitDirectDraw function:

lpA = DDLoadBitmap(pDD, "a.bmp", 0, 0);
And finally, we will release the surface when we're done with it, in ExitDirectDraw:
void ExitDirectDraw()
{
	lpA->Release();
	lpA = NULL;

We have now loaded the bitmap into a surface, and it is ready for drawing.

Drawing Bitmaps

Now we have a surface, we can copy from it to the back buffer. This is done using the BltFast method of the back buffer:

void OnIdle(void)
{
	if (lpPrimary == NULL)
		return;

	RECT	r;
	
	r.left = 0;
	r.top = 0;
	r.right = 640;
	r.bottom = 480;
	lpBackBuffer->BltFast(0, 0, lpA, &r, 
		DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);

This code copies the whole of the bitmap to the screen. You can see that by changing the values in the rectangle, we can copy (blit) sections of the bitmap. The destination corner is given by the first two parameters of BltFast ((0, 0) in this case).

Now there's something on the back buffer, let's do a buffer swap to get things on the screen:

	lpPrimary->Flip(NULL, DDFLIP_WAIT);
}

Run the program. Your bitmap should appear in full 16-bit colour glory. Here's a sample of what those of you with real artistic talent might see:

Now, your cursor is probably still visible. That's not good - when was the last time you saw a Windows cursor in a DirectDraw game? Let's get rid of it. Handle the message WM_SETCURSOR, and blank the cursor:

	case WM_SETCURSOR:
		SetCursor(NULL);
		return 0;

Congratulations! You just wrote your first proper DirectDraw application.

Colour Keys and Sprites

All well and good, but nothing we couldn't have done in Windows. It gets better (I promise). Create another bitmap, call it 'sprites.bmp' (don't get excited yet) and make it 640x64. You will want some 64x64 sprite images on this. Give it a horrible purple background (green will do, too). This is an example (included in the ZIP file):

I want to wholeheartedly apologise for the quality of my artistic skills... but you get the point.

The pink background will be our transparent colour (since no-one in their right minds would actually use it as a colour). We have to note the RGB value of this colour. You can get this from your painting package (I recommend Paint Shop Pro). This purple (magenta, actually) is RGB(255, 0, 255). Remember these numbers...

We will need another surface pointer declaration:

IDirectDrawSurface7 *lpSprites;

We will then add the following code to the end of our InitDirectDraw function. Note the colour key (transparent colour) is set here, just as we read:

	lpSprites = DDLoadBitmap(pDD, "sprites.bmp", 0, 0);
	DDSetColorKey(lpSprites, RGB(255, 0, 255));

And finally, we will release the surface when we're done with it, in ExitDirectDraw:

void ExitDirectDraw()
{
	lpSprites->Release();
	lpSprites = NULL;

Now, we will draw some of these sprites in places. For example, we will draw the third sprite (the orange one) in the middle of the screen:

	r.left = 128;
	r.top = 0;
	r.right = 192;
	r.bottom = 64;
	lpBackBuffer->BltFast(288, 208, lpSprites, &r, 
		DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

	lpPrimary->Flip(NULL, DDFLIP_WAIT);

Notice that we've used SRCCOLORKEY this time, to specify that we're using the source surface transparency.

You now have a sprite in the middle of the screen. That was easy, wasn't it?

You can now draw sprites (with transparency) anywhere on the screen. You can move them around:

	static int x;

	x++;
	if (x > 100)
		x = -100;
	
	r.left = 0;
	r.right = 64;
	lpBackBuffer->BltFast(288 + x, 100, lpSprites, &r, 
		DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

However, we don't have any input capability yet, so there will be no moving with the keyboard. That's what we're going to do next... We will, however, move a sprite with the mouse:

	POINT p;
	GetCursorPos(&p);

	r.left = 64;
	r.right = 128;
	lpBackBuffer->BltFast(p.x, p.y, lpSprites, &r, 
		DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT); 

That beats mouse cursors any day. Note that the sprite disappears when it reaches the right and bottom edges of the screen; this is because BltFast will not clip the sprite automatically. We will deal with this later.

Footnote: Speed and Resolution

If you want a laugh, try pushing up the resolution:

	pDD->SetDisplayMode(800, 600, 16, 0, 0);

Now there's something you'd have difficulty with in DOS. Your program will almost certainly run just as quickly in 800x600 as it did in 640x480. You're limited at 60Hz, which is the default refresh rate.

If (and only if) your monitor can cope, push the refresh rate up further. Try 85, or even 100Hz:

	pDD->SetDisplayMode(640, 480, 16, 100, 0);

Your application will run faster. Why?

It has to do with Flip. When we call Flip, we tell it to wait for the vertical refresh interval (the time between the CRT electron gun reaching the bottom and it reappearing at the top). Then it does the flip, so you don't see the 'tearing' effect. That's why our sprite is moving so slowly: it's moving one pixel every 1/60th of a second.

If you tell Flip not to wait:

	lpPrimary->Flip(NULL, DDFLIP_DONOTWAIT);

you will see a strange effect: the sprite will stop at intervals, then jump, and in fact do all kinds of strange things. This has to do with the fact that your program can repaint the screen thousands of times a second (it takes about 120 microseconds on my Celeron 300 with nVidia TNT)... but your monitor can repaint the screen only 100 times a second.

Drawing is fast under DirectDraw. It still pays to be efficient... but we've got the capability to do some serious graphics here.

Next, we're going to investigate DirectInput. There are other ways of getting keyboard input, including WM_KEYxxx messages, but DirectInput is the way to go - especially if you want to support the joystick, too.

 

Copyright © David McCabe, 1998 - 2001. All rights reserved.

You will need to download and install the m-math control to display any equations on this Web site. Without this control, you will not see most of the equations. Please do not e-mail me asking why the equations do not display!