Imported from jz315/SkyEngine (
src/render/features/tilemap/AGENTS.md). Install upstream withnpx skills add jz315/SkyEngine --skill tilemap. Copyright stays with the author.
AGENTS.md — src/render/features/tilemap
Overview
- This module owns SkyEngine's low-level chunked 2D tilemap renderer and the render-only Tiled import bridge.
- Keep tilemap-specific logic here. Do not add Tiled concepts to generic render runtime, phase sorting, sprite, mesh, view, or execution modules unless the concept is truly shared.
- Game/editor tile scene semantics live in
src/tile/, not here. The normal public path isTiles -> Map -> typed layers; Tiled import/export is reached through explicitopen_tiled/save_as_tiledmethods. - The high-level public path is:
TilemapFeature— registers tilemap extraction and drawing.TilemapStorage/TilemapHandle— owns large map data outside ECS component storage.TilemapRenderer— ECS-facing component incomponent.rs.TiledImport— parses.tmx,.tmj, and.jsoninto engine tilemap data.TiledMapInstance— render-only fast path that creates entities/resources for one imported Tiled map.
- Rendering is transparent-phase, texture-atlas based, and uses instanced quads.
File Map
mod.rs— module exports and tilemap internal wiring.storage.rs—Tile,TileId,TileFlags,Tilemap,TilemapStorage, handles, chunks, and dirty versions.tiled/mod.rs— Tiled facade and public imported data types.tiled/import.rs— file and asset loading entry points.tiled/parse.rs— format parsing and map instantiation.tiled/data.rs— CSV/base64/compressed tile GID decoding shared by JSON and TMX import.tiled/error.rs—TiledImportErrorand error formatting/source wiring.tiled/json.rs— raw Tiled JSON/TMJ schema types and serde defaults.tiled/layer.rs— raw tile layer cells, GID flag handling, tileset splitting, bounds, and cell-to-tile conversion.tiled/object.rs— raw Tiled object parsing, shape decoding, tile-object GID handling, and public object conversion.tiled/properties.rs— Tiled property collection and typed value/color/file conversion for JSON and TMX.tiled/tileset.rs— embedded/external tileset resolution for TMX/TSX/TSJ, image collection handling, tile animations, and tile rect metadata.tiled/tmx.rs— TMX group/layer/object traversal, inherited layer context, and TMX tile layer cell extraction.tiled/types.rs— public imported Tiled data types such asTiledLayer,TiledObject,TiledProperty, andTiledTileset.tiled/util.rs— shared XML attribute parsing and relative path resolution helpers.instance.rs—TiledMapInstance, spawn/despawn lifecycle, tile object spawning, parallax synchronization.feature.rs—TilemapFeatureregistration.extract.rs— ECS extraction, visibility culling, tile-to-instance conversion, sort order, frame-cache population.cache.rs— per-frame and retained GPU instance buffers for chunks/batches.draw.rs— tilemap draw function, WGSL pipeline, bind groups, phase payloads.component.rs— ECS-facing tilemap authoring types such asTilemapRendererandTilesetGrid.../../shaders/tilemap/tilemap_draw.wgsl— tilemap draw shader.
Architecture
Render-only Tiled path
Tiled file
-> TiledImport
-> Tilemap + TiledLayer/TiledObjectLayer metadata
-> TiledMapInstance::spawn(...)
-> TilemapStorage resource + TilemapRenderer/SpriteRenderer entities
-> ExtractTilemaps
-> TilemapFrameCache
-> DrawTilemap
Game/editor tile scene path
Tiled file or custom authoring data
-> sky_engine::tile::Tiles
-> Map facade over World-owned tile runtime state
-> derived render/physics/navigation caches
Data Model
TilemapStorageis aWorldresource. Large tile data belongs there, not inside ECS components.TilemapHandleis a generation-checked handle intoTilemapStorage.Tilemapis layered and chunked. Chunks track:- dirty
version - non-empty tile count
- dirty
TilemapRendererselects one map layer and describes how to draw it:- tileset texture/grid
- logical tile size
- draw size and tile offset
- orientation, stagger/hex settings, render order, depth sort
- visibility, color, layer mask
- Prefer one
TilemapRendererentity per visual tile layer.
Tiled Import
TiledImportis parsing/conversion only. It should not mutate aWorld.TiledMapInstanceowns runtime spawning:- loads tileset texture into
AssetServer - inserts map data into
TilemapStorage - spawns tile layer entities
- spawns tile objects as sprites
- records entities/resources for despawn
- syncs Tiled parallax when the app provides camera position
- loads tileset texture into
TiledMapInstanceis not a scene graph. It is a loaded-map handle.- Keep
TiledSpawnOptionssmall and focused on spawn policy. - For editable or persistent maps, prefer
sky_engine::tile::Tiles::open_tiledorTiles::createinstead of this render-only instance path.
Rendering And Batching
- Ordinary tilemap layers are extracted into per-view/per-layer GPU instance batches.
TilemapDepthSort::YThenLayerkeeps finer-grained draw payloads to preserve overhanging wall/foreground correctness.- Do not sacrifice Tiled visual correctness for larger batches. Sorting correctness comes first.
- Batch keys should stay compatible with the existing transparent phase grouping.
- Texture changes are a batch boundary. Mixed tileset textures in one draw batch should be rejected or split.
- GPU caches live in
cache.rs; extraction should populate them, draw should only consume prepared payloads.
Coordinate And Sorting Rules
- Tiled uses top-left-ish map data conventions; SkyEngine rendering uses the engine's world-space conventions.
- Import code is responsible for converting Tiled coordinates into engine tile coordinates.
TilemapRenderer::cell_to_local_originandcell_to_local_centerare the source of truth for tile placement.tile_sizeis logical cell stride.tile_draw_sizeis the rendered image size.tile_offsetis the image offset relative to the logical cell.- Be careful with isometric/staggered/hexagonal parity. Tests cover official Tiled samples; extend them when fixing layout bugs.
Boundaries
- Keep generic render-phase and draw contexts generic. Do not add tilemap-specific fields to shared execution state.
- Do not push parallax into global camera/view logic. Tiled parallax currently belongs to
TiledMapInstance::sync_parallax. - Do not make sprites understand tilemaps. Tile object rendering may use
SpriteRenderer, but the conversion lives here. - Do not turn
GpuSceneinto a tilemap cache. Tilemap-specific GPU state belongs inTilemapFrameCache. - Do not use
TiledMapInstanceas a general app scene system. - Do not introduce
TiledAdapterorTmjExporter; current tile-scene format facades areTiledImporterandTiledExporterundersrc/tile/io/tiled.
Public API Expectations
- Render-only app code can load a Tiled map directly with:
let map = TiledMapInstance::spawn(world, path, TiledSpawnOptions::centered())?;
- Game/editor app code that needs persistence, editing, palettes, object metadata, or save support should use the tile scene layer:
let mut tiles = sky_engine::tile::Tiles::new(world);
let mut map = tiles.open_tiled(path)?;
map.tiles("Ground")?.set([12, 4], grass)?;
map.save()?;
- Keep this path simple. Demos should not manually assemble layer entities, storage handles, texture handles, and parallax metadata unless they are demonstrating low-level APIs.
- Manual tilemap construction should continue to use:
let mut storage = TilemapStorage::new();
let map = storage.create(TilemapDescriptor::new(width, height, layers));
storage.get_mut(map).unwrap().set_tile(layer, x, y, tile);
world.insert_resource(storage);
world.spawn((Transform::default(), TilemapRenderer::new(map, tileset), SortingLayer(0)));
Supported Tiled Surface
- Currently supported:
.tmx.tmj/.json- orthogonal, isometric, staggered, and hexagonal orientations
- finite layers and normalized infinite-map chunks
- group layer inheritance for visibility/opacity/offset/parallax
- CSV and base64 tile data
- zlib and gzip compression
- external
.tsx/.tsjtilesets - single-image tilesets
- limited single-image image collection tilesets
- tile offset, margin, spacing, transparent color, animation
- tile flip flags in tile layers
- TMX tile objects as sprites
- Known limits:
- multiple used tilesets in tile layers are rejected
- image collection tilesets using multiple images are rejected
- non-tile object shapes/text/polygons are not a complete rendering path
- JSON object layers are not as complete as TMX object groups
- infinite maps are imported into a bounded tilemap rather than streamed
Tests And Validation
- Tilemap-focused tests:
cargo test --features app features::tilemap
- Import-only tests:
cargo test --features app features::tilemap::tiled
- Instance lifecycle tests:
cargo test --features app features::tilemap::instance
- Example compatibility after public API or render pipeline changes:
cargo check --examples --features app
- Useful demo runs:
cargo run --example tiled_browser_demo --features app
cargo run --example tiled_browser_demo --features app -- examples/assets/tiled/tiled/examples/forest/forest.tmx
cargo run --example tiled_import_demo --features app -- examples/assets/tiled/sewers.tmx
Review Checklist
- Does the change stay within tilemap-specific modules unless a generic concept is truly needed?
- Are Tiled coordinate conversions tested with official sample maps?
- Does
perspective_walls.tmxor any overhanging wall map still sort correctly? - Does ordinary layer batching still preserve visible cell order?
- Are animated tiles invalidating the GPU cache when their frame changes?
- Are despawn paths removing spawned entities, storage handles, and runtime textures?
- Did
cargo fmt --check, relevant tilemap tests, and example checks pass?