# Bevy Tilemap Renderer
## Motivation
Bevy currently lacks first-party support for tilemap rendering, leaving developers to rely on third-party crates or implement their own systems. This fragmentation leads to inconsistent APIs and duplicate effort. First-party tilemap rendering will enable developers to build tile-based games and 3rd party integrations more easily and efficiently.
## Goals
* Support a wide variety of use cases
* Rectangular, isometric, hexagonal tile shapes
* Finite and infinite tilemaps
* Atlas, array texture, and file-per-tile assets for tilesets
* Clean separation of tilemap rendering and higher-level APIs
* Focus on performance
* Enable existing tilemap crates to swap out their own rendering implementations
* Build on top of Bevy's high-level rendering as much as possible (Mesh2d/Material2d)
## Features
### Asset to Tileset Pipeline
* Provide a first-party asset type for tilesets
* Load tileset images as array texture (1 layer per tile variant)
* Define metadata for tile dimensions and layout
* Support slicing packed texture atlases into the array texture
* Support combining separate tile images into the array texture
### Fast Chunk-Based Rendering
* Divide the map into fixed-size chunks (e.g. 32x32 tiles)
* A chunk is a Mesh2d with a custom chunk shader
* The shader is responsible for rendering tiles based on tile data and cached mesh data
### Tile Data
* Tileset texture index (tile variant)
* Color
* Visible
* Flip X/Y/Diagonal
* Rotate?
### Tile Storage
* Tile data stored in a single source of truth
* Support both sparse and dense storage?
* Modifying tile storage should only update relevant chunks
### Infinite/finite Map
* Allow dynamic growth in all directions (IVec2 tile positions, not UVec2)
* Support lazy loading of chunks?
* Allow setting map bounds to constrain and enable whole-map transform anchoring
## Proposed API
* Basic infinite map with sparse storage
```rust=
let chunk_size = UVec2::splat(32);
let mut tile_storage = TileStorage::sparse(chunk_size);
// Sets tiledata at (3200,3200) and marks chunk position 100,100 as dirty.
tile_storage.set(
IVec2::splat(3200),
TileData {
tileset_index: 0,
color: Color::WHITE,
visible: true,
flip: TileFlip {
x: true,
y: false,
d: false
}
}
);
commands.spawn((
Tilemap::default(),
Tileset(assets.load("atlas-tileset.tileset.ron")),
tile_storage
));
```
* Basic finite map with dense storage
```rust=
let map_size = UVec2::new(128, 32);
let chunk_size = UVec2::splat(32);
let mut tile_storage = TileStorage::new_dense_fill(map_size, chunk_size, TileData::from_index(0));
commands.spawn((
Tilemap {
// Override the tile size determined by the tileset
tile_display_size: UVec2::splat(32)
},
Tileset(assets.load("atlas-tileset.tileset.ron")),
tile_storage,
Anchor::BOTTOM_CENTER
));
```
## Out of Scope
* Higher-level ECS API (Entity-per-tile, Tile/Tilemap relation, traversal, etc.)
* Advanced features like animated tiles, auto-tiling,