TiledWrapper

The TiledWrapper component wraps a tilemap exported from the Tiled map editor and selects which layer to render. It works together with a TilemapRenderer on the same entity, which draws the tiles using the tilesets of the map. It can also create entities from the objects placed in the tilemap.

Limitations

  • Only orthogonal maps are supported. The other orientations are not translated, so the tiles of an isometric or hexagonal map are placed as if the map were orthogonal.
  • Tiles flipped or rotated in Tiled are not supported yet. Their ids carry the flip flags, so they are not rendered as expected. Support is planned.
  • The collision shapes drawn on the tiles of a tileset are not supported. The objectgroup a tile carries from the Tile Collision Editor is not read. The colliders of a tilemap come from the TilemapCollider, which generates them from the tiles that are not empty.

Options

OptionTypeDescription
tilemapPathstringThe URL of the JSON tilemap exported from Tiled. A wrapper without one is ignored.
layerToRenderstringThe name of the Tiled layer to render.
objectsMap<string, TiledObjectBlueprint>The entities to create from the objects of the tilemap, keyed by the class of the Tiled object.

Example

import { Transform, TiledWrapper, TilemapRenderer } from "angry-pixel";

this.entityManager.createEntity([
    new Transform(),
    new TiledWrapper({ tilemapPath: "tilemap/map.json", layerToRender: "Ground" }),
    // the tilesets are created from the ones embedded in the map
    new TilemapRenderer({ layer: "Foreground" }),
]);

The tilemap is referenced by the URL of its JSON export. It does not need to be loaded beforehand: when it is not among the loaded assets, the engine loads it and reads it as soon as it is available, so the map renders a few frames after the entity is created. Loading it in the scene’s loadAssets method with the asset manager is still recommended, because the scene waits for its assets before creating the entities, and the map is there from the first frame.

loadAssets(): void {
    this.assetManager.loadJson("tilemap/map.json");
    this.assetManager.loadImage("image/tileset.png");
}

The URL is also what the paths of the tileset images are resolved against, so reference the tilemap by its URL rather than by an asset name.

Tilesets

The tilesets of the TilemapRenderer are created from the ones embedded in the map the first time the component is processed, with their image, tile size, margin, spacing, first tile id and number of tiles. A TilemapRenderer used with a TiledWrapper does not need to declare them.

The tilesets must be embedded in the JSON export. Tiled can keep a tileset in its own .tsx file and reference it from the map; those tilesets are not read by the engine, because the map only carries their firstgid and the path of the .tsx. Embed them in the map with the Embed Tileset action of the Tilesets panel, or declare the tilesets of the TilemapRenderer by hand. Otherwise the component throws when it tries to create them — both for a map that declares no tilesets at all and for one whose tilesets are external — and the tiles animated in Tiled are not mapped either.

Tiled stores the path of a tileset image relative to the map file, so it is resolved against the URL of the map: a map loaded as tilemap/map.json that uses ../image/tileset.png needs its image loaded as image/tileset.png.

The tilesets can still be declared by hand, which is needed when the images are loaded under a name that does not match their path, or to add tile animations of your own. The declared tilesets are matched by position with the ones of the map, so they have to be listed in the same order, and the firstgid of each one is taken from the map unless it is set by hand.

Layers

The layer named in layerToRender is searched in the whole tilemap, including inside group layers. These properties of the layer are applied to the TilemapRenderer:

Layer propertyApplied as
offsetx / offsetyThe offset of the renderer. The offset of the groups that contain the layer is added to it. It is applied to the TilemapCollider too, so the colliders follow the tiles.
opacityThe opacity of the renderer, multiplied by the opacity of the groups that contain the layer.
tintcolorThe tintColor of the renderer. The alpha channel is discarded.
visibleA layer that is not visible, or that belongs to a group that is not visible, renders nothing and generates no colliders.
startx / startyThe origin of an infinite tilemap, whose chunks are placed from the top-left corner of the layer instead of the origin of the map, and can have negative coordinates.

