Uzebox Tutorials And Such 2 - Basics Review

From Uzebox Wiki
Jump to: navigation, search

Flash Tiles

  • Uzebox is a tile-based system (unless it is not.) Most of the video modes are tilebased.
  • There are also modes that do vector graphics and code tiles.
  • A tile is a graphic, usually 8x8 pixels (64 bytes). Using tiles instead of actual direct pixel graphics saves lots of flash space. However, tiles do remain a large part of the flash cost of your game.

Ram Tiles

  • There is a type of tile called a Ram Tile. These operate similar to regular flash tiles in vram. However, dynamic content can be put into them. Their intended usage is for sprite blitting.
  • Ram tiles primary purpose is for sprite blitting.
  • Ram tiles are a sort of wild card. It can be a tile. You can copy a tile into it and even modify the pixels in that tile in code.
  • They are also useful as general purpose temporary buffers.

Sprites

A hardware sprite can move independently of the background. Uzebox uses Software Sprites and a technique called "blitting". Basically, blitting is a super-imposing of the sprite on top of all overlapped background tiles. A dynamic replacement tile is generated for each overlapped sprite. The replacement tile has the overlapped background tile and the sprite combined where the background tile will appear through the sprite's transparent regions. This new tile is created within a ram tile and used in place of the tiles that would have been in vram before. Sprites REQUIRE ram tiles. If there are not enough ram tiles to blit a sprite then that sprite will not be visible.

Sprites and Ram Tile Cost

  • For each tile that a sprite overlaps it will cost a ram tile.
  • It is important to try to keep sprites aligned at least on one axis as to limit the usage of ram tiles. More ram tiles available means more sprites can be on screen without being clipped or flickering.
  • Aligned means "not overlapping an 8 pixel boundary."
  • If a sprite tile overlaps into the next tile even a little then an additional ram tile will be required. You only have so many ram tiles.
  • Many games have 26-34 and each of these ram tiles has a cost of 64 bytes of ram.
  • Ram tile costs for a 2x2 sprite:
    • 0 overlapped axis: 4 ram tiles.
    • 1 overlapped axis: 6 ram tiles.
    • 2 overlapped axis: 9 ram tiles.
  • Example for a 4x4 sprite:
    • 0 overlapped axis: 16 ram tiles. (just to have it on screen!)
    • 1 overlapped axis: 20 ram tiles.
    • 2 overlapped axis: 25 ram tiles.
  • Example for a 1x1 sprite:
    • 0 overlapped axis: 1 ram tiles.
    • 1 overlapped axis: 2 ram tiles.
    • 2 overlapped axis: 4 ram tiles.

You will likely want to keep your sprite maps at 2x2 or 3x3. If you can do 1x1 the costs are lower but your graphics will be very simple.

It gets progressively more expensive as your sprite map gets larger.

How to Determine How Many Ram Tiles Are in Use

// Put this in your program to gain access to this variable.
extern unsigned char free_tile_index;

The kernel keeps track of what the next ram tile index will be. It will start at 0 and continue to increment the number for each ram tile required. If you can monitor the free_tile_index you can change your game logic to make your sprites move differently in high ram tile usage situations. Perhaps if two enemies are moving then the third should wait until the other two enemies have finished moving.

Debugging

Debugging is important! You may need to make sure that a variable has a specific value. The CUzeBox emulator has support for the "whisper port." It is a way of displaying up to two 8-bit values. If you want 16-bit or 32-bit values you will need to do something different.

// Simple debugging. Used with CUzeBox emulator.
static void _emu_whisper(int port, unsigned char val){
	if(port==0x39 || port == 0){ u8 volatile * const _whisper_pointer1 = (u8 *) 0x39; *_whisper_pointer1 = val; }
	if(port==0x3A || port == 1){ u8 volatile * const _whisper_pointer2 = (u8 *) 0x3A; *_whisper_pointer2 = val; }
}

