pixi-tiledmap
v2.6.0
Published
Tiled Map Editor loader and renderer for PixiJS v8
Maintainers
Readme
pixi-tiledmap v2 
Load and render Tiled Map Editor maps with PixiJS v8.
v2 is a ground-up rewrite targeting PixiJS v8, with its own Tiled JSON and TMX XML parser (no external deps), full layer-type support, typed API, and ESM/CJS dual output.
Features
- PixiJS v8 — uses the modern
Assets/LoadParserextension system - Tiled JSON + TMX XML — full spec coverage (Tiled 1.11), both
.tmjand.tmxformats - All layer types — tile, image, object, and group layers
- All orientations — orthogonal, isometric, staggered, hexagonal
- Render order — right-down, right-up, left-down, left-up
- Infinite maps — chunk-based tile layer rendering
- Packed tile layers — static map tiles render as PixiJS batchable mesh geometry grouped by texture source, without an external tilemap dependency
- Tile features — animated tiles, flip/rotation flags, image-collection tilesets, tint color, tile offset,
tilerendersize/fillmode - Object rendering — rectangles, ellipses, polygons, polylines, points, text (with underline/strikeout), tile objects
- Object templates — automatic
.tx/.tjresolution with gid remapping between template and map tileset spaces - Parallax scrolling — per-layer
parallaxx/parallaxyand map-levelparallaxorigin, composed multiplicatively through group layers, applied viaTiledMap.applyParallax(cameraX, cameraY) - Data encoding — CSV (both
.tmxand.tmj) and base64 (uncompressed, gzip, zlib) - External tilesets — automatic resolution via the asset loader (
.tsjand.tsx) - Tree-shakable — ESM + CJS dual build, side-effect-free
- Typed — comprehensive TypeScript types for the full Tiled spec
Notes on Tiled-spec coverage.
zstd-compressed tile data is not supported — the browser'sDecompressionStreamAPI only exposesgzipanddeflate, and this library intentionally ships with zero runtime dependencies. Wang sets and terrains are parsed and exposed onResolvedTilesetfor introspection, but they are editor-only metadata with no runtime rendering behaviour.
Modernization Check (v2.0.0)
v2.0.0 is modernized for the current PixiJS ecosystem and modern TypeScript package distribution:
- Targets PixiJS v8 via
peerDependencies(pixi.js: >=8.7.0) - Ships ESM + CJS + TypeScript types through the
exportsmap (import+require) - Uses native LoadParser integration (
tiledMapLoader) instead of legacy global loader APIs - Declares side-effect-free package metadata (
"sideEffects": false) for tree-shaking - Uses a modern toolchain (
typescript,biome,vitest,tsdown)
Why this package stands out
- Complete Tiled coverage in one package: JSON + TMX, all layer types, all map orientations.
- PixiJS-native loading path: register once via
extensions.add(tiledMapLoader)and load maps throughAssets. - Performance-minded internals: batchable packed mesh-backed tile layers, chunked infinite-layer traversal, cached tile textures, and efficient GID→tileset resolution.
- Composable renderer pipeline: layer filtering, parallax, tile visuals, and texture loading are factored so manual and loader-based usage share the same rendering behavior.
- Production packaging: side-effect-free metadata, ESM/CJS dual output, and bundled type definitions.
Internal Model
The library keeps three concepts separate:
- Tiled map data: TMJ JSON or TMX XML in the shape Tiled writes.
- Resolved map IR: normalized data with defaults applied, external tilesets supplied, templates merged, GIDs decoded, and layer data decoded.
- PixiJS rendering: a
TiledMapcontainer built from a resolved map, texture maps, layer tree rendering, packed tile meshes, tile visuals for object/animated cases, and map geometry.
The asset loader runs the complete pipeline for .tmj and .tmx files. Manual construction gives you the same pieces directly: parse to a resolved map, load textures, then construct TiledMap.
Optimization checklist (for app integrators)
If you want the best runtime behavior in your game/application:
- Prefer
.tmjfor the fastest parse path when authoring allows it. - Preload map, tileset, and image assets with
Assetsbefore scene transitions. - Reuse
TiledMapinstances for frequently revisited scenes when possible. - Keep large worlds in infinite/chunked maps to avoid over-allocating one giant layer.
- Avoid unnecessary texture churn; pass stable texture maps into
TiledMapoptions. - Treat
TileLayerRenderer.childrenas renderer internals. Static map tiles are usuallyMeshchildren now, not oneSpriteper tile. - Run
npm run benchbefore and after renderer hot-path changes if you maintain a fork. npm testincludes a headless MagicLand visual regression that renders a real TMX + GIF tileset fixture and pixel-compares it against a checked-in reference image.
Installation
npm install pixi-tiledmap pixi.jsQuick Start — Asset Loader (recommended)
Register the loader extension once, then load .tmj (JSON) or .tmx (XML) files through Assets:
import { Application, extensions, Assets } from 'pixi.js';
import { tiledMapLoader } from 'pixi-tiledmap';
extensions.add(tiledMapLoader);
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const { container } = await Assets.load('assets/map.tmj');
app.stage.addChild(container);The loader auto-detects the format by file extension:
.tmj→ JSON,.tmx→ XML.
Manual Construction
If you prefer to parse and build the display tree yourself:
import { parseMap, TiledMap } from 'pixi-tiledmap';
import { Assets, Texture } from 'pixi.js';
import type { TiledMap as TiledMapData } from 'pixi-tiledmap';
const response = await fetch('assets/map.tmj');
const data: TiledMapData = await response.json();
const mapData = parseMap(data);
const tilesetTextures = new Map<string, Texture>();
for (const ts of mapData.tilesets) {
if (ts.image) {
tilesetTextures.set(ts.image, await Assets.load(ts.image));
}
}
const container = new TiledMap(mapData, { tilesetTextures });
app.stage.addChild(container);For image layers, image-collection tilesets, and animated GIF sources, pass the corresponding texture maps through TiledMapOptions. The asset loader fills these maps automatically.
API Reference
Exports
| Export | Description |
| --------------------- | ---------------------------------------------------------------- |
| tiledMapLoader | PixiJS LoadParser extension — register with extensions.add() |
| TiledMap | Container subclass that renders a resolved map |
| TileLayerRenderer | Packed mesh-backed Container for a single tile layer |
| ImageLayerRenderer | Container for a single image layer |
| ObjectLayerRenderer | Container for a single object layer |
| GroupLayerRenderer | Container for a group layer (recursive) |
| PackedTileLayerRenderer | Internal packed tile mesh base used by TileLayerRenderer |
| TileSetRenderer | Texture manager for a tileset |
| parseMap(data) | Synchronous Tiled JSON → resolved IR |
| parseMapAsync(data) | Async variant (required for gzip/zlib compressed data) |
| parseTmx(xml) | Parse TMX XML string → TiledMap data (same shape as JSON) |
| parseTsx(xml) | Parse TSX XML string → TiledTileset data |
| parseTx(xml) | Parse TX XML string → TiledObjectTemplate data |
| decodeGid(raw) | Decode a raw GID into tile ID + flip flags |
TiledMap Container
const map = new TiledMap(resolvedMap, {
tilesetTextures, // Map<imagePath, Texture>
imageLayerTextures, // Map<imagePath, Texture>
tileImageTextures, // Map<imagePath, Texture> (image-collection tiles)
tileImageGifSources, // Map<imagePath, GifSource> (animated image-collection tiles)
imageLayerGifSources, // Map<imagePath, GifSource> (animated image layers)
layerFilter, // optional (layer) => boolean, for rendering selected layers
tileSpritePadding, // optional, defaults to 0.01 to hide fractional-scale seams
});
map.orientation; // 'orthogonal' | 'isometric' | 'staggered' | 'hexagonal'
map.mapWidth; // tile columns
map.mapHeight; // tile rows
map.tileWidth; // tile pixel width
map.tileHeight; // tile pixel height
map.getLayer('ground'); // find layer Container by name
// Parallax: call after moving your camera each frame. Layers with
// parallaxx/parallaxy < 1 move slower than the camera; layers with
// parallax 0 are pinned in screen space. Group-layer parallax composes
// multiplicatively with its children.
map.applyParallax(camera.x, camera.y);To split a map around a player sprite, render the same resolved map twice with different layer filters:
const isOverhead = (layer: ResolvedLayer) =>
layer.properties.some((prop) => prop.name === 'overhead' && prop.value === true);
const belowPlayer = new TiledMap(resolvedMap, {
tilesetTextures,
layerFilter: (layer) => !isOverhead(layer),
});
const abovePlayer = new TiledMap(resolvedMap, {
tilesetTextures,
layerFilter: isOverhead,
});Object Templates
When loading through the asset loader, any object with a template field
is resolved automatically — referenced .tx / .tj files are fetched in
parallel and merged into the map before rendering.
For manual construction (parseMap / parseMapAsync), pass templates via
ParseOptions.templates:
import { parseMap, parseTx } from 'pixi-tiledmap';
const templates = new Map();
templates.set('sign.tx', parseTx(await (await fetch('sign.tx')).text()));
const mapData = parseMap(data, { externalTilesets, templates });Template-instance merging follows Tiled semantics: the template's object
fields are the base, and the instance overrides any field it explicitly
sets (name, type, size, properties, gid, shape). If the template carries
an external-tileset reference whose source also exists in the map,
gid is translated from the template firstgid-space to the map
firstgid-space, preserving flip flags.
Migration from v1
| v1 (PixiJS v4) | v2 (PixiJS v8) |
| ------------------------------------- | -------------------------------------------------------- |
| PIXI.loader.add('map.tmx').load(…) | extensions.add(tiledMapLoader); Assets.load('map.tmj') |
| new PIXI.extras.TiledMap('map.tmx') | const { container } = await Assets.load('map.tmj') |
| Global namespace mutation | Named ESM imports |
| TMX XML via tmx-parser | Built-in JSON + XML parser (no external deps) |
| Tile + image layers only | All layer types |
Development
npm install
npm run build # ESM + CJS + types via tsdown
npm run dev # watch mode
npm run check # Biome lint + format
npm run typecheck # tsc --noEmit
npm test # Build, Vitest, and MagicLand visual regression
npm run bench # renderer hot-path benchmarksBenchmark guidance and the current smoke baseline live in docs/BENCHMARKS.md.
