Generating Tiles and Maps with gconvert

From Uzebox Wiki
Jump to navigation Jump to search

Gconvert is a C++ port of Uzebox Tool's gfx converter and it has an interface, which means you no longer have to edit code, compile and run to get the job done. This tutorial will teach you how to make your tileset ready for conversion and configure gconvert. I'm going to use The Gimp, but you can use any other tool that can convert your tileset to either raw (8bpp, no headers) or PNG-8.

Before We Start

This tutorial assumes:

  • All Uzebox tools are on your execution PATH
  • You have a project structured in the "standard" Uzebox way:
/Urbanmess   -> project root
 /data       -> ressources like images, sound, .inc and gconvert files 
 /default    -> build artifacts and makefile 
 main.c 

Our Tileset

Urbanzoom.png

We are going to use this tileset I made just for this tutorial, it's in PNG-24 format (24-bits per pixel).


Urbanmess.png


I am also providing you a version with the visible grid, coordinates and maps to help you understand this guide.

As your can see, the top left tile(which is gonna be tile 0) is black, because it is the tile used by the ClearVram() function, it doesn't have to be like that (maybe it has in mode 2), but that most people wouldn't want a brick wall to be their "empty" screen.

Converting

GConvert needs the input graphics file to be in 8bits per pixel, palette indexes. To simplify the process, you would normally export your image straight in PNG-8 with the right palette. Though, for the sake of this tutorial, we'll assume we have some other format in hands.

Now that we have everything we need, it's time to start working.

Generating an Image File That Can Be Converted (GIMP)

1. Open Urbanmess.png with The Gimp and go to Windows > Dockable Dialogs > Palettes.

2. Right-click in the palettes view and, from the pop-up menu, select Import Palette.... In the dialog window, click Palette File under Select source and locate the .ACT palette file under where_you_checked_out_the_svn/gfx/uzebox.act. Click Import.

3. Go to Image > Mode > Indexed and select use custom palette, Click the colored button to show your available palettes. From the drop down, pick the uzebox.act palette. Make sure you uncheck remove unused colors from colormap and click convert.

4. Now save it: File > Export as... and select PNG file format, I'm naming it urbanmess.png. Select compression level of zero.. Note that it can also be saved as a RAW file.

Generating an Image File That Can Be Converted (Photoshop)

1. Open Urbanmess.png with Photoshop and go to Image > Mode > Indexed Color...

2. In the palette dropdown, select Custom... to open the palette selection dialog and click the Load... button to select the Uzebox palette file located in

where_you_checked_out_the_svn/gfx/uzebox.act. 

Clik Ok twice.

3. Now save it as a PNG file, I'm naming it urbanmess.png. When asked, save in non-interlaced mode. Note that it can also be saved as a RAW file.

Writing Our XML File

Gconvert reads an xml file to know what to do. This is what our urbanmess.gconvert.xml looks like:

<?xml version="1.0" ?>
<gfx-xform version="1">
    <input file="urbanmess.png" type="png" tile-width="8" tile-height="8" width="96" height="80" />
    <output file="urbanmess.inc">
        <tiles var-name="uMTiles"/>
        <maps pointers-size="8">
	    <map var-name="map_graffiti" left="0" top="2" width="3" height="4"/>
	    <map var-name="map_title" left="3" top="0" width="9" height="7"/>
	    <map var-name="map_window" left="0" top="7" width="2" height="2"/>			
	</maps>	
    </output>
</gfx-xform>

Edit the input and output values to match your needs. As you can see on the "big tileset", the tiles are 8x8 and it is 12 tiles wide by 10 tiles tall. Width and height of the image are in pixels, not in tiles and have to be specified if you're converting a raw image (but are optional for PNG). The exact coordinates and size of the maps are also indicated.

Pointer-size depends on the mode you're going to use. Video mode 3 uses 8 bit map indexes.

Running gconvert

1. Cd into the directory where your urbanmess.gconvert.xml is and run

C:\work\uzebox\Urbanmess\data> gconvert urbanmess.gconvert.xml

2. If the xml doesn't have any problems, you should get an output similar to this one:

Current working directory: C:\work\uzebox\Urbanmess\data
Loading transformation file: urbanmess.gconvert.xml ...
File version: 1
Input file: C:\work\uzebox\Urbanmess\data\urbanmess.png
Input file type: png
Input width: 96px
Input height: 80px
Tile width: 8px
Tile height: 8px
Output file: C:\work\uzebox\Urbanmess\data\urbanmess.inc
Tiles variable name: uMTiles
Maps pointers size: 8
Map elements: 3
Blank Tile index: 0
File exported successfully!
Unique tiles found: 61
Total size (tiles + maps): 4074 bytes

