@argila/tilemap
v0.1.0
Published
Chunked tilemaps for argila: Tiled loading, Canvas2D and Pixi rendering, Matter.js and Rapier collisions
Readme
@argila/tilemap
Chunked tilemaps for argila: Tiled maps loaded into a tilemap component, drawn by core's
Render (Canvas2D) or by @argila/pixi, colliding through Matter.js or Rapier.
Tilemap: layers and tilesets on one entity, the tiles kept in 16×16 chunks.Tilemap.fromTiled(json, textures)reads a Tiled JSON export (orthogonal or isometric),Tilemap.setTileandgetTileedit and read it.TilemapRender: pre-draws each chunk layer once to a canvas and shows it as a sprite, soRendersorts it with everything else. Chunks outside the camera are hidden.TilemapPixiRender(@argila/tilemap/pixi): a mesh per chunk layer and tileset.TilemapCollider(@argila/tilemap/matter): static bodies for the solid, one-way and sloped tiles, rebuilt per chunk when tiles change.TilemapRapierCollider(@argila/tilemap/rapier): fixed Rapier boxes for the solid tiles, from each layer's elevation up one block, merged per chunk and rebuilt when tiles change (see@argila/rapier).TileQuery: what is at a point (solid, floor below, liquid), asked straight from the tiles.readTiledObjectsandObjectSpawner: the map's objects, spawned by type.TilemapDebug(@argila/tilemap/debug): collider and chunk overlays for the debug views.
import { Assets, Entity, Render, Transform, World } from "@argila/core";
import { isTiledMap, Tilemap, TilemapRender } from "@argila/tilemap";
const json = await Assets.loadJSON("level.json", isTiledMap);
const map = Entity.create(world);
Transform.create(map, 0, 0);
Tilemap.create(map, Tilemap.fromTiled(json, { terrain: await Assets.load("terrain.png") }));
Render.create(map, "main");
World.addSystem(world, TilemapRender);Isometric maps
An isometric (diamond) map is a top-down map drawn at a slant. It lives in ground space, a
flat top-down plane: each cell is a tileheight × tileheight square there, which is Tiled's own
convention for the object coordinates of isometric maps. The slant is the camera's view
projection, applied only when drawing. So the colliders, TileQuery, Tilemap.worldToTile and
the objects from readTiledObjects work exactly as on an orthogonal map, in ground pixels.
Tilemap.create(map, Tilemap.fromTiled(json, textures)); // orientation: "isometric"
Camera.configure(camera, { projection: Tilemap.projection(map), target: hero });
Render.setDepthSort(world, true); // props from the object layer sort by depthTilemap.projection(map)is the projection that shows the map as authored:Isometric.projection(tilewidth, tileheight)for an isometric map,undefinedfor an orthogonal one. The map records itsorientationand its art tile size (artTileWidth,artTileHeight), whiletileWidthandtileHeightare the ground cell.- The renderers draw each chunk as the camera sees it: the diamonds back to front, each picture standing on its diamond with its tileset's offset, as Tiled draws them. Each chunk is placed upright at its projected ground origin, so it lines up with its neighbours. Seen without a projection, the same map is drawn top-down, its diamonds flattened into ground squares. An orthogonal map seen through an isometric camera turns into diamonds.
- Culling takes the camera's view back into ground space, so no visible chunk is hidden. Camera
bounds are in view space:
Projection.boundsInto(Tilemap.projection(map), 0, 0, width, height, bounds)gives the box around the map's diamond. - Tile layers are flat floors: each layer is drawn at one
zIndex. Make walls you walk behind, trees and pillars entities from an object layer (ObjectSpawner), at thezIndexof the things that move among them, and turn on depth sorting (Render.setDepthSort(world, true)). Anchor tall sprites at their feet (Sprite.setAnchor(e, 0.5, 1)) so they sort by where they stand. A tall tile in a tile layer still draws, but over or under what moves near it as its whole layer does. - Slopes and tile collision polygons of an isometric map are read in the ground cell (a
tileheightsquare, origin at its top-left corner), not in the tile's picture. - Isometric drawing assumes a map with a uniform scale and no rotation. Staggered and hexagonal maps are not supported.
Raised layers
A layer's elevation custom property (a number, in ground units, 0 by default) raises it: ground
space is x and y on the ground, and elevation is z, up, as on transforms.
- A tile layer with an
elevationdraws all of its chunks at that z (above the map entity's own), so layers stacked at rising elevations make raised ground: a terrace, a rooftop. Under an isometric camera one ground unit up is one view pixel up (Isometric.projection'srise); seen without a projection, elevation moves nothing. It is read once, intoTileLayer.elevation, when the layer is made (properties: { elevation: 32 }for a layer built in code). - Culling accounts for it: the view is taken back into ground space on the planes of the lowest and the highest layer, so a raised chunk is shown where it is drawn.
- An object layer's
elevationis its objects'z(MapObject.z, fromreadTiledObjects): give the entity a spawn makes that z (Transform.setZ) to put it on the raised ground. - Only a layer's own property counts: a group's doesn't add to its layers'.
readElevation(properties)reads the property the way the renderers do (a finite number, else 0), for anything else that needs it, like colliders with heights.- The debug views draw at the elevations too: colliders and tile queries at the colliding
layers' (the lowest when they differ), chunk outlines at each of their layers', objects at
their
z. - The colliders from
@argila/tilemap/matterstay flat: Matter knows no heights. The ones from@argila/tilemap/rapierhave them: each solid tile is a box from its layer'selevationupblockHeight, so stacked layers make ledges and stairs a Rapier character can climb.
