TilemapRenderer

The TilemapRenderer component renders a tile-based map. It uses one or more tileset images as the source for individual tiles, arranged according to an array of tile IDs. It uses the entity’s Transform for position. See Rendering for an overview.

Each tile is referenced by an ID, where 0 represents empty space. The tile data can be provided directly, or populated from a Tiled map with the TiledWrapper component.

Options

OptionTypeDefaultDescription
tilesetsTileset[][]The tilesets that provide the tiles (see below).
datanumber[][]Array of tile IDs. 0 is empty space.
chunksChunk[][]Tile data split into chunks, for large maps.
widthnumber0Map width in tiles.
heightnumber0Map height in tiles.
tileWidthnumberRendered tile width.
tileHeightnumberRendered tile height.
layerstring"Default"The render layer.
opacitynumber1Opacity between 0 and 1.
tintColorstringColor used to tint the tiles.
maskColorstringMask color applied to the tiles.
maskColorMixnumberMask color opacity between 0 and 1.
smoothbooleanfalseSmooths pixels. Not recommended for pixel art.
offsetVector2(0, 0)X-Y axis offset from the entity position.

Tileset

FieldTypeDescription
imageHTMLImageElement | stringThe tileset image, or an asset URL/name string.
tileWidthnumberTile width in pixels.
tileHeightnumberTile height in pixels.
marginnumberSpace in pixels between the tiles and the four edges of the image. Defaults to 0.
spacingnumberSpace in pixels between adjacent tiles. Defaults to 0.
firstgidnumberThe ID of the first tile of the tileset. Defaults to 1.
tileCountnumberThe number of tiles of the tileset. Obtained from the image if it is not set.
animationsMap<number, TileAnimation>Animated tiles, keyed by the tile ID to animate (see below).

For a tileset whose tiles are extruded by 1 pixel, the image has a margin of 1 and a spacing of 2:

tilesets: [
    {
        image: this.assetManager.getImage("tileset.png"),
        tileWidth: 16,
        tileHeight: 16,
        margin: 1,
        spacing: 2,
    },
];

Multiple tilesets

Each tileset owns a range of tile IDs: it starts at its firstgid and covers as many tiles as the tileset has. A tile is drawn by the tileset whose range contains its ID, and IDs outside every range are not drawn. This is the same criterion Tiled uses, so the ranges of a map exported from Tiled match its firstgid values.

The firstgid of the first tileset defaults to 1, and tileCount is obtained from the image, dividing it by the size of a tile. A tileset of 72 tiles starting at 1 is followed by a tileset starting at 73:

tilesets: [
    { image: this.assetManager.getImage("ground.png"), tileWidth: 16, tileHeight: 16 },
    { image: this.assetManager.getImage("props.png"), tileWidth: 16, tileHeight: 16, firstgid: 73 },
];

The tiles of each tileset are rendered in a separate pass, so a map that uses several tilesets costs one draw call per tileset and chunk. When the tilemap comes from Tiled, the TiledWrapper creates the tilesets from the ones embedded in the map, so they do not need to be declared.

Tile animations

A TileAnimation cycles a tile through a sequence of tileset tile IDs. The animations map is defined in the tileset and is keyed by the tile ID that should animate: every tile with that ID plays the animation. Since the animations belong to the tileset, every tilemap using that tileset plays them in sync. Animations always loop.

OptionTypeDefaultDescription
tilesnumber[][]The sequence of tile IDs to cycle through.
fpsnumber12Frames per second.

When the tilemap comes from Tiled, the tiles animated in the map editor are mapped to this map automatically. See TiledWrapper.

Example

import { Transform, TilemapRenderer } from "angry-pixel";

this.entityManager.createEntity([
    new Transform(),
    new TilemapRenderer({
        layer: "Default",
        tilesets: [
            {
                image: this.assetManager.getImage("tileset.png"),
                tileWidth: 16,
                tileHeight: 16,
            },
        ],
        data: [1, 2, 3, 4],
        width: 2,
        height: 2,
    }),
]);

Animated tiles example

import { Transform, TilemapRenderer, TileAnimation } from "angry-pixel";

this.entityManager.createEntity([
    new Transform(),
    new TilemapRenderer({
        tilesets: [
            {
                image: this.assetManager.getImage("tileset.png"),
                tileWidth: 16,
                tileHeight: 16,
                // Every tile with ID 3 cycles through 3, 4, 5 at 6 fps.
                animations: new Map([[3, new TileAnimation({ tiles: [3, 4, 5], fps: 6 })]]),
            },
        ],
        data: [1, 2, 3, 4],
        width: 2,
        height: 2,
    }),
]);

Updating the tilemap at runtime

The tile data is processed once: the data array and the chunks array are generated from each other, and the height of the tilemap is resolved. After changing the data at runtime, call refresh so it is processed again.

refresh keeps the array the tiles were given in and empties the one generated from it, so the change has to be made on the source array: data for a tilemap defined with tiles, and chunks for a tilemap defined with chunks, which is the case of the infinite tilemaps of Tiled. Assigning data on a tilemap defined with chunks has no effect, because data is generated again from the chunks.

const tilemapRenderer = this.entityManager.getComponent(entity, TilemapRenderer);

// a tilemap defined with tiles
tilemapRenderer.data = newData;
// a tilemap defined with chunks
tilemapRenderer.chunks = newChunks;

tilemapRenderer.refresh();

This operation is expensive, do not call it on every frame. When the tilemap comes from Tiled, the TiledWrapper needs to be refreshed too, and so does the TilemapCollider if the entity has one.