Note that duplicated tiles were eliminated. Instead of having 120 tiles, we only have 61.

The Generated Inc File

The tiles and maps are all going to be in the same file, unlike Uzebox Tools where you would get a file for maps and a file for tiles. There are also defines, such as:

#define UMTILES_SIZE 61

These can be very useful when programing your game. The arrays are, pretty much, ready to be used, since they are already declared with PROGMEM.

Testing The Results

Urbanmessfinal.png

This is the main function from a simple c file I wrote that draws all three maps and a brick wall (in video mode 3):

#include "data/urbanmess.inc"

int main(){
	SetTileTable(uMTiles);
	//Draw the title
	DrawMap2(10,1,map_title);
	//Make a brick wall
	Fill(0,10,30,18,1);
	//Draw some windows
	for (unsigned int x = 2; x < 27; x += 3){
		for (unsigned int y = 12; y < 22; y += 3){
			DrawMap2(x,y,map_window);
		}
	}
	//And the graffiti
	DrawMap2(15,23,map_graffiti);
	while(1);
}

You can download the hex, the c file and everything else here. Just don't forget to adjust the paths on the Makefile.

Makefile integration

By invoking gconvert in your makefile, the conversion process can happen automatically whenever changes happen to your graphics or xml transform file. To do this simply modify your Makefile in the following way. Note that in order to work, all paths in the gconvert.xml file must be relative to the /default directory.

...

## Objects explicitly added by the user
LINKONLYOBJECTS = 

## Include Directories
INCLUDES = -I"$(KERNEL_DIR)" 

## Build
all: ../data/urbanmess.inc $(TARGET) $(GAME).hex $(GAME).eep $(GAME).lss $(GAME).uze size 

## Regenerate the graphics include file
../data/urbanmess.inc: ../data/urbanmess.png ../data/urbanmess.gconvert.xml
	gconvert ../data/urbanmess.gconvert.xml

## Compile Kernel files
uzeboxVideoEngineCore.o: $(KERNEL_DIR)/uzeboxVideoEngineCore.s
	$(CC) $(INCLUDES) $(ASMFLAGS) -c  $<

...

Mega (Meta) Tile Compression

When implementing a game that relies on big scrolling levels, it becomes necessary to perform level compression to ensure that you maximize the use of your available flash storage. Let's say we draw a 255 x 25 tile level in gimp:

Gconvert1.png

Next we define a gconvert schema to generate a level map:

<map var-name="map_level" left="0" top="0" width="255" height="25"/>

After conversion, you will end up with a map that requires 6375 bytes of flash! Clearly that doesn't scale for a scrolling game with multiple levels. To combat this excessive flash usage, gconvert supports the 'mega-map' tag. The 'mega-map' tag is an optional parent for the 'map' tag. Changing our gconvert schema to use a mega map looks like this:

<mega-map var-name="map_mega_tiles" mega-tile-width="5" mega-tile-height="5">
    <map var-name="map_level" left="0" top="0" width="255" height="25"/>
</mega-map>

After converting the same level image, we end up requiring only 1130 bytes of flash! That's about an 80% saving. What happens is that instead of 1 map, 2 maps are generated by gconvert. The 'map_level' map of our example is now a 51 x 5 size array with the contents being an indirect index into the 'map_mega_tiles' map. The 'map_mega_tiles' map contains a de-duplicated series of 5 x 5 tile indexes. These indexes point into the tile map. Below is the result of the conversion using our mega-map example:

Note: The indirect indexes in 'map_level' work like follows. Value 0 represents the first 25 bytes in 'map_mega_tiles', value 2 the second 25 etc.

#define MAP_LEVEL_WIDTH 51
#define MAP_LEVEL_HEIGHT 5
const char map_level[] PROGMEM ={
51,5
,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x1,0x0,0x0,0x1,0x0,0x0,0x0,0x0
,0x1,0x0,0x0,0x0,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0
,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x3,0x3,0x4,0x3,0x3,0x3,0x3,0x4,0x3
,0x3,0x3,0x4,0x3,0x4,0x3,0x3,0x3,0x4,0x3,0x3,0x3,0x3,0x4,0x3,0x5,0x5,0x5,0x5,0x5
,0x5,0x5,0x5,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3,0x3,0x3,0x4,0x3,0x3,0x3,0x3,0x3
,0x4,0x3,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6
,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x7,0x8,0x9
,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0xa,0x6,0x6,0x6,0x6
,0x6,0xb,0xc,0xd,0xe,0x6,0x6,0x6,0xa,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0x6,0xf
,0x6,0x10,0x6,0x10,0x6,0x11,0x12,0x13,0x6,0x6,0x6,0x14,0x6,0x6,0xa,0x6,0x6,0x6,0x6,0x6
,0x6,0x6,0x6,0x6,0x15,0x15,0x16,0x15,0x15,0x15,0x15,0x17,0x2,0x2,0x2,0x2,0x18,0x15,0x15,0x16
,0x19,0x1a,0x1b,0x1a,0x1b,0x1a,0x1c,0x1d,0x1e,0x1e,0x1f,0x20,0x21,0x20,0x21,0x20,0x22,0x15,0x15,0x15
,0x15,0x15,0x15,0x15,0x15,0x16,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15};

