DirectX tilebased demo

Author: James McCue
Author's e-mail: aloiterer@juno.com
Author's homepage: Jim the loiterer's PC games, programming, & stuff

First off, I guess I'll talk about building this project, because even though this is just a small project, a part of big projects is being able to send them (or parts of them) to other coders you may be working with.

This project will show I still need work in that department! - because there are some "pain in the butt" things you'll need to do to build this...

Download the .ZIP, (see link at bottom of this mess) unzip it someplace... (I've included an EXE you can just run if you want)

If your VC4 isn't sparked up yet, spark it up now...

Goto FILE->New, and make a new workspace, an application, name it whatever, and give the directory you unzipped the files to as the project's location.

Add tiled.cpp and ddutil.cpp to your new project (INSERT->files into project), along with WINMM.LIB (located in my MSDEVSTD\LIB directory) and DDRAW.LIB (located in my SDK\LIB directory)

Now, we're almost there... this is the pain in the butt part...

INSERT->resource, you want to click the IMPORT button here... and then select all 12 of my .BMP files.

For each .BMP, right click on the IDB_BITMAP# and choose properties - maybe this graphic will give you an idea of what to do.

Change the names of the bitmap resources to whichever BMP they are. (pssst! keep the quotes)

After you've made those changes, save the .RC file... and then (once again) INSERT->Files into project, insert the .RC you made into the project.

Now it's time to talk about what I'm doing in this little demo....

Be forewarned, some of the sourcecode I'll show you here is going to look a little "wackily" indented... because I rely on the TAB key to indent my code a bit too much apparently.

Most of the code this project uses is from Andre' Lamothe's discussion of DirectX programming, I think it was in game developer.

I used his code as a template, you can check out the discussion I'm referring to at http://www.xgames3d.com/, which is Andre's awesome site!

Additional code from this project is from ddutil.cpp and ddutil.h, which are included in the complete DirectX 5 sdk.

Take a look at my Game_Main routine, this is where the action is, I'll discuss it below.

/////////////////////////////////////////////////////////////////////////////////////////////

// All the tile drawing stuff is done in here.
// This isn't the best algorithm for drawing tiles, because
// it's checking every dang tile to see if it needs to be clipped
// along the right hand side of the screen...
//
// but anyway, here goes...

void Game_Main(void)
{
// process your game logic and draw next frame

// right now, it's just a cheesy clipping tile engine!

	static int px = SCREEN_WIDTH, // upperleft corner of screen
	           py = SCREEN_HEIGHT; 
	DWORD new_frame_time;
	int movementfactor;

	LPDIRECTDRAWSURFACE pdds; // an alias we'll use to the
	                          // tile to be used in BltFast!
    RECT rcRect;              /// used for clipping tiles
	HRESULT             ddrval;
    HDC hdc;
	int start_left, start_top;
	int start_right, start_bottom;
    int start_x_cell, start_y_cell;
	int render_it_x,render_it_y;
	int clip_right, clip_bottom;
    int which_cell,cell_x_save;
	int num_x_tiles, num_y_tiles;
	int x_offset, y_offset;
    int orig_left_clip,orig_top_clip;

    // record starting time of frame. I don't really use this
	// but it could be used to let your current framerate dictate
	// how much to move in response to keystrokes...
	// It's a timing paradigm that WILL make for some choppy
	// play on slow machines, but at the same time will have 
	// for example. 2 people who are running this, one on a
	// P200 and one on a P100, who start it up at the same time,
	// seeing the same image 30 seconds later... more or less...
    new_frame_time = GetTickCount();

	movementfactor = new_frame_time - last_frame_time;

	// get input, note how keyboard is accessed with constants "VK_"
	// these are defined in "winuser.h" are basically the scan codes
	// for letters just use capital ASCII codes

	if (KEY_DOWN(VK_ESCAPE))
		PostMessage(main_window_handle,WM_CLOSE,0,0); // this is how you exit you game

	// which way is player moving
	if (KEY_DOWN(VK_RIGHT))
	if ((px+=4)>(X_BOUND)) px=X_BOUND;

	if (KEY_DOWN(VK_LEFT))
	if ((px-=4)<SCREEN_WIDTH) px=SCREEN_WIDTH;

	if (KEY_DOWN(VK_UP))
	if ((py-=4)<SCREEN_HEIGHT) py=SCREEN_HEIGHT;

	if (KEY_DOWN(VK_DOWN))
	if ((py+=4)>=Y_BOUND) py=Y_BOUND-1;

	// my muddleheaded setup for the tile engine

	start_left = px-SCREEN_WIDTH;
	start_top = py-SCREEN_HEIGHT;

	start_x_cell = start_left / CELL_X_SIZE;
	start_y_cell = start_top / CELL_Y_SIZE;

	orig_left_clip = rcRect.left = start_left%CELL_X_SIZE;
	orig_top_clip = rcRect.top = start_top%CELL_Y_SIZE;

    start_right = (start_left+CELL_X_SIZE)%CELL_X_SIZE;
	start_bottom = (start_top+CELL_Y_SIZE)%CELL_Y_SIZE;

	if (start_right<=start_left)
		rcRect.right = CELL_X_SIZE;
	else
		rcRect.right = start_right;

	if (start_bottom<=start_top)
		rcRect.bottom = CELL_Y_SIZE;
	else
		rcRect.bottom = start_bottom;

	// get starting texture to draw with

	cell_x_save = start_x_cell + (start_y_cell<<TILE_Y_NUM_SHIFT);

	which_cell = cell_x_save;

	x_offset = 0;
	y_offset = 0;

	num_x_tiles = ((SCREEN_WIDTH+start_left)/ CELL_X_SIZE) + 1;
	num_y_tiles = ((SCREEN_HEIGHT+start_top)/ CELL_Y_SIZE) + 1;

// loop thru entire scene left to right, top to bottom

	for (render_it_y = 0; render_it_y<num_y_tiles; render_it_y++)
	{
		x_offset = 0;

		// get original start left clip for each x loop

        rcRect.left = orig_left_clip;

		// assume, for now, that we've got the whole tile
		// to work with horizontally

        rcRect.right = CELL_X_SIZE;

		// draw tiles left to right

		for (render_it_x = 0; render_it_x<num_x_tiles; render_it_x++)
		{
			pdds = lpddstextures[terrain_texture[which_cell]];
             
			ddrval = lpddsback->BltFast( x_offset, y_offset, pdds, &rcRect, DDBLTFAST_WAIT );
 
			// the following will only evaluate to a value other
			// than 64 once for every row of tiles

			x_offset+=CELL_X_SIZE-rcRect.left;

			++which_cell;

	        rcRect.left = 0;
	        rcRect.right = CELL_X_SIZE;

			// determine where to kick out of the loop
			// or just clip the right hand side of the rect

			clip_right = SCREEN_WIDTH - x_offset;

			if ((clip_right)<0)
				break;

			if ((clip_right)<64)
				if ((clip_right)==0)
			    {
				    ++which_cell;
				    break;
			    }
				else
					rcRect.right = clip_right;
			// sync time (optional)
		} // end for render_it_x

		// this will only evaluate to a value other than 64
		// once...

		y_offset+=CELL_Y_SIZE-rcRect.top;

		cell_x_save += TILE_X_NUM;
        which_cell = cell_x_save;

		rcRect.top = 0;
	    rcRect.bottom = CELL_Y_SIZE;

		// clip tiles w/ respect to Y screen boundary

		clip_bottom = SCREEN_HEIGHT - y_offset;

		if ((clip_bottom)<CELL_Y_SIZE)
			if ((clip_bottom)<=0)
			    break;
		    else
				rcRect.bottom = SCREEN_HEIGHT-y_offset;

			
    } // end for render_it_y

	// print a message on the backbuffer

	if (lpddsback->GetDC(&hdc) == DD_OK)
	{
		SetBkColor( hdc, RGB( 0, 0, 255 ) );
		SetTextColor( hdc, RGB( 255, 255, 0 ) );
		TextOut( hdc, 0, 0, "Keys: LEFT, RIGHT, UP, DOWN, .. and ESC", 39 );
		lpddsback->ReleaseDC(hdc);
	}   

	// flip back buffer to primary buffer
    lpddsprimary->Flip(NULL,DDFLIP_WAIT);

	last_frame_time = new_frame_time;

// return and let windows have some time

} // end Game_Main
This is a muddleheaded approach to doing a 2d tile scroller, to be sure, and I'm clearly using a couple of variables I don't need. But enough bagging, here's how it works. First off, I'm doing my drawing to a back buffer, which is set up in the DDInit() routine in tiled.cpp.

In Game_Init(), I'm using a routine from DDUTIL.CPP to load the twelve(12) .BMP files I'm using into twelve(12) LPDIRECTDRAWSUFACE's. I have some timer code in Game_Main() that I'm really not using here, and for a discussion of the timing paradigm I'm hinting at here, goto Dhonn's site to read more about it. Now, from the top of Game_Main() (ignoring the timer stuff):

px and py, which are meant to hold the coordinate LOWER/RIGHT HAND corner of the screen (the "virtual" coordinate that is), are moved based on keyboard input. Subtracting the SCREEN_WIDTH and SCREEN_HEIGHT, respectively, from px and py, gives starting LEFT and TOP positions we'll be working from.

At this point, we're going to start relying on the rcRect structure, because the tiles we'll be drawing aren't going to be perfectly aligned to the screen most of the time. I do tests to see if I need to clip the top or left sides of a tile, and change the contents of rcRect to reflect that.

The number of tiles to be drawn vertically and horizontally has to be derived now, so that it's always enough to fill the entire screen.

Now we're at the first loop, where we'll start with the original x clipping and assume that the right hand side of a tile is NOT clipped.

The inner loop uses BltFast to draw row's of tiles, each time checking if the right side of a tile needs to be clipped against the right side of the screen. In the inner loop, after the first BltFast, we're going to assume that the left side of each tile to be drawn won't need to be clipped again.

The outer loop works in a way similar to the inner loop.

Here's the code from my WinMain:

	while(1)
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
		{ 
			// test if this is a quit
			if (msg.message == WM_QUIT)
			break;

			// translate any accelerator keys
			TranslateMessage(&msg);

			// send the message to the window proc
			DispatchMessage(&msg);
		} // end if
		else
		{
		// do asynchronous processing here

		// call main logic module
		Game_Main();


		} // end else

	} // end while
You can change some of the defines, like, for example, changing SCREEN_WIDTH and SCREEN_HEIGHT to 800 and 600 respectively... or changing the tile sizes...

Just make sure that regardless of tilesize, the world is bigger than the screen, if you know what I mean...

Download the demo and source code


LinkExchange
LinkExchange Member



[ Homepage | my Games | Dos C Programming | Win95/DirectX C/C++ | VB Programming | Links ]


Jim the Loiterer's first web page - copyright 1997©,1998© - by James McCue

Get your FREE web pages here...