Sign in to follow this  
Eric Poitras

SDL 2D Tile Engine Question

Recommended Posts

Eric Poitras    122
hey, I'm trying to make a 2d tile engine and I'm having a problem with my code. I'm trying to randomly make a map from a bunch of tiles on a map surface then blit that map surface to my screen surface to display but for some reason it won't display. Everything is written in SDL btw. Take a look at my code and let me know if you have any ideas why it won't work.

#include <sdl_image.h>
#include <sdl.h>
#include <stdio.h>
#include <stdlib.h>

// Screen resolution
const int ScreenX = 640;
const int ScreenY = 480;
const int ScreenBBP = 16;

// Tile dimensions
const int TileDimension = 32;

SDL_Surface* g_pMainSurface = NULL; // surface for screen
SDL_Surface* g_pTileSurface = NULL; // surface for tile graphic
SDL_Surface* g_pMapSurface = NULL; // surface for the actual map
SDL_Event g_Event;  
SDL_Rect g_SrcRect, g_DstRect, g_MapRect; // RECT info for all surfaces

int main(int argc, char* argv[])
{
	// calculate the number of tiles needed to fill the screen
	int MapSizeX = ScreenX / TileDimension;
	int MapSizeY = ScreenY / TileDimension;
	
	// integers for the loop to make the map
	int y = 0;
	int x = 0;
	int RandTile = 0;

	// Initialize SDL Video
	SDL_Init(SDL_INIT_VIDEO);
	
	// Set Video mode on MainSurface
	g_pMainSurface = SDL_SetVideoMode(ScreenX,ScreenY,ScreenBBP,SDL_ANYFORMAT);

	// Load graphic for tiles
	g_pTileSurface = IMG_Load("grasstiles.tga");

	// Loop to randomly draw a map
	for(y = 0; y < MapSizeY; y++)
	{
		for(x = 0; x < MapSizeX; x++)
		{
			// Randomly select a number for the tile
			RandTile = rand() % 14;

			// Get the position of the random tile
			g_SrcRect.x= (RandTile * TileDimension) + RandTile ;
			g_SrcRect.y=0;
			g_SrcRect.w= g_SrcRect.x + TileDimension;
			g_SrcRect.h= TileDimension;

			// set the position of the next tile
			g_DstRect.x = x * TileDimension;
			g_DstRect.y = y * TileDimension;
			g_DstRect.w = g_DstRect.x + TileDimension;
			g_DstRect.h = g_DstRect.y + TileDimension;
			
			// Draw the tile to the Map Surface
			SDL_BlitSurface(g_pTileSurface, &g_SrcRect, g_pMapSurface, &g_DstRect);
		}
	}

	// get the dimensions for the mao
	g_MapRect.x= 0;
	g_MapRect.y= 0;
	g_MapRect.w= ScreenX;
	g_MapRect.h= ScreenY;
	
	for(;;)
	{
		if(SDL_PollEvent(&g_Event)==0)
		{
			// blit map surface to main surface
			SDL_BlitSurface(g_pMapSurface, &g_MapRect, g_pMainSurface, &g_MapRect);
			
			// update screen
			SDL_UpdateRect(g_pMainSurface, 0, 0, 0, 0);
		}
		else
		{ if(g_Event.type==SDL_QUIT) break; }
	}
	
	return(0);
}
	


Share this post


Link to post
Share on other sites
Roots    1625
Hmmm, I don't see anything wrong in particular...so you say nothing displays at all? Why don't you take out the code where you display the map and take it outside of the loop just to be safe? Or maybe instead of blitting all your tile images to an intermediate image, blit them directly to the surface and see what happens?

Print statements are your friends. :) Make sure that every blit statement is being executed. You might have a problem with that blit surface being inside the SDL_Event loop because it might blit the map and then loop back and immediately blit again, but it might overwrite all your tiles and instead display a blank screen. That's the only thing I can think of, but I haven't touched the SDL video functions in a while. Hope that helps.

Share this post