Example to show you the number of ram tiles used and the total ram tiles in the program:

_emu_whisper(0, free_tile_index);
_emu_whisper(1, RAM_TILES_COUNT);

Repurposing VRAM and RAM TILES

Both ram tiles and vram use most of the ram resources on the Uzebox. However, they can be "time-shared" in a way. For example, when loading from the SD card into SPI RAM you can reduce the number of vram render lines and actually use the vram as a data buffer.

// Render only the first 8 scan lines (1 tile high.)
SetRenderingParameters(FIRST_RENDER_LINE, 8);

// ... do something...

// Render all lines again.
SetRenderingParameters(FIRST_RENDER_LINE,FRAME_LINES);

You should reduce the number of render lines for two reasons. First, anything put into vram will be considered a tile ID so the screen will appear to be filled with garbage. Second, with less lines to render you actually have more cycles avaialble to do things such as read from the SD card. With only 8 scan lines rendering (1 tile height) the load time for reading 72k from SD to SPIRAM was significantly reduced to almost nothing. The vram array has VRAM_TILES_V*VRAM_TILES_H bytes available. Example: 30 by 26 screen has 780 bytes.

You can use ram tiles as a general buffer also. But remember, ram tiles are used for sprites so you will either need to turn off sprite processing or reserve a number of ram tiles temporarily.

If you reserve ram tiles you are making the kernel not start that count at 0 but at the number you specify. A ram_tile consists of 64 bytes of ram (assuming 8x8 pixel tiles.)

// Reserve a ram tile.
SetUserRamTilesCount(1);

SEE ALSO: http://uzebox.org/wiki/Function_SetSpriteVisibility

Please note that setting the sprite visibility to false does NOT hide sprites. It just turns off the sprite blitter. I suggest not using it. Just reserve the number of ram tiles that you need and those ram tiles will not be messed with by the blitter. It would be wise to first clear the sprites (or hide them) from the screen before using ram_tiles as a temporary buffer. You can hide sprites by setting their x member property to SCREEN_TILES_H*TILE_WIDTH or OFF_SCREEN. They are both actually the same value.

// Loop through all sprites.
for(int i=0;i<MAX_SPRITES;i++){ sprites[i].x=OFF_SCREEN; }

Pointers

Learn how to use pointers!. It is totally worth it. This will help you when you want to write to specific parts of vram or ram_tiles or other things. If you want to run a list of functions in order within a for loop you can use an array of function pointers.

Graphical Assets

Look here as well: http://uzebox.org/wiki/Generating_Tiles_and_Maps_with_gconvert And here: http://uzebox.org/wiki/Hello_World

Gconvert is the standard tool for creating graphical assets on Uzebox. What it does is take an image, break it up into tiles, and create a C array that represents each tile as data. Additionally, tile maps are created. Maps define what tiles consist of a specific image (the player, enemies, etc.) With gconvert the first two bytes are the width and height and the remaining bytes are the tiles for that image. Gconvert also will remove duplicate tiles and remap duplicates to the first instance of that tile. This can help save flash space. To use gconvert you can either invoke it manually through the command line or make it part of your Makefile script. You'll need to create a .xml file to drive it and you will also need to have your source image file. Gconvert accepts either raw or .bmp images. I do not use gconvert. I ported gconvert to JavaScript and use it through a web interface. I've added a few features as well. However, I made sure that I retained compatibilty with the standard gconvert at least for the features that I use. I literally ported it to JavaScript. It operates much the same way but with HTML5 canvas. A major flaw with gconvert was the need to convert your source image to indexed RGB each time you used it. Perhaps I didn't know better but I really disliked the process. Also, if I didn't actually have enough colors in the image the color palette would be wrong. With HTML5 canvas the source image is 32-bit RGBA. I use some masking and bitshifting to convert the pixel data to the Uzebox palette.

This tutorial should work for both the standard gconvert and my gconvert JS. Understanding the concept of gconvert is more important than who's gconvert you use.