#define MAP_MEGA_TILES_MEGA_TILE_WIDTH 5
#define MAP_MEGA_TILES_MEGA_TILE_HEIGHT 5
#define MAP_MEGA_TILES_MEGA_TILE_COUNT 35
const char map_mega_tiles[] PROGMEM ={
0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,
0x60,0x60,0x60,0x60,0x60,0x61,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,
0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,
0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x62,0x62,0x62,0x62,0x63,0x63,0x63,0x63,0x63,
0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x61,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x62,0x62,0x62,0x62,0x63,0x63,0x63,0x63,0x63,
0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x11,0x11,0x11,0x11,0x11,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x63,0x63,0x4,0x11,0x11,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x2,0x2,0x11,0x11,0x11,0x11,0x11,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x63,0x63,0x63,0x11,0x4,0x63,0x63,0x63,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x64,0x63,0x63,0x63,0x63,0x65,0x66,0x67,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x2,0x63,0x4,0x4,0x4,0x4,0x2,0x4,0x4,0x4,0x4,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x63,0x63,0x2,0x2,0x4,0x2,0x2,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x2,0x2,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x63,0x63,0x2,0x2,0x4,0x2,0x63,0x4,0x4,0x4,0x4,0x2,0x4,0x4,0x4,0x4,0x4,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x63,0x63,0x63,0x2,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x63,0x63,0x63,0x63,0x4,0x63,0x63,0x63,0x63,0x63,0x2,0x63,0x63,0x63,0x63,
0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x2,0x2,0x11,0x11,0x11,0x11,0x11,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,
0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x2,0x63,0x11,0x11,0x11,0x4,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,
0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x2,0x2,0x4,0x11,0x11,0x11,0x4,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x2,0x2,
0x67,0x66,0x68,0x63,0x63,0x63,0x63,0x68,0x67,0x66,0x66,0x67,0x68,0x63,0x63,0x63,0x63,0x69,0x63,0x63,0x2,0x2,0x2,0x2,0x2,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x63,0x63,0x63,0x4,0x4,0x2,0x2,0x2,0x4,0x4,0x4,0x4,0x4,0x4,0x4,
0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x63,0x63,0x4,0x4,0x4,0x2,0x63,0x4,0x4,0x4,0x4,0x2,0x4,0x4,0x4,0x4,0x4,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x2,0x2,0x2,0x2,0x4,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x18,0x18,0x18,0x18,0x18,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x63,0x63,0x63,0x2,0x4,0x2,0x2,0x2,0x4,
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x2,0x63,0x63,0x63,0x63,0x4,0x2,0x2,0x2,0x2,
0x63,0x63,0x63,0x63,0x2,0x63,0x63,0x63,0x2,0x4,0x63,0x63,0x2,0x4,0x4,0x63,0x2,0x4,0x4,0x4,0x2,0x4,0x4,0x4,0x4,
0x2,0x2,0x2,0x2,0x2,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,
0x2,0x2,0x2,0x2,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,
0x18,0x18,0x18,0x18,0x18,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,
0x4,0x2,0x2,0x2,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,0x4,
0x4,0x63,0x63,0x63,0x63,0x4,0x2,0x63,0x63,0x63,0x4,0x4,0x2,0x63,0x63,0x4,0x4,0x4,0x2,0x63,0x4,0x4,0x4,0x4,0x2};

The effectiveness of this compression is only as good as the level design. The level design needs to ensure it uses repeating blocks of mega tiles. Below is the same level image used above, but with a 5 x 5 grid marked on it:

Gconvert2.png

There are 35 unique 5 x 5 mega tiles comprising this level and results in the savings mentioned above. The dimensions of the mega tiles should be informed by the level design and the level design needs to make sure that repeating blocks of imagery align with the mega tile boundaries.

Note: The mega tile dimensions need to be divisible into the level dimensions otherwise gconvert will output an error.