Link to post
Share on other sites
Eric Poitras    122
ok I tried displaying it directly to the main surface and it displays but not when I try drawing the map on another surface first. So of course I ran the debugger and it seems that nothing gets actually on to the MapSurface. The pointer always stays at NULL. No idea why. Anyway, would it be best If I just displayed directly on the Main Surface? I forgot what was faster, haven't done this in a while.

Share this post


Link to post
Share on other sites
Roots    1625
Quote:
Original post by Eric Poitras
ok I tried displaying it directly to the main surface and it displays but not when I try drawing the map on another surface first. So of course I ran the debugger and it seems that nothing gets actually on to the MapSurface. The pointer always stays at NULL. No idea why. Anyway, would it be best If I just displayed directly on the Main Surface? I forgot what was faster, haven't done this in a while.


Yes, I always blit to the screen instead of blitting to an intermediate surface and then blitting that to the screen. (I think it should be slightly faster going directly to the screen).

I also highly recommend you use a double buffer and call SDL_Flip instead of SDL_UpdateRect(screen, 0, 0, 0, 0); This eliminates a couple of common video display problems, I forget the names of them at the moment. If you enable this option, then you can freely draw to the screen without worry of anything being partially or incompletely displayed. When you are done with your drawing, just call SDL_Flip and it will flip the screen surface you just worked on with what's actually on the screen. Its a piece of cake and works great.

Share this post


Link to post
Share on other sites
Eric Poitras    122
yup I already switched to SDL_Flip and Double Buffer. I just didn't bother changing it in the code above. I know I can do this stuff easily but last time I actually programmed any 2D was about 2 years ago and it was all in DirectDraw. Anyway, just another quick question.

How to I set up the fastest way to get the x and y values of my source tiles if I have a tileset of multiple rows and multiple columns. like for example this is what I have for just one row:

g_SrcRect.x = (RandTile * TileDimension) + RandTile;
g_SrcRect.y = 0;
g_SrcRect.w = (g_SrcRect.x + TileDimension);
g_SrcRect.h = TileDimension;

Just to clarify, the RandTile variable is just a random tile number and the + Randtile I have there is because I have a 1 pixel border around all the tiles. I know there's a quick way you can do this with modulus but I can't remember how.

Share this post


Link to post
Share on other sites
Roots    1625
Hmm, ok let me get this straight. So you have a big image that is made up of several rows and columns of tiles, and you want to know the fastest way to get a random X, Y coordiante? I think that's what you're asking...well anyway lets say you have 5 tile rows and 3 tile columns. To get the x,y coordinates of a random tile (x,y coordiante is the top left hand corner of a tile) using the modulus operator try something like this:


Src_rect.x = rand() % tile_rows;
Src_rect.y = rand() % tile_cols;


The modulus operator returns the remainder after division. So in the Src_rect.x case you are dividing a random number by 5, so you can have a remainder between 0 and 4 (integers only). Same idea with the cols. I think that's the answer you're looking for.



I did my own random map generator for some test code a while back, but instead of keeping all the tiles in a tileset like you did I kept each tile in an individual file. Here's the function.