Let's get started! Sample .XML file:

<?xml version="1.0"?><gfx-xform version="1">
	<input file="bb_goodguy_sprites.png" type="" tile-width="8" tile-height="8"/>
	<output file="bb_goodguy_sprites.inc" file2="">
	<tileset tilesetoutputTo="FLASH_FLASH">
	<tiles var-name="bb_goodguy_sprites"/>

	<maps pointers-size="8">

	<map var-name="blacktile" left="2" top="8" width="2" height="2" outputTo="NOWHERE"/>
	<map var-name="p1_gnd_mov_f1" left="0" top="2" width="2" height="2" outputTo="FLASH"/>
	<map var-name="p1_gnd_mov_f2" left="2" top="2" width="2" height="2" outputTo="FLASH"/>
	<map var-name="p1_gnd_idle_f1" left="4" top="2" width="2" height="2" outputTo="FLASH"/>
	<map var-name="p1_gnd_idle_f2" left="6" top="2" width="2" height="2" outputTo="FLASH"/>
	<map var-name="p1_fal_f1" left="0" top="4" width="2" height="2" outputTo="FLASH"/>
	<map var-name="p1_fal_f2" left="2" top="4" width="2" height="2" outputTo="FLASH"/>
	<map var-name="p1_jmp_f1" left="4" top="4" width="2" height="2" outputTo="FLASH"/>
	<map var-name="p1_jmp_f2" left="6" top="4" width="2" height="2" outputTo="FLASH"/>

	</maps
	</tileset>

	</output>
</gfx-xform>

Sample Image:

Sample Gconvert Results:

#define BB_GOODGUY_SPRITES_SIZE 14
const char bb_goodguy_sprites[] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00   // DEC tile #: 0 (HEX tile #: 0x00)
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x67, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x3A, 0xFF, 0x3A, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x3A, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF   // DEC tile #: 1 (HEX tile #: 0x01)
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x67, 0x67, 0x67, 0x67, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x67, 0x67, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x67, 0x67, 0x67, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x67, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x00   // DEC tile #: 2 (HEX tile #: 0x02)
, 0x00, 0x3A, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x3A, 0x3A, 0xFF, 0x3A, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x67, 0x67, 0x67, 0x00, 0x00, 0x67, 0xFF, 0x67, 0x67, 0x67, 0x67   // DEC tile #: 3 (HEX tile #: 0x03)
, 0x3A, 0x3A, 0x3A, 0x3A, 0x67, 0x67, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x67, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x67, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x67, 0x67, 0x67, 0x3A, 0x00, 0x00, 0x00, 0x3A, 0x67, 0x67, 0x67, 0x3A, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x67, 0x67, 0x3A, 0x67, 0x00, 0x00, 0x67, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x67, 0x00, 0x67, 0x67, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A   // DEC tile #: 4 (HEX tile #: 0x04)
, 0x00, 0x3A, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x3A, 0x3A, 0xFF, 0x3A, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x67, 0x67, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x67, 0x67, 0x67, 0x67, 0x00, 0x67, 0x67, 0xFF, 0xFF, 0x67, 0x67, 0x67   // DEC tile #: 5 (HEX tile #: 0x05)
, 0x3A, 0x3A, 0x3A, 0x3A, 0x67, 0x67, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x67, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x67, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x67, 0x67, 0x67, 0x3A, 0x00, 0x00, 0x00, 0x3A, 0x67, 0x67, 0x67, 0x3A, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x67, 0x67, 0x3A, 0x67, 0x67, 0x3A, 0x67, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x67, 0x67, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00   // DEC tile #: 6 (HEX tile #: 0x06)
, 0x00, 0x3A, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x3A, 0x3A, 0xFF, 0x3A, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x67, 0x67, 0x67, 0xFF, 0xFF, 0xFF, 0x67, 0x67   // DEC tile #: 7 (HEX tile #: 0x07)
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x67, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x3A, 0xFF, 0x3A, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0xFF, 0xFF, 0x3A, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x3A, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF   // DEC tile #: 8 (HEX tile #: 0x08)
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x67, 0x67, 0x67, 0x67, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x67, 0x67, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x67, 0x67, 0x67, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x67, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x67, 0x67, 0x00, 0x00, 0x00, 0x00   // DEC tile #: 9 (HEX tile #: 0x09)
, 0x00, 0x3A, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x3A, 0x3A, 0xFF, 0x3A, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A, 0x00, 0x67, 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x67, 0x67, 0x67, 0xFF, 0xFF, 0xFF, 0x67, 0x67, 0x67, 0x00, 0x67, 0x67, 0xFF, 0xFF, 0xFF, 0x67, 0x67   // DEC tile #: 10 (HEX tile #: 0x0A)
, 0x3A, 0x67, 0x67, 0x67, 0x67, 0x67, 0x00, 0x00, 0x3A, 0x67, 0x67, 0x67, 0x67, 0x00, 0x00, 0x00, 0x3A, 0x67, 0x67, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x67, 0x67, 0x3A, 0x67, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x67, 0x67, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00   // DEC tile #: 11 (HEX tile #: 0x0B)
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x67, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x3A, 0x00, 0x3A, 0x00, 0xFF, 0x3A, 0x00, 0x00, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0x00, 0x3A, 0xFF, 0xFF, 0x3A, 0xFF, 0xFF, 0xFF   // DEC tile #: 12 (HEX tile #: 0x0C)
, 0x00, 0x3A, 0xFF, 0xFF, 0x3A, 0xFF, 0xFF, 0xFF, 0x00, 0x3A, 0x3A, 0xFF, 0x3A, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x67, 0x67, 0x67, 0x00, 0x67, 0x67, 0xFF, 0x67, 0x67, 0x67, 0x67   // DEC tile #: 13 (HEX tile #: 0x0D)
};

const char p1_gnd_mov_f1[] PROGMEM = { 2, 2
    , 0x01, 0x02
    , 0x03, 0x04
};
const char p1_gnd_mov_f2[] PROGMEM = { 2, 2
    , 0x01, 0x02
    , 0x05, 0x06
};
const char p1_gnd_idle_f1[] PROGMEM = { 2, 2
    , 0x01, 0x02
    , 0x07, 0x06
};
const char p1_gnd_idle_f2[] PROGMEM = { 2, 2
    , 0x01, 0x02
    , 0x07, 0x04
};
const char p1_fal_f1[] PROGMEM = { 2, 2
    , 0x08, 0x09
    , 0x0A, 0x0B
};
const char p1_fal_f2[] PROGMEM = { 2, 2
    , 0x08, 0x02
    , 0x0A, 0x04
};
const char p1_jmp_f1[] PROGMEM = { 2, 2
    , 0x0C, 0x09
    , 0x0D, 0x0B
};
const char p1_jmp_f2[] PROGMEM = { 2, 2
    , 0x0C, 0x02
    , 0x0D, 0x04
};

NOTE: outputTo, and file2 are not part of the gconvert standard. I will cover those at a later time.

So, the idea is to create a <map> entry for each tile map. Gconvert will break it up into tile ids, remove duplicates and you will have c arrays that you can include and call within your game. A breakdown of a map is this: var-name: The name of the tilemap c array. You will refer to this map with this name. left: How many tiles from the left of the image of the left-most tile of the tilemap. top: How many tiles from the topt of the image of the top-most tile of the tilemap. width: The width of your tilemap in tiles. height: The height of your tilemap in tiles.

So, you fill in these values. The attributes left and top should refer to the top-left-most tile in your tilemap. Tilemaps are typically rectangular or square.

Counting tiles can be fustrating. Using a grid view in your graphics editor will help! If you are still using mspaint I suggest you learn GIMP right away.

One of my tools has a feature where you can hover over the image region and the coordinates (left/top) will be shown. Double-clicking will create an entry. My tool chain, the Uzebox Asset Manager will be in another tutorial. My point is that if something is bothering you or annoying then find a new way to deal with the problem. Many of us have built custom tools. As long as it is compatible with the Uzebox kernel then go right ahead and do it your own way. :)


You draw a flash tile map with DrawMap2.

You tell the program what tileset to use with SetTileTable.

http://uzebox.org/wiki/Function_SetTileTable

API: http://uzebox.org/wiki/Function_DrawMap2

These are for flash tiles. They are ALWAYS aligned on an 8x8 axis. The x and the y for DrawMap2 is in tiles, not pixels.

Gconvert can also be used for sprites. In fact it is exactly the same. Drawing sprites is different though. They still use tilesets and tile maps but they are sprites and can appear to move independently on the screen.

Sprites are a different tutorial.

Sprites

Sprites can appear to move independently of the background by overlapping tiles. They move with pixel precision where flash tiles are always placed at positions that are multiples of the tile width and height.

Any sprite that overlaps a tile (even if exactly aligned on both x/y axis) will require one tile per overlapped tile. Even if the overlap is only 1 pixel a ram tile will be required.

The only exception to this is when sprites overlap other sprites. In this case they share the ram tile.

Alignment is very important because ram tiles can be used up quickly if you do not manage the movement of your sprites well.

You create sprites the same way that you create background tiles. You use gconvert. You use them in your program differently though.

Actually, in Uzebox, tiles are tiles. You can draw a background tile as a sprite and you can draw a sprite as a background tile. So, it is important to tell the program what tileset and sprite tileset to use.

If you only have one sprite bank to use then you can use these:

However, you likely would like to have multiple sprite banks. You should use these instead:

A huge benefit to using sprite banks is that each sprite can be set to use a different spriteset table (total of 4 are available.) There is a limit of 256 tiles that the Uzebox can address per bank. This is true of sprite tiles. However, with flash tiles ram tiles take up the first indexes automatically so you can actually have less flash tiles than 256. Sprites however allow for multiple banks to be used at the same time.

Each sprite is a struct in an array of structs within sprites[].

Here is an example of directly setting sprites:

	// Set the tilesets for each sprite bank.
	SetSpritesTileBank(0 , bb_textscreens   );      // 0 -- SPRITE_BANK0
	SetSpritesTileBank(1 , bb_goodguy_sprites   );  // 1 -- SPRITE_BANK1
	SetSpritesTileBank(2 , bb_enemy_sprites );      // 2 -- SPRITE_BANK2
	SetSpritesTileBank(3 , bb_projectile_sprites ); // 3 -- SPRITE_BANK3

	// Manual method:
	sprites[0].tileIndex=10; //the 10th tile
	sprites[0].x=100;
	sprites[0].y=50;
	sprites[0].flags = (SPRITE_BANK0 | SPRITE_FLIP_X | SPRITE_FLIP_Y)

	// Using MapSprite2:
	MapSprite2(0, p1_gnd_idle_f2, (SPRITE_BANK0 | SPRITE_FLIP_X | SPRITE_FLIP_Y);

In this example we specified the tile id of the sprite, the x and y position and the sprite flags. In the flags you can specify the sprite bank to use as well as if you want it flipped on X or flipped on Y (or both.)

With the manual method you used index 0. With MapSprite2 you also specified 0 (first argument.) Why? A single sprite takes up one of these sprite indexes. However, something like a 2x2 sprite map would take up 4 indexes. With the manual method you would need to be aware of this and configure each sprite. This is easier with MapSprite2 since it does them all for you. However, you need to keep track of what sprite index you would be on for the next sprite. The next index in this case would be 4. After you get the hang of all this you will likely find that the manual method provides you more power and you will want to create your own methods for mapping sprites. I did! They are quite similar. Do it your way if you think it is better.