# 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,