void MapData::LoadData(string map_name) {
// First remove all the old data in our vectors
tile_vector.clear();
for (unsigned int r = 0; r < map_vector.size(); r++)
map_vector[r].clear();
map_vector.clear();

// The following is temporary code until we get the data configuration code working
if (map_name == "testing") {
num_tile_rows = TILE_ROWS * 2; // Set the # of tile rows in the new map
num_tile_cols = TILE_COLS * 4; // Set the # of tile columns in the new map
col_pos = 2 * TILE_COLS + 4; // Set the initial column position
row_pos = TILE_ROWS; // Set the initial row position
num_tiles = 15; // Set the number of unique tile images that compose the map

//tile_vector.resize(num_tiles); // Resize our tile vector. For some weird reason, this causes a seg fault

// Load the tile vector with all of the tile filename strings
tile_vector.push_back("tile/bones_01.png");
tile_vector.push_back("tile/clay_01.png");
tile_vector.push_back("tile/desert_01.png");
tile_vector.push_back("tile/desert_02.png");
tile_vector.push_back("tile/desert_03.png");
tile_vector.push_back("tile/dirt_01.png");
tile_vector.push_back("tile/forest_01.png");
tile_vector.push_back("tile/path_01.png");
tile_vector.push_back("tile/path_02.png");
tile_vector.push_back("tile/road_01.png");
tile_vector.push_back("tile/road_02.png");
tile_vector.push_back("tile/rock_01.png");
tile_vector.push_back("tile/rock_02.png");
tile_vector.push_back("tile/wall_01.png");
tile_vector.push_back("tile/water_01.png");

// Load all the new tile images into the image_cache
for (unsigned int i = 0; i < tile_vector.size(); i++)
VideoManager.LoadImage(tile_vector[i]);

// Resize our map_vector
map_vector.resize(num_tile_rows);
for (int r = 0; r < num_tile_rows; r++)
map_vector[r].resize(num_tile_cols);

// Now we go thru and generate a random map with the tiles we loaded in. Cool huh?
for (int r = 0; r < num_tile_rows; r++) {
for (int c = 0; c < num_tile_cols; c++) {
if (r == 0 || r == num_tile_rows - 1) // Make the top and bottom one tile for distinction
map_vector[r][c] = num_tiles - 1; // Top & bottom are now "water"
else if (c == 0 || c == num_tile_cols - 1) // Make the sides one tile for distinction
map_vector[r][c] = num_tiles - 2; // Top & bottom are now "wall"
else
map_vector[r][c] = RandomNum(0, num_tiles - 3); // Assign a random tile in the map_vector
}
}
}
}


RandomNum is a function I wrote myself that returns a Guassian random variable. The vectors are a little confusing at first but it works like this: the 2 dimension map_vector stores a bunch of indeces. These indeces are used to access the tile_vector, which stores the actual images.


I got the code working and managed to get a sprite to walk around on a randomly generated map. If you want to see more code go here:

http://sourceforge.net/projects/allacrost/

Then click on "Browse CVS" and go to module_01 => src and view map.h and map.cpp. It's primitive but it works. :) Hope that helps.

Share this post


Link to post
Share on other sites
Eric Poitras    122
hmmm ok that wasn't exactly what I wanted. I'll explain in greater detail. First let's start with my tileset, I have 190 tiles at 32x32 pixels, of course I couldn't put 190 tiles in one row because I don't even think that would fit on a surface so I divided it up in 10 rows with 19 tiles in each.

ok so now what I'm doing is instead of having variables for rows and columns, I only have one variable for the tile. meaning that each tiles are numbered like so:

[0] [1] [2] [3] [4]
[5] [6] [7] [8] [9]
[10][11][12][13][14]

ok so now let's say Randtile = 2. The rect would look like this:

Rect.x= RandTile * TileDimension;
Rect.y= 0;
Rect.w= Rect.x + TileDimension;
Rect.h= TileDimension;

ok that's all good if I only have one row but I have more than one row and that rect won't suffice the way it is. Let's say I want to display the 7th tile for instance, how would I make a rect that would handle drawing all the tiles. I hope this makes some sense.

btw Roots, how's your game comming along?

Share this post


Link to post
Share on other sites
JTippetts    12970
Well, if your tiles are arranged in a 19x10 grid (19 across, 10 down) you can get the row/col of any given tile, T, by:

Row = T / 19
Col = T % 19


Multiply Row and Col by the size of a tile to get the actual pixel coords of that tile within the image.

Share this post


Link to post
Share on other sites
Eric Poitras    122
ok I just don't get it. Here's the routine I use for drawing.



int TileDimension = 32;

for(y = 0; y < MapSizeY; y++)
{
for(x = 0; x < MapSizeX; x++)
{
// Randomly select a number for the tile
RandTile = rand() % 190;

// Get the position of the random tile
g_SrcRect.x= (RandTile * TileDimension) + RandTile;
g_SrcRect.y= 0;
g_SrcRect.w= (g_SrcRect.x + TileDimension);
g_SrcRect.h= TileDimension;

// set the position of the next tile
g_DstRect.x = x * TileDimension;
g_DstRect.y = y * TileDimension;
g_DstRect.w = g_DstRect.x + TileDimension;
g_DstRect.h = g_DstRect.y + TileDimension;

// Draw the tile to the Map Surface
SDL_BlitSurface(g_pTileSurface, &g_SrcRect, g_pMainSurface, &g_DstRect);
}
}