The size of the tilemap and the size of its tiles are taken from the tilemap, and from the layer bounds in the case of infinite tilemaps.

Animated tiles

The tiles animated in Tiled are mapped to the animations of the TilemapRenderer tileset they belong to the first time the component is processed, so they play without any extra configuration.

A tile is keyed by the firstgid of its tileset plus the id it has within it. Animations already defined in the tileset take precedence over the ones declared in Tiled.

Tiled allows a different duration for each frame, while the engine renders every frame of an animation at the same rate. The average duration is used, which keeps the total duration of the animation and is exact whenever every frame lasts the same.

Updating the tilemap at runtime

The tilemap is read once, and the tile data is processed once. To apply a change made at runtime, such as rendering another layer, call refresh on the components involved:

const tiledWrapper = this.entityManager.getComponent(entity, TiledWrapper);
tiledWrapper.layerToRender = "Background";

tiledWrapper.refresh();
this.entityManager.getComponent(entity, TilemapRenderer).refresh();
this.entityManager.getComponent(entity, TilemapCollider).refresh();

refresh reads the tilemap again, processes the tile data again and generates the collider shapes again. It is an expensive operation, do not call it on every frame. The entities created from the objects of the tilemap are not created again.

Creating entities from Tiled objects

The objects map associates a Tiled object class with a blueprint. One entity is created for each object of that class, and the objects whose class is not in the map are ignored. The entities are created once, the first time the component is processed.

A blueprint can be:

  • An archetype.
  • A collection of components (instances or classes).
  • A factory function that receives the object’s properties and returns either of the two.
import { TiledWrapper, TiledObjectBlueprint, Transform, SpriteRenderer } from "angry-pixel";
import { playerArchetype } from "../entity/Player";
import { Door } from "../component/Door";

const objects = new Map<string, TiledObjectBlueprint>([
    // an archetype
    ["Player", playerArchetype],
    // a collection of components
    ["Coin", [new Transform(), new SpriteRenderer({ image: "coin.png" })]],
    // a factory function
    ["Door", (properties) => [new Door({ locked: properties.get("locked") as boolean })]],
]);

new TiledWrapper({ tilemapPath: "tilemap/map.json", layerToRender: "Ground", objects });

Object properties

The factory function receives the properties of the Tiled object as a Map keyed by property name, and the Tiled object itself as a second argument (useful to read its name, width, height, polygon, etc.).

A property value can be a number (int and float), a boolean (bool), a string (string, color and file), an Entity (object), or a nested set of values (class), so it needs to be cast to the expected type.

Properties of type object reference another Tiled object by its id. They are given to the factory as the entity created for the referenced object, so entities can be linked to each other. All the entities are created before any factory is called, so the order of the objects in the tilemap does not matter. A property that references an object for which no entity was created is given as undefined.

Position

The entity’s Transform position is set from the object’s x and y, relative to the center of the tilemap, plus the position of the entity that holds the TiledWrapper. If the blueprint does not include a Transform, one is added.

The position is calculated from the center of the object, in the space the tilemap is rendered in: the size and the tile size of the TilemapRenderer, which do not always match the ones declared by Tiled. An infinite tilemap is rendered with the size of its chunks. When the entity has no TilemapRenderer, the values declared in the tilemap are used.

Tile objects (objects with a gid) have their origin in the bottom-left corner; the rest have it in the top-left corner.

The rotation of the object is applied to the Transform as well, and the center of the object rotates around its origin, as it does in Tiled. A rotation of zero is not applied, so a blueprint can define a rotation of its own.

Rules

  • Objects are matched by class in every object layer of the tilemap, regardless of the layer they belong to, including the layers nested in groups.
  • Objects with visibility turned off are ignored, and so are all the objects of a layer or a group with visibility turned off.
  • The offset of the object layer, and of the groups that contain it, is applied to the position of the object.
  • The parent-child relationship between Tiled objects is not supported.
  • Tiled 1.9 exports the class of an object as class, and the other versions as type. Both are supported.