Purple Martians
Technical Code Descriptions
Bitmaps and tiles
Overview
History
Data Structure
Loading tile bitmaps
Rebuilding tile bitmaps
Using indexed tiles
Overview
Purple Martians is a tile based game. Each tile is 20 x 20 pixels. (A design decision made in 1997)
Each level is 100x100 tiles or 2000 x 2000 pixels.
The tiles are stored in arrays of bitmaps and accessed by the array indexes.
History
From the very beginning in 1997 up until 2017, the tiles were 256 color, 8 bit bitmaps.
I made my own bitmap and palette editor and drew all my bitmaps with those tools.
I ended up making my own custom palette with 15 colors, each color being faded 16 times.
I used to save and load tiles to disk one get_pixel and one put_pixel at a time.
I'm kind of embarrassed to say I continued to do that up until 2017!
I finally converted the whole project to 24 bit color in 2017.
The main push to convert came from all the work arounds I had to do in Windows 7.
8 bit color does not render properly in full screen modes with Allegro 4. (although windowed mode was fine)
And now in Allegro 5, its just not supported at all.
Unfortunately, my bitmap and palette editor did not survive the conversion. Now I have to use an external editor like gimp.
I used to store different colorized versions of players and doors with the main tiles.
I have since converted door and player tiles to have their own tilemap files.
Data Structure
The bitmap files for the tiles are stored in the bitmaps/ folder:
tiles.bmp (1024 main tile storage)
block_tiles.bmp (1024 tiles used for blocks in levels)
player_tiles.bmp (16 colors x 24 shapes)
door_tiles.bmp (16 colors x 8 shapes x 2 door types)
Each file is loaded to 2 different types of allegro bitmaps. (video bitmaps and memory bitmaps)
The video bitmaps are stored in the graphics card memory, are much faster, and used for all drawing in the game.
Unfortunately video bitmaps are lost whenever the display changes, so I also store them as memory bitmaps.
That way I can very quickly restore the video bitmaps from the memory bitmaps when needed.
I also create arrays of sub-bitmaps so that I can draw tiles with indexes.
Here are all the bitmap declarations:
#define NUM_SPRITES 1024
class mwBitmap
{
public:
ALLEGRO_BITMAP *tilemap;
ALLEGRO_BITMAP *btilemap;
ALLEGRO_BITMAP *ptilemap;
ALLEGRO_BITMAP *dtilemap;
ALLEGRO_BITMAP *M_tilemap;
ALLEGRO_BITMAP *M_btilemap;
ALLEGRO_BITMAP *M_ptilemap;
ALLEGRO_BITMAP *M_dtilemap;
ALLEGRO_BITMAP *tile[NUM_SPRITES];
ALLEGRO_BITMAP *btile[NUM_SPRITES];
ALLEGRO_BITMAP *player_tile[16][32];
ALLEGRO_BITMAP *door_tile[2][16][8];
Here I actually create the bitmaps:
void mwBitmap::create_bitmaps(void)
{
// this bitmap format will be used for all bitmaps, it is never changed
al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ANY_32_WITH_ALPHA);
// these bitmap flags are only for the M_XXX memory bitmaps
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
// create memory bitmaps as temp storage for restoring tilemaps after screen change
M_tilemap = create_and_clear_bitmap(640, 640);
M_btilemap = create_and_clear_bitmap(640, 640);
M_ptilemap = create_and_clear_bitmap(480, 320);
M_dtilemap = create_and_clear_bitmap(160, 640);
// this bitmap format is used for tiles
al_set_new_bitmap_flags(ALLEGRO_NO_PRESERVE_TEXTURE | ALLEGRO_VIDEO_BITMAP);
// create tilemap bitmaps
tilemap = create_and_clear_bitmap(640, 640);
btilemap = create_and_clear_bitmap(640, 640);
ptilemap = create_and_clear_bitmap(480, 320);
dtilemap = create_and_clear_bitmap(160, 640);
}
Loading tile bitmaps
'load tiles()' is only called only once from 'initial_setup()'
- tilemaps are loaded from files
- M_tilemaps are created from tilemaps
- sub_bitmaps are created from tilemaps
int mwBitmap::load_tiles(void)
{
int load_error = 0;
// get main tiles
tilemap = al_load_bitmap("bitmaps/tiles.bmp");
if (!tilemap)
{
mInput.m_err("Can't load tiles from bitmaps/tiles.bmp");
load_error = 1;
}
else
{
al_convert_mask_to_alpha(tilemap, al_map_rgb(0, 0, 0)) ;
al_set_target_bitmap(M_tilemap);
al_draw_bitmap(tilemap, 0, 0, 0);
for (int y=0; y<32; y++)
for (int x=0; x<32; x++)
tile[y*32 + x] = al_create_sub_bitmap(tilemap, x*20, y*20, 20, 20);
}
// get block tiles
btilemap = al_load_bitmap("bitmaps/block_tiles.bmp");
if (!btilemap)
{
mInput.m_err("Can't load tiles from bitmaps/block_tiles.bmp");
load_error = 1;
}
else
{
al_convert_mask_to_alpha(btilemap, al_map_rgb(0, 0, 0)) ;
al_set_target_bitmap(M_btilemap);
al_draw_bitmap(btilemap, 0, 0, 0);
for (int y=0; y<32; y++)
for (int x=0; x<32; x++)
btile[y*32 + x] = al_create_sub_bitmap(btilemap, x*20, y*20, 20, 20);
}
// get player tiles
ptilemap = al_load_bitmap("bitmaps/player_tiles.bmp");
if (!ptilemap)
{
mInput.m_err("Can't load tiles from bitmaps/player_tiles.bmp");
load_error = 1;
}
else
{
al_convert_mask_to_alpha(ptilemap, al_map_rgb(0, 0, 0)) ;
al_set_target_bitmap(M_ptilemap);
al_draw_bitmap(ptilemap, 0, 0, 0);
for (int a=0; a<16; a++)
for (int b=0; b<24; b++)
player_tile[a][b] = al_create_sub_bitmap(ptilemap, b*20, a*20, 20, 20);
}
// get door tiles
dtilemap = al_load_bitmap("bitmaps/door_tiles.bmp");
if (!dtilemap)
{
mInput.m_err("Can't load tiles from bitmaps/door_tiles.bmp");
load_error = 1;
}
else
{
al_convert_mask_to_alpha(dtilemap, al_map_rgb(0, 0, 0)) ;
al_set_target_bitmap(M_dtilemap);
al_draw_bitmap(dtilemap, 0, 0, 0);
for (int a=0; a<16; a++)
for (int b=0; b<8; b++)
{
door_tile[0][a][b] = al_create_sub_bitmap(dtilemap, b*20, a*20, 20, 20);
door_tile[1][a][b] = al_create_sub_bitmap(dtilemap, b*20, 320+a*20, 20, 20);
}
}
load_sprit(); // get animation sequences and shape attributes
if (load_error) return 0;
else return 1;
}
Rebuilding tile bitmaps
The tilemap bitmaps are rebuilt if the display changes and their contents are lost.
This is done by drawing the memory bitmaps (M_tilemaps) to the video bitmaps (tilemaps).
The sub_bitmaps do not need to rebuilt, just their parents.
This is only called from 'proc_screen_change()'
void mwBitmap::rebuild_bitmaps(void)
{
// rebuild main tiles
al_set_target_bitmap(tilemap);
al_clear_to_color(al_map_rgba(0,0,0,0));
al_draw_bitmap(M_tilemap, 0, 0, 0);
// rebuild block tiles
al_set_target_bitmap(btilemap);
al_clear_to_color(al_map_rgba(0,0,0,0));
al_draw_bitmap(M_btilemap, 0, 0, 0);
// rebuild player tiles
al_set_target_bitmap(ptilemap);
al_clear_to_color(al_map_rgba(0,0,0,0));
al_draw_bitmap(M_ptilemap, 0, 0, 0);
// rebuild door tiles
al_set_target_bitmap(dtilemap);
al_clear_to_color(al_map_rgba(0,0,0,0));
al_draw_bitmap(M_dtilemap, 0, 0, 0);
...
Using indexed tiles
The indexed tiles are used like this:
al_draw_bitmap(mBitmap.tile[shape], x, y, 0);
where shape is an int from 0-1023.
Both btile[1024] and tile[1024] have only one index [0-1023]
mBitmap.ptilemap[16][32] has an 2nd index for color.
mBitmap.player_tile[color 0-15][shape 0-23]
mBitmap.dtilemap[2][16][8] has an 2nd index for color and a 3rd for the 2 types of door shapes
mBitmap.door_tile[shape type 0-1][color 0-15][shape 0-7]