Share this post


Link to post
Share on other sites
JTippetts    12970
Try this for your source rectangle:

g_SrcRect.x= (RandTile % 19) * TileDimension;
g_SrcRect.y= (RandTile / 19) * TileDimension;
g_SrcRect.w= TileDimension;
g_SrcRect.h= TileDimension;


Note that in SDL_BlitSurface(), width and height in the source rectangle specify the dimensions in pixels of the area to draw, not the coordinates of the lower right hand corner. They are ignored in the dest rectangle. Note also that this above does not necessarily take into account any pixel borders around the tiles; you'll have to tweak to compensate.

Share this post


Link to post
Share on other sites
Roots    1625
Glad you figured it out, and sorry I mis-understood.

Quote:
Original post by Eric Poitras

btw Roots, how's your game comming along?


Its coming along great now, thanks for asking. :) We kinda had a 2-month hiatus because everyone was so busy with school and other things during October/November, but I only have a week left (and no finals woo!) so I've been working like MAD on it for the past week. I've honestly never been so happy to be able to write C++/Lua code. ^_^ And we got a new site design coming real soon that's going to beat the pants off the old one :D

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Why doesn't my code work? It worked until I put in putmap and makemap... now the map shows (if i remove the comments) but the map surface apparently has nothing blitted on to it :(

#include
#include


SDL_Surface *screen;
SDL_Surface *bush;
SDL_Surface *grass;
SDL_Surface *map;

void showthing(SDL_Surface *img, int x, int y);
void drawmap(int height, int width);
void init_tiles();
void makemap(SDL_Surface *img, int x, int y);
void putmap (int x, int y);


int main(int argc, char *argv[])
{
Uint8* keys;


if ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 )
{
printf("Unable to init SDL: %s\n", SDL_GetError());
exit(1);
}
atexit(SDL_Quit);

screen=SDL_SetVideoMode(640,480,32,SDL_SWSURFACE|SDL_DOUBLEBUF);
if ( screen == NULL )
{
printf("Unable to set 640x480 video: %s\n", SDL_GetError());
exit(1);
}
init_tiles();
drawmap(10,10);
putmap(40,40);
SDL_Flip(screen);
int done=0;

while(done == 0)
{
SDL_Event event;

while ( SDL_PollEvent(&event) )
{
if ( event.type == SDL_QUIT ) { done = 1; }

if ( event.type == SDL_KEYDOWN )
{
if ( event.key.keysym.sym == SDLK_ESCAPE ) { done = 1; }
}


}
}
return 0;
}


void showthing(SDL_Surface *img, int x, int y ) {
SDL_Rect dest; //always blit things to rects

dest.x = x;
dest.y = y;
SDL_BlitSurface(img, NULL, screen, &dest); //first the image, then piece u want (as rect)
//Null=all
//then the surface u want to blit it to (screen)
//then the dest. (h, w, x, y and so forth)
}

void init_tiles(){

bush = SDL_LoadBMP("bush.bmp");
grass = SDL_LoadBMP("grass.bmp");
}

void drawmap (int height, int width){
int x,y,tilex,tiley;
int tilesize = 16;
for (tiley=0;tiley y=tiley*tilesize;
for (tilex=1;tilex x=tilex*tilesize;
if ((x%10 + y%5) == 9) {
makemap(bush, x, y);
//showthing(bush, x, y);
}
else {
//showthing(grass, x, y);
makemap(grass, x, y);
}
}
}

}//end func

void makemap(SDL_Surface *img, int x, int y){
SDL_Rect dest;

dest.x = x;
dest.y = y;
SDL_BlitSurface(img, NULL, map, &dest); //first the image, then piece u want (as rect)
//then the surface u want to blit it to (screen)
//then the dest. (h, w, x, y and so forth)
}

void putmap(int x, int y){
SDL_Rect dest;

dest.x = x;
dest.y = y;
SDL_BlitSurface(map, NULL, screen, &dest);
}

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this