Hello everybody,
Yesterday I put together a nice tile engine with SDL. Now I have the problem that it runs so slow that I doubt SDL can even handle it.
Here are the details:
- 2 layered tile engine ( 320x200 screen size; 10x10 tile size; 32x20 tiles on scree), first layer is ground layer and second layer is detail layer (rendered with transparent color key) for stones, tables, trees etc. Tiles are not isometric but square sized.
- static sprite support, at the moment a soldier is drawn on the tileset, which can be moved by pressing the cursor keys.
- a sprite that replaces the hidden standard cursor.
- a particle system that creates real-time rendered sparks whenever you click the mouse.
I am sure my code is not even close to being optimized, but I'd still like to know whether I can optimize the code I already have in a simple way or whether I should rather switch to something with faster blitting routines.
Here is the relevant code as well:
The tile engine part:
void TilesDrawRect ( int tile_layer, int tile_value, int x1, int y1, int x2, int y2 )
// draws a rectangle to the tile array variable
{
for ( int x = x1; x < x2; x++ )
{
for ( int y = y1; y < y2; y++ )
{
tile[tile_layer][x][y] = tile_value;
}
}
}
void TilesDrawLine ( int tile_layer, int tile_value, int x1, int y1, int x2, int y2 )
// draws a line to the tile array variable
{
if ( x1 == x2 )
{
for ( int y = y1; y < y2; y++ )
{
tile[tile_layer][x1][y] = tile_value;
}
}
if ( y1 == y2 )
{
for ( int x = x1; x < x2; x++ )
{
tile[tile_layer][x][y1] = tile_value;
}
}
}
void LoadTiles ( int layer )
// loads data for one layer and puts it to the tile variable
{
if ( layer == 1 )
{
TilesDrawRect ( layer, 1, 0, 0, TILES_X, TILES_Y );
TilesDrawRect ( layer, 2, 14, 0, 18, TILES_Y );
TilesDrawLine ( layer, 4, 14, 0, 14, TILES_Y );
TilesDrawLine ( layer, 5, 18, 0, 18, TILES_Y );
}
else if ( layer == 2 )
{
TilesDrawRect ( layer, 2, 0, 0, TILES_X, TILES_Y );
for ( int i = 0; i < 20; i++ )
{
int rand_x, rand_y, rand_tile_value;
rand_x = rand ( ) % TILES_X;
rand_y = rand ( ) % TILES_Y;
rand_tile_value = /*( rand ( ) % 2 ) +*/ 2;
tile[layer][rand_x][rand_y] = rand_tile_value;
}
}
}
void DrawTiles ( int layer )
// draws all the tiles in one layer to the screen
{
if ( layer == 1 )
{
for ( int i = 0; i < TILES_X; i++ )
{
for ( int j = 0; j < TILES_Y; j++ )
{
SDL_Surface *image;
SDL_Rect rect;
rect.x = i * TILE_WIDTH;
rect.y = j * TILE_HEIGHT;
if ( tile[layer][j] == 0 )
return;
else if ( tile[layer][j] == 1 )
image = SDL_LoadBMP ( "tiles/tile1.bmp" );
else if ( tile[layer][j] == 2 )
image = SDL_LoadBMP ( "tiles/tile2.bmp" );
else if ( tile[layer][j] == 3 )
image = SDL_LoadBMP ( "tiles/tile3.bmp" );
else if ( tile[layer][j] == 4 )
image = SDL_LoadBMP ( "tiles/tile1-2_x.bmp" );
else if ( tile[layer][j] == 5 )
image = SDL_LoadBMP ( "tiles/tile2-1_x.bmp" );
else
ReturnError ( "Invalid tile value!" );
if ( image == NULL )
ReturnError ( "Couldn't create surface to draw image!" );
if ( image->format->palette && screen->format->palette )
SDL_SetColors ( screen, image->format->palette->colors, 0, image->format->palette->ncolors );
if ( SDL_BlitSurface ( image, NULL, screen, &rect ) < 0 )
ReturnError ( "Couldn't blit surface to screen!" );
SDL_UpdateRect ( screen, TILE_WIDTH * i, TILE_HEIGHT * j, image->w, image->h );
SDL_FreeSurface ( image );
}
}
}
else if ( layer == 2 )
{
for ( int i = 0; i < TILES_X; i++ )
{
for ( int j = 0; j < TILES_Y; j++ )
{
SDL_Surface *image;
SDL_Rect rect;
rect.x = i * TILE_WIDTH;
rect.y = j * TILE_HEIGHT;
if ( tile[layer][j] == 0 )
return;
else if ( tile[layer][j] == 1 )
image = SDL_LoadBMP ( "tiles/deco1.bmp" );
else if ( tile[layer][j] == 2 )
image = SDL_LoadBMP ( "tiles/deco2.bmp" );
else if ( tile[layer][j] == 3 )
image = SDL_LoadBMP ( "tiles/deco3.bmp" );
else
ReturnError ( "Invalid tile value!" );
SDL_SetColorKey ( image, SDL_SRCCOLORKEY, SDL_MapRGB ( image->format, 0, 0, 0 ) );
if ( image == NULL )
ReturnError ( "Couldn't create surface to draw image!" );
if ( image->format->palette && screen->format->palette )
SDL_SetColors ( screen, image->format->palette->colors, 0, image->format->palette->ncolors );
if ( SDL_BlitSurface ( image, NULL, screen, &rect ) < 0 )
ReturnError ( "Couldn't blit surface to screen!" );
SDL_UpdateRect ( screen, TILE_WIDTH * i, TILE_HEIGHT * j, image->w, image->h );
SDL_FreeSurface ( image );
}
}
}
}
So much for the 2-layered tile engine. Now let's go to the object system ( for static sprites ):
class object
// object is the base class for any object on the screen
{
public:
char *file_name;
int x, x_vel, y, y_vel, speed;
void draw ( int x, int y );
void refresh ( void );
};
void object::draw ( int x, int y )
{
SDL_Surface *image;
SDL_Rect rect;
rect.x = x;
rect.y = y;
image = SDL_LoadBMP ( file_name );
SDL_SetColorKey ( image, SDL_SRCCOLORKEY, SDL_MapRGB ( image->format, 0, 0, 0 ) );
if ( image == NULL )
ReturnError ( "Couldn't create surface to draw image!" );
if ( image->format->palette && screen->format->palette )
SDL_SetColors ( screen, image->format->palette->colors, 0, image->format->palette->ncolors );
if ( SDL_BlitSurface ( image, NULL, screen, &rect ) < 0 )
ReturnError ( "Couldn't blit surface to screen!" );
SDL_UpdateRect ( screen, x, y, image->w, image->h );
SDL_FreeSurface ( image );
}
void object::refresh ( void )
{
if ( speed < 1 )
speed = 1;
for ( int i = 0; i < speed; i++ )
{
if ( x_vel < 0 && x <= i )
x_vel = 0;
if ( x_vel > 0 && ( x + TILE_WIDTH ) >= ( SCREEN_WIDTH - i ) )
x_vel = 0;
if ( y_vel < 0 && y <= i )
y_vel = 0;
if ( y_vel > 0 && ( y + TILE_HEIGHT ) >= ( SCREEN_HEIGHT - i ) )
y_vel = 0;
}
x += x_vel;
y += y_vel;
}
object soldier;
object mouse
In the end I define and later on I use two objects, one time the soldier sprite for the player character and one time the fake cursor.
And now to the particle system:
void DrawPixel ( SDL_Surface *pSurf, int x, int y, Uint32 color )
{
int bpp;
Uint8 *p;
if ( x < 0 || x >= pSurf->w || y < 0 || y >= pSurf->h )
return;
bpp = pSurf->format->BytesPerPixel;
p = (Uint8 *)pSurf->pixels + y * pSurf->pitch + x * bpp;
switch ( bpp )
{
case 1:
*p = color;
break;
case 2:
*(Uint16 *)p = color;
break;
case 4:
*(Uint32 *)p = color;
break;
case 3:
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
p[0] = (color >> 16) & 0xff;
p[1] = (color >> 8) & 0xff;
p[2] = color & 0xff;
#else
p[0] = color & 0xff;
p[1] = (color >> 8) & 0xff;
p[2] = (color >> 16) & 0xff;
#endif
break;
}
}
particle_t *CreateParticle ( float x, float y, Uint32 color, float vx, float vy )
{
particle_t *p = (particle_t *)malloc(sizeof(particle_t));
p->color = color;
p->x = x;
p->y = y;
p->vx = vx;
p->vy = vy;
p->next = g_pParticles;
if (p->next)
p->next->pprev = &p->next;
p->pprev = &g_pParticles;
g_pParticles = p;
return p;
}
void RemoveParticle ( particle_t *p )
{
if (p->next)
p->next->pprev = p->pprev;
*p->pprev = p->next;
free ( p );
}
void ParticleThink ( )
{
particle_t *next = g_pParticles;
while ( next )
{
particle_t *p = next;
next = p->next;
p->vy += 300 / 1000.0f * frametime;
p->x += p->vx * frametime/1000.0f;
p->y += p->vy * frametime/1000.0f;
if (p->x < 0 || p->x >= SCREEN_WIDTH || p->y >= SCREEN_HEIGHT )
RemoveParticle ( p );
}
}
void DrawParticle ( )
{
particle_t *p;
for ( p = g_pParticles; p; p = p->next )
DrawPixel(screen, (int)p->x, (int)p->y, p->color);
}
void EmitParticles ( )
{
while ( lastemit + PARTICLE_DELAY < curframe )
{
float speed, angle;
float vx, vy;
Uint32 color;
lastemit += PARTICLE_DELAY;
color = SDL_MapRGB ( screen->format, ( rand ( ) % 75 ) + 160, ( rand ( ) % 75 ) + 160, ( rand ( ) % 25 ) + 50 );
speed = (float)( rand ( ) % 100 );
angle = (float)( rand ( ) * ( ( 2.0 * MATH_PI ) / RAND_MAX ) );
vx = (float)( cos ( angle ) * speed );
vy = (float)( sin ( angle ) * speed );
CreateParticle ( (float)mouse_x, (float)mouse_y, color, vx, vy );
}
}
void ExitParticles ( void )
{
while(g_pParticles)
RemoveParticle ( g_pParticles );
}
void P_Init ( )
{
atexit ( ExitParticles );
lastemit = curframe;
}
ALlright now a last bit of code. Here's how I blit everything to the screen every frame:
void Repeatedly_Execute ( void )
// called every frame
{
// keep our mouse coordinate variables accurate
SDL_GetMouseState ( &mouse_x, &mouse_y );
// draw the soldier
soldier.draw ( soldier.x, soldier.y );
soldier.refresh ( );
// draw the first tile layer
DrawTiles ( 1 );
// then draw the second tile layer
DrawTiles ( 2 );
mouse.x = mouse_x;
mouse.y = mouse_y;
mouse.draw ( mouse.x, mouse.y );
if ( draw_mouse_particles )
{
EmitParticles ( );
}
ParticleThink ( );
//SDL_FillRect(screen, 0, SDL_MapRGB ( screen->format, 0, 0, 0 ) );
if ( !SDL_LockSurface ( screen ) )
{
DrawParticle ( );
SDL_UnlockSurface ( screen );
}
else
ReturnError ( "Couldn't lock the screen surface! ");
// flip the buffer
SDL_Flip ( screen );
}
Anybody have any ideas on why this is so slow? I am really stuck with this.
Am I doing something obviosly the wrong way?
EDIT: I should probably say that it's even to slow without the particle system and the fake cursor and soldier. The 2-layered tile engine is already slow and then the rest adds up to "unplayableness".