Skip to main content

Tiled Sprites

Use the TilemapSprite and TileSprite functions to give your entity a grid-based sprite, where each tile of the grid can show a different image, a different color, or be empty. This is an efficient way to create the level for your grid-based game, whether it be an RPG, a platformer, or a puzzle game.

Minimal Example​

pub const Level1Data = [
[ 1, 0, 0, 0, 0 ],
[ 1, 0, 0, 0, 0 ],
[ 1, 0, 1, 0, 0 ],
[ 1, 0, 1, 1, 0 ],
[ 1, 0, 0, 0, 0 ],
[ 1, 1, 1, 1, 1 ],
]

pub game fn World.Main() {
Spawn walls {
use body=this
Body(pos=@(0,0), immovable=true)

// Create the tilemap
TilemapSprite(layer=-10)

// Fill the tilemap
for row, rowData in Level1Data {
for col, isSolid in rowData {
if !isSolid { continue }
TileSprite(coords=@(col, row), image=@brick.png)
}
}
}
}
  • The tiled sprite is first created using the TilemapSprite function. This is where you specify parameters that apply to the whole tilemap, such as the body and the layer.
  • Then the tiled sprite is populated using the TileSprite function, where col and row are the column and row indices of the tile to set.

Tile Content​

In a TilemapSprite, each tile can display an image, a color, or nothing at all:

// Create the TilemapSprite
TilemapSprite(layer=100)

// Set the content of each tile
TileSprite(coords=@(0, 1), image=null) // empty tile
TileSprite(coords=@(0, 2), image=@ice.png) // tile with an image
TileSprite(coords=@(0, 3), image=#ff0000) // tile with a color

Y-sorting​

Each tile in the TilemapSprite is Y-sorted individually. Tiles closer to the bottom of the screen will be on top of other objects on the same layer. This is useful for creating a top-down view of a room, where the player can walk behind walls and furniture.

Animation​

You can provide an array of images to TileSprite to animate each tile. The animation will loop through the images at a rate determined by the TilemapSprite(frameInterval) parameter:

TileSprite(coords=@(2, 3), image=[@tree-1.png, @tree-2.png, @tree-3.png])
TileSprite(coords=@(2, 3), image=@tree-*.png) // more performant way of doing the same thing
tip

It is faster to use a wildcard pattern like @tree-*.png rather than an array of images like [@tree-1.png, @tree-2.png, @tree-3.png]. A wildcard pattern is already pre-processed for animation at compile time and so no processing or copying needs to be done at runtime, making it more efficient.

Spilling out of the tile​

Let's say you want to make a forest of trees as a TilemapSprite. Trees are tall, so you want their image to be drawn beyond the bounds of the tile they are on, potentially occluding what is behind them. This is how you can do that:

// Create a TilemapSprite which pins the bottom of each tile image to the tile origin
TilemapSprite(imageAnchor=@(0, 1))

// Populate the TilemapSprite with trees
for row in Range(0, 10) {
for col in Range(0, 10) {
TileSprite(coords=@(col, row), image=@tree.png, imageRadius=@(0.5, 4))
}
}
  • The first thing to note is that we have not specified the tileSize, which means it remains at its default value of tileSize=1. This default value makes the tiles and world coordinates the same, making tilemaps easier to work with. This means our tile radius is 0.5 (half of tileSize=1).
  • Specifying imageRadius=@(0.5, 4) means the image bounding box is taller than the tile's radius, but not wider. This allows the tile image to spill out of the tile.
  • The imageAnchor=@(0, 1) means the bottom of the image is always anchored to the tile origin, meaning the tree will spill upwards out of the top of the tile, which is needed because trees grow upwards from the ground.

See the TilemapSprite reference for more information on these parameters.

Dense Storage​

The TilemapSprite data is stored as a dense array of tiles, meaning every tile between the minimum and maximum column/row that you have touched takes up memory, even the empty tiles.

Whenever you add tiles to a TilemapSprite, it will automatically expand its internal storage to accommodate the new tiles. The storage never shrinks, and so it is best to use a TilemapSprite where the columns/rows stays within a fixed range.

tip

If you have a game where the tilemap keeps growing more columns or rows, for example an infinite runner that constantly scrolls to the right, consider creating a new TilemapSprite at regular intervals and then deleting the old one, instead of allowing it to grow indefinitely.

Performance​

Because a TilemapSprite is a grid, the renderer quickly finds the subset of tiles that are visible on the screen and only considers those for rendering. This makes it fast, even when the tilemap is very large, as long as the number of visible tiles is small. The renderer also only needs to calculate the transformation from tile space to screen space once, as all the tiles share the same transformation, which is another performance boost.

See Also​

  • Tilemaps for a broader overview of tilemaps and how to use them.
  • Graphics to learn more about sprites in general.