Using MAME's tilemap system

From MAMEDEV Wiki
Revision as of 13:47, 23 December 2007 by Cuavas (talk | contribs) (→‎Scrolling)

Many arcade systems have features for displaying tilemaps. Unlike a bitmap or pixelmap, which specifies an image in terms of a grid of pixels, a tilemap specifies an image in terms of a grid of tiles. Why use tilemaps? First of all, with all the tiles drawn in advance, the game can be simpler because it just needs to say where to place the tiles, rather than drawing each pixel. Second, you can save storage space by reusing tiles.

Some games use fixed, ROM-based tilemaps, where the arrangement of tiles is stored in ROM. One example of this is background in some scrolling vertical shooters: the graphics ROM contains reusable terrain tiles, and the tilemap ROM contains the tile codes and attributes. Some games use dynamic, RAM-based tilemaps, where the game stores the tile codes and attributes in RAM.

This page gives a general overview of how you can use MAME's tilemap system. For more detailed information, look at the comments in src/emu/tilemap.h. For more examples, look through src/mame/video for games with tilemaps.

Core Concepts

A tilemap is a 2D grid of rectangular tiles. Each tile has its own characteristics that determine how it is rendered. When creating a tilemap, you specify the number of rows and columns, and the size of the tiles in pixels.

A tile is a single rectangular element within a tilemap. Tiles may be any size, but all tiles in a tilemap must be the same size as each other.

A 4-bit category can be specified for each tile. Each category can be rendered independently.

Depending on the pen, each pixel will be assigned to a layer. There are three layers, but in general, only layer 0 is used. If a pixel doesn't belong to a layer, it will be considered transparent.

Creating your Tilemaps

In MAME, a tilemaps are created by calling tilemap_create. You usually create tilemaps when initialising the video hardware emulation. Before you can create a tilemap, you need to answer two questions: how big are the tiles, and how big is the tilemap? The answer to the first question is usually obvious: it will be the size of the graphics elements used by the hardware. The answer to the second question may seem obvious, too: wouldn't you just make it as big as what the hardware deals with? Usually yes, but some games use absolutely massive ROM-based tilemaps, and making MAME render the whole thing just wastes a lot of memory. In these cases, it's better to your tilemap smaller than what the hardware sees, and invalidate it when the game scrolls out of range.

tilemap *tilemap_create(tile_get_info, mapper, type, tilewidth, tileheight, cols, rows)

Pass a pointer to your tile info callback for tile_get_info (this is discussed later).

Pass a pointer to a function that converts a row and column position to a tile index as mapper. You can write your own if you need to, but usually you can just use one of the predefined tilemap mappers:

  • tilemap_scan_rows scans rows from left to right consecutively from top to bottom (so in a 2x2 tilemap, 0 is top left, 1 is top right, 2 is bottom left and 3 is bottom right)
  • tilemap_scan_rows_flip_x scans rows from right to left consecutively from top to bottom (so in a 2x2 tilemap, 0 is top right, 1 is top left, 2 is bottom right and 3 is bottom left)
  • tilemap_scan_rows_flip_y scans rows from left to right consecutively from bottom to top (so in a 2x2 tilemap, 0 is bottom left, 1 is bottom right, 2 is top left and 3 is top right)
  • tilemap_scan_rows_flip_xy scans rows from right to left consecutively from bottom to top (so in a 2x2 tilemap, 0 is bottom right, 1 is bottom left, 2 is top right and 3 is top left)
  • tilemap_scan_cols scans columns from top to bottom consecutively from left to right (so in a 2x2 tilemap, 0 is top left, 1 is bottom left, 2 is top right and 3 is bottom right)
  • tilemap_scan_cols_flip_x scans columns from top to bottom consecutively from right to left (so in a 2x2 tilemap, 0 is top right, 1 is bottom right, 2 is top left and 3 is bottom left)
  • tilemap_scan_cols_flip_y scans columns from bottom to top consecutively from left to right (so in a 2x2 tilemap, 0 is bottom left, 1 is top left, 2 is bottom right and 3 is top right)
  • tilemap_scan_cols_flip_xy scans columns from bottom to top consecutively from right to left (so in a 2x2 tilemap, 0 is bottom right, 1 is top right, 2 is bottom left and 3 is top left)

type controls how pixels are assigned to layers. Currently, the only recommended value TILEMAP_TYPE_PEN, which means the raw pen value is looked up in a table to determine which layer a pixel belongs to.

tilewidth and tileheight are the width and height of the tiles in pixels.

cols and rows are the number of rows and columns in the tilemap, respectively. The size of the tilemap in pixels can be found by multiplying the number of columns by the tile width and the number of rows by the tile height.

Tilemaps are tracked by the MAME core, so they are automatically saved and freed. There is no corresponding call to manually dispose of a tilemap.

The Tile Info Callback

When MAME wants to know what to draw at a particular location in your tilemap, it calls the tile info callback function that you supplied when you created the tilemap. This function takes the tile index as a parameter, and sets the tile information. You can declare tile info callbacks with the TILE_GET_INFO macro. The two most important parameters are tile_index (the tile index), and param (a user-specified value set with tilemap_set_user_data).

You usually set the tile information with the SET_TILE_INFO macro:

SET_TILE_INFO(gfx, code, color, flags)

gfx is the graphics bank to take the tile from, code is the graphics code and color is the color code. You can combine the following values for the flags:

  • TILE_FLIPX to flip the tile horizontally
  • TILE_FLIPY to flip the tile vertically
  • TILE_4BPP if the tile data is packed four bits per pixel format
  • TILE_FORCE_LAYER0, TILE_FORCE_LAYER1 or TILE_FORCE_LAYER2 to force all pixels in the tile to be assigned to a particular layer (TILE_FORCE_LAYER0 is often used to force a tile to be opaque)

Setting Up Transparency

Sorry, haven't written this yet.

Manipulating Tilemaps

MAME can do a few tricks with tilemaps with no work on your part. The most common tilemap manipulation functions are detailed here.

tilemap_set_user_data(tmap, user_data)

Sets the value passed to a tilemap's tile info callback as param. tmap is the tilemap pointer and user_data is the value (void pointer).

tilemap_set_palette_offset(tmap, offset)

Apply a palette offset to the color values for a tilemap, to be added to each pixel value before looking up the palette.. tmap is the tilemap pointer and offset is the palette offset.

tilemap_set_enable(tmap, enable)

Enable or disable drawing of a tilemap. When a tilemap is disabled, requests to draw it are ignored. tmap is the tilemap pointer; pass non-zero for enable to enable the tilemap, or zero to disable it.

tilemap_set_flip(tmap, attributes)

Set flipping for a tilemap, or for all tilemaps. tmap is the tilemap pointer, or ALL_TILEMAPS to apply the transform to all tilemaps; attributes should be a zero, TILEMAP_FLIPX, TILEMAP_FLIPY or the logical sum of these values.

Scrolling

Although some games use tilemaps that are the same size as the display area (and therefore fit on the screen neatly), many use tilemaps that are much larger than the screen and scroll them around as necessary. Lots of games can scroll parts of a tilemap independently, too. MAME caters for most of the common cases very elegantly.

One thing to note before going further is that MAME considers tilemaps to be circular: if you scroll it so the left edge is off the left edge of the screen, MAME will wrap the right edge of the tilemap around to fill in the space and vice versa (and likewise vertically). If that isn't the behaviour you want (for example if you want the space to be all transparent), you will need to tell MAME that the tilemap is bigger than it really is when you create it, and write a custom mapper function to make life easier when writing your tile info callback.

Let's start with the simple functions that scroll the whole tilemap. These functions may be called in the VIDEO_START handler to set up global scroll offsets, or they may be called in write handlers for control registers that scroll the whole tilemap:

tilemap_set_scrolldx(tmap, dx, dx_if_flipped)

This function scrolls the entire tilemap horizontally, where tmap is the pointer to the tilemap, dx is the amount to scroll by when the tilemap is not flipped horizontally, and dx_if_flipped is the amount to scroll by if the tilemap is flipped horizontally (both in pixels).

Positive dx moves the tilemap to the right and negative dx moves the tilemap to the left. Since dx_if_flipped applies when the tilemap is flipped horizontally, its behaviour is the inverse: positive values move the tilemap to the left and negative values move the tilemap to the right. Note that values passed to this function are absolute, in that they are not relative to the current horizontal scroll offset of the tilemap.

tilemap_set_scrolldy(tmap, dy, dy_if_flipped)

This is the equivalent function for scrolling the entire tilemap vertically: tmap is the pointer to the tilemap, dy is the amount to scroll by when the tilemap is not flipped vertically, and dy_if_flipped is the amount to scroll by if the tilemap is flipped vertically (both in pixels).

Positive dy moves the tilemap down and negative dy moves the tilemap up. Since dy_if_flipped applies when the tilemap is flipped vertically, its behaviour is the inverse: positive values move the tilemap up and negative values move the tilemap down. Note that values passed to this function are absolute, in that they are not relative to the current vertical scroll offset of the tilemap.

You can also get the current overall scroll offsets for a tilemap:

tilemap_get_scrolldx(tmap)
tilemap_get_scrolldy(tmap)

These functions return the horizontal and vertical scroll offsets for the tilemap passed in tmap, respectively. The values returned are appropriate for the current flip state of the tilemap (you can't easily retrieve the values for the opposite flip states).

That's all very well and good if you only want to scroll the whole tilemap at once, which may be all the hardware can do. But that isn't the always the case: often a machine is capable of independently scrolling parts of the tilemap. This is often called row/column scroll. Before you can scroll parts of a tilemap, you need to tell MAME how many independently scrollable rows and/or columns the hardware supports:

tilemap_set_scroll_rows(tmap, scroll_rows)
tilemap_set_scroll_cols(tmap, scroll_cols)

These functions set the number of independently scrollable rows and columns, respectively, where tmap is a pointer to the tilemap, scroll_rows is the number of independently scrollable rows and scroll_cols is the number of independently scrollable columns.

These functions are usually called in the VIDEO_START handler, as this is generally a fixed hardware capability. If you don't call these functions, MAME will assume the tilemap consists of one row and one column: that is, you can only scroll the whole lot at once.

How does MAME know how tall each row is and how wide each column is? It just divides the height and width by the number of rows and columns, respectively. If your hardware has uneven or unusual size columns, you'll need to do something fancier. MAME does a very good job for the common cases.

Now that you've told MAME how many independent rows and columns there are, you can scroll parts of the tilemap. These functions are usually called from write handlers for the tilemap chip's control registers:

tilemap_set_scrollx(tmap, row, value)

Scrolls one row of the tilemap horizontally, where tmap is the tilemap pointer, row is the row number (0 for top row up to scroll_rows - 1 for bottom row) and value is the amount to scroll by (in pixels). Positive value moves the row to the right, and negative value moves the row to the left. Note that value is absolute (not relative to the current scroll value for the row), and things are reversed if the tilemap is flipped.

tilemap_set_scrolly(tmap, col, value)

Scrolls one column of the tilemap vertically, where tmap is the tilemap pointer, col is the column number (0 for leftmost column up to scroll_cols - 1 for rightmost column) and value is the amount to scroll by (in pixels). Positive value moves the column down, and negative value moves the column up. Note that value is absolute (not relative to the current scroll value for the column), and things are reversed if the tilemap is flipped.

When Things Change

MAME caches tilemaps aggressively to avoid unnecessary CPU load, so you need to let it know when parts of a tilemap need to be redrawn. Examples of when this may happen are when the game writes to tilemap memory for a RAM-based tilemap, or when the tilemap scrolls out of range when you're only rendering part of a large ROM-based tilemap. There are two functions that can be used: tilemap_mark_tile_dirty marks a single tile as needing to be redrawn, and tilemap_mark_all_tiles_dirty marks entire tilemaps as needing to be redrawn.

tilemap_mark_tile_dirty(tmap, memory_index)

Marks a single tile as needing to be redrawn. tmap is the tilemap pointer and memory_index is the index of the tile to be redrawn.

tilemap_mark_all_tiles_dirty(tmap)

Marks entire tilemaps as needing to be redrawn. tmap the tilemap pointer, or ALL_TILEMAPS to mark all tilemaps as needing to be redrawn.

Drawing Tilemaps

You can draw a tilemap into a bitmap any time you want. In general, though, you do this in your VIDEO_UPDATE callback, to get graphics onto the emulated display. The most common function used for this is tilemap_draw:

tilemap_draw(dest, cliprect, tmap, flags, priority)

dest is the destination bitmap. Usually, you pass the bitmap argument you received in your VIDEO_UPDATE callback.

cliprect is the clipping rectangle. Drawing will be restricted to this area. You usually pass the cliprect argument that you received in your VIDEO_UPDATE callback.

tmap is the tilemap pointer.

flags is a combination of the following values:

  • TILEMAP_DRAW_CATEGORY( x ) specifies the category of tiles to draw (default 0)
  • TILEMAP_DRAW_LAYER0 to draw only layer 0
  • TILEMAP_DRAW_LAYER1 to draw only layer 1
  • TILEMAP_DRAW_LAYER2 to draw only layer 2
  • TILEMAP_DRAW_OPAQUE to draw everything, even if it's normally transparent
  • TILEMAP_DRAW_ALPHA to draw with alpha blending

priority is the priority value to store in the priority bitmap for pixels that are not transparent.

Advanced Tilemap Drawing

Haven't thought about this section yet, either. It should cover priority masks, ROZ, etc.

Examples

Will get to this when time permits.