loaderplugin
The scene's catalogue of files, fetched and decoded once, for rules, actors and plugins.
Putting it in a scene
List loader in the scene's plugins. The scene's sim lists the loader kind from #plugins/loader/loader.sim.ts, and the scene has one loader row, which holds nothing. A scene that is a child of the scene manager uses the manager's catalogue unless it gives its own.
"plugins": ["loader", "render2d", "sprite2d"],
"world": {
"loader": {
"assets": {
"defs": { "type": "json", "url": "/games/mygame/assets/defs.json", "preload": true },
"hero": { "type": "sheet", "url": "/games/mygame/assets/hero.json", "preload": true, "nearest": true, "facing": { "first": 0.25, "step": 0.25, "mirror": true } },
"colors": { "type": "palette", "url": "/games/mygame/assets/db32.gpl", "preload": true },
"level": { "type": "tilemap", "url": "/games/mygame/assets/level.tmj", "preload": true, "nearest": true },
"ui": { "type": "bitmapfont", "url": "/games/mygame/assets/ui.fnt", "for": "ui" },
"boom": { "type": "sound", "url": "/games/mygame/assets/boom.ogg" }
}
}
}
A url is a plain path from the folder the dev server serves, written as the file is named: a space is a space and a % is a %. A path inside a file, such as a sheet's picture or a map's tileset, is read from that file's own folder.
Properties
Read from world.loader.
.assets : { [name]: entry }
The catalogue. Each name is what rules, actors and other modules ask for, and each entry says what kind of file it is and where it is. Required. The catalogue never changes while the scene runs, so no row carries it and no packet sends it.
Entries
The fields of one entry of assets. Which fields apply depends on type.
| Field | Meaning |
|---|---|
type | One of json, palette, pixels, binary, sheet, tilemap, texture, bitmapfont, model, sound. |
url | The path of the file. |
preload | true to fetch the file when the scene starts. A json, palette, pixels, binary, sheet or tilemap entry with preload has landed before the scene's first step and before the page draws its first frame, so rules and actors can read it from the start. Any other entry with preload starts on the page's first tick. An entry without it is fetched when something asks for it. |
for | "ui" on a texture, sheet or bitmapfont decodes it for the ui module, so it comes out as a PixiJS texture even in a 3D scene. Left out, the entry is decoded for the scene's renderer. A scene that names no ui cannot load an entry marked "ui". |
json
A data file read as it is parsed. Use it for anything a rule reads that never changes, so no row has to carry it to every seat every step. A scene's definitions are one such file, named by world.defs, with an entry per kind that holds every number of that kind that never changes; def reads one.
palette
A GIMP palette (.gpl). The first line is GIMP Palette. Name: and Columns: lines, # comments and blank lines are skipped, and each other line is a colour: R G B from 0 to 255, then an optional name. The value is an array of colours as 0xRRGGBB numbers, in the order the file lists them.
pixels
A PNG read as data, not drawn: a level's heights, a saved city, anything a rule reads a value a pixel from. The value is { width, height, data, text }. data is every pixel as red, green, blue and alpha from 0 to 255, a row at a time from the top, whatever the file's colour type, bit depth or interlacing; a 16-bit sample is read as its high byte. Alpha is not multiplied into the colour, so a pixel of alpha 0 keeps its colour. text holds the file's text chunks by keyword.
binary
A file read byte for byte, for a format of its own that no other type decodes, such as a ROM, a disk image or a compiled module. Rules read it as a Uint8Array, and the page's copy is { bytes }. Nothing may write into it. A file written while the game runs hands its new bytes from the next call on, so read it each time instead of keeping the array.
sheet
A sprite sheet: frames cut from page images, and clips that play them. The file is an Aseprite or TexturePacker JSON, or a Tiled tileset (.tsj).
| Field | Meaning |
|---|---|
nearest | true for pixel art: every page is drawn with nearest filtering on whole pixels. |
facing | { first, step, mirror }, which way each run of a clip faces. Run k faces first + k × step of a whole turn, counter-clockwise from facing right on the screen. With mirror, a way no run faces is drawn as the run facing its mirror image across the up-down line, flipped. Defaults 0, 0 and false: a clip's one run faces right and nothing is mirrored. |
palette | The name of a palette entry, the colours the sheet is drawn in, which a row may swap for another palette's (sprite2d). |
These three are set in the catalogue, never in the file, because Aseprite drops what it does not know from a file it writes.
From an Aseprite or TexturePacker JSON the loader reads:
| In the file | Becomes |
|---|---|
frames, a list or an object by name | A frame each. Its id is its filename, or its key in an object. frame is where it sits on the page, turned a quarter clockwise there when rotated. When trimmed, spriteSourceSize and sourceSize place the kept pixels in the whole frame. pivot is the anchor as a fraction of the whole frame, and may lie outside 0 to 1. duration is how many milliseconds the frame shows. |
meta.image | The page. |
meta.frameTags | A clip each, the frames from to to, played direction (forward, reverse, pingpong or pingpong_reverse), round forever or repeat times; 0 or left out is forever. In a ping-pong each pass one way is one time. |
animations | A clip each of the frames it names, played forward, round forever. |
a tag or animation named <clip>@<k> | Run k of <clip>. A clip with runs for eight facings is eight tags, numbered 0 to 7 with none missing. |
meta.slices | Aseprite's slices by name, each a list of keys { frame, bounds, pivot?, center? }: from frame number frame of the file's order on, until the next key, the slice is bounds in the whole frame, pivot a point from the bounds' top left, and center the middle a nine-slice stretches. |
meta.related_multi_packs | The other packs of a multipack, each read the same way, its image the next page. |
From a Tiled tileset each tile is a frame whose id is the tile's id, cut from the tileset's image by columns, margin and spacing, or from its own image in a tileset of images. It stands on the point Tiled stands a tile object on, objectalignment (unspecified is bottom-left), moved by tileoffset. Every tile with a Tiled type is a clip named by that type (the first such tile when several share one): its animation, round forever, or else the tile alone, still. A tile with an animation and no type is a clip named by its id.
A clip whose frames have no duration is not timed. A rule then picks its frames one by one, or a particle spreads them over its life.
The value a rule reads is a Sheet:
| Key | Meaning |
|---|---|
pages | The page images, plain paths. |
frames | By id, each { id, page, box, rotated, size, trim, anchor }. box is [x, y, w, h], the pixels as they lie on the page; when rotated they lie turned a quarter clockwise, so box is the upright picture's height across and its width down. size is [w, h], the whole upright frame, and trim is [x, y, w, h], where the kept pixels lie in it. anchor is the point of the whole frame, in pixels from its top left, that stands on the place the frame is drawn at, such as a thing's feet. |
order | The frame ids in the file's order, a multipack's packs one after another. |
clips | By name, each { runs, loop, durations? }. runs is a list of frame ids per facing, in the order they play, with a ping-pong's way back and every repeat written out. loop is true to go round again after the last frame, else the clip is held on it. durations is the seconds each place of a run shows, present when the file times its frames. |
slices | By name, each a list of keys { frame, bounds, pivot?, center? } as above. Only Aseprite files have them. |
facing, nearest, palette | As the entry says. |
tilemap
A Tiled map saved as JSON (.tmj), with its tilesets embedded or as .tsj files beside it. A .tmx or .tsx is refused. The map must be orthogonal or isometric; staggered and hexagonal maps are refused, as are rotated objects and objects made from a template. A tileset that draws its tiles at the grid's size instead of their own is refused. Only nearest applies, filtering every image nearest. The map is drawn by render2d, and an entry for it fails in a scene without one. A tile in the value is named by its gid, its tileset's firstgid plus its id.
| Key | Meaning |
|---|---|
orientation, renderorder | orthogonal or isometric; and right-down (the default), right-up, left-down or left-up. |
width, height, tilewidth, tileheight, infinite | The map's size in cells and a cell's size in pixels, and whether it is infinite. |
background, properties | The background colour as 0xRRGGBB, or null when it has none; and the map's custom properties by name. |
tilesets | Each { name, firstgid, count, tilewidth, tileheight, offset, source }. |
tiles | By gid, each { gid, tileset, id, page, box, type, properties, shapes, animation? }. type is the Tiled type or an empty string. animation is a list of { tile, duration }, a gid and milliseconds. shapes are the collision rectangles and convex polygons of the tile's object group, each { points, top }: points in the tile's own pixels going clockwise on the screen, and top true when the object's type is top, a one-way shape. A shape that is not convex, is rotated, or is an ellipse, polyline or point is refused. |
layers | Bottom first, with groups flattened into them. Each has name, kind, visible, opacity, tint, offset, shift, parallax and properties. Offsets, parallax factors, opacity and tint are the layer's own combined with those of the groups over it; the layer is visible only when every group over it is. offset is in pixels on the screen and shift the same in cells. tint is 0xRRGGBB or null. |
a layer of kind tiles | x, y, width, height, data and flips. data is the gid of each cell a row at a time, 0 for none, and flips is each cell's flip bits: 4 horizontal, 2 vertical, 1 diagonal. Cells may be a CSV list or base64, uncompressed or zlib, gzip or zstd. An infinite map's chunks are laid into one grid whose first cell is [x, y]. Two tile layers may not share a name. |
a layer of kind image | image, a plain path, and repeat, [x, y] booleans. |
a layer of kind objects | objects, each { id, name, type, x, y, w, h, visible, properties } plus polygon or polyline (points from x, y), point, ellipse, and for a tile object its gid in tile and flip bits in flip. A tile object's own properties go over its tile's. Positions and sizes are in cells and include the layer's offset; a tile object's y is its top, though Tiled places it by its bottom. |
A cell is 1 across and 1 down in the value: a point [x, y] of the world is in cells, x along a row and y down the rows. An orthogonal map's pixels are tilewidth across and tileheight down a cell. An isometric map's objects are tileheight pixels a cell both ways, as Tiled writes them.
texture
A picture, decoded for the scene's renderer: a three.js texture under render3d, a PixiJS texture under render2d or with for: "ui". A scene with neither renderer gets an HTMLImageElement, which an <img> or a CSS background takes. Under render3d these three fields also apply:
| Field | Meaning |
|---|---|
space | "linear" for a picture of numbers, such as a normal or roughness map, sampled as stored. Left out, colours are sRGB and decoded to linear light as they are sampled. |
wrap | "repeat" repeats the picture past its edges both ways, for a surface that tiles it. |
anisotropy | A whole number from 1: how many texels to sample along where a surface is seen at a slant. The GPU's own maximum caps it. |
bitmapfont
An AngelCode BMFont: its .fnt, as text or XML, and the page images it names beside it. It is installed as PixiJS's bitmap font of the entry's name, so a BitmapText whose style's fontFamily is that name draws with it. for: "ui" decodes it for the ui module; otherwise it is for render2d, and the scene needs one of them. nearest filters its pages nearest. A name PixiJS already has a font of is that font, one per page.
model
One self-contained .glb, in any letter case. A .gltf, which names files beside it, is refused. A glb packed by gltfpack with meshopt compression decodes like any other. It needs render3d in the scene.
sound
An audio file, decoded through the audio module's Web Audio context, which the scene needs. The value is an AudioBuffer.
Methods
Rules
From #plugins/loader/loader.sim.ts. from is the w a rule is handed, or the plugins.wire of an actor. A rule reads a json, palette, pixels, binary, sheet or tilemap entry; the process keeps one decoded copy of each, shared by every rule and actor. A read fails while the file has not landed, so a rule never sees half of one.
json( from, name ) : any
The parsed file.
def( from, kind ) : any
The entry for kind in the json entry that world.defs names. Fails if the scene names no defs or the file has no entry for the kind.
sheet( from, name ) : Sheet
tiled( from, name ) : Tiled
palette( from, name ) : number[]
pixels( from, name ) : { width, height, data, text }
binary( from, name ) : Uint8Array
The decoded entry, each as its entry type above. Each fails if the entry is of another type.
load( w, name )
Starts fetching an entry that is not preloaded. It starts a name once for the scene, however many steps ask, so a rule may call it on every step until ready.
ready( w, name ) : boolean
Whether every file the entry reads has landed and, for the six types a rule reads, been decoded.
asset( w, name ) : ArrayBuffer
The bytes of the entry's file. Fails while they have not landed.
prepared( world, name ) : Promise
Fetches the entry if nothing has, and resolves to its decoded value once every file it reads has landed (a binary entry resolves to its bytes). It rejects with the reason a file cannot be fetched or decoded. A kind's own prepare uses it for a file its first step reads, since that runs beside the loader's own and not after it. It rejects for a type a rule does not read.
Sheets
From #plugins/loader/loader.sim.ts. These take a Sheet, as sheet returns.
pick( sheet, clip, turns ) : { run, mirror }
The run of clip that faces turns, a fraction of a whole turn counter-clockwise from right on the screen, and whether to draw it mirrored. It chooses the run, or the mirror image of a run, nearest that way; a run wins over its mirror image, and an earlier run over a later one. A clip of one run on a sheet that mirrors nothing faces every way.
frameOf( sheet, clip, run, i ) : Frame
Frame number i of run run of the clip.
play( sheet, clip, frame, ft, dt ) : [frame, ft]
The frame a clip shows and the seconds it has shown it, dt seconds on from showing frame frame for ft seconds. It moves on when a frame has shown for its duration. After the last frame it goes back to the first if the clip loops, and otherwise holds on the last with no time on it. A clip of one frame that is not timed holds it. Fails for a clip that is not timed and has more than one frame.
span( sheet, clip ) : number
Seconds the clip takes to play through once. Fails for a clip that is not timed.
reach( frame, mirror ) : [left, right, up, down]
How far a frame's kept pixels reach from its anchor as it is drawn, in pixels. A mirrored frame reaches left as far as the frame reaches right.
Pictures and palettes
From #plugins/loader/loader.sim.ts.
decodePng( bytes ) : { width, height, data, text }
Reads a PNG as the pixels entry does: every colour type and bit depth, palette and colour transparency, and Adam7 interlacing. It is done here and never by a canvas, because a canvas multiplies a colour by its alpha and back, which loses a value stored in a channel.
encodePng( pixels ) : Uint8Array
Writes the same shape as an 8-bit RGBA PNG, its text as iTXt chunks. What decodePng reads back is what was written, pixel for pixel and word for word. A text keyword is 1 to 79 Latin-1 characters with no NUL.
isPng( bytes ) : boolean
Whether the bytes start with the PNG signature.
gpl( text, url ) : number[]
Reads GIMP palette text into 0xRRGGBB colours, as the palette entry does.
Files written while the game runs
The editor writes a file while the game runs, and every decoded copy that read it is refilled in place, so every rule and actor already holding one reads what the file now says. From #plugins/loader/loader.sim.ts.
written( url, content )
The file at url now holds content, new text or, for a pixels or binary file, new bytes. A file the new text names that the process does not hold yet is fetched first.
holds( url ) : boolean
Whether the process has read the file into anything it decoded.
writes( url ) : number
How many times the file at url has been written on the machine while the process ran, counting pictures a rule never reads, such as a sheet's page. The page fetches a picture written since the process began with ?written=<n> added to its url, so a renderer that keeps a picture by its url decodes it as it now is.
jsonAt( name, url ) : Promise
Fetches and decodes a file with no catalogue entry, and resolves to the copy every rule reads. sheetAt( name, url, entry? ), paletteAt, tiledAt, pixelsAt and binaryAt do the same for their types. A test uses them to hold the process's copy.
On the page
Through plugins.loader, from an actor or another plugin. The page's copies are its own: letting go of a texture or model on one page leaves every other page's whole. A name asked for before the page's first tick waits for it.
get( name ) : thing | undefined
The decoded thing, or undefined while it is still coming. What it is depends on the type: a parsed value, a texture, a { data, pages, texture, uv, pick, frameOf } for a sheet, a { map, texture, image, version } for a tilemap, a BitmapFont, a glTF, an AudioBuffer.
ready( name ) : boolean
Whether the thing has landed.
load( name ) : Promise
Starts the entry if it has not started, and resolves to the thing. It never throws. A name that cannot be started or a file that cannot be fetched or decoded rejects the promise and is written to the console once. A failed name is not fetched again by itself, and every later load returns the same rejected promise.
retry( name ) : Promise
Starts a failed name again.
on( name, f )
Calls f( thing ) once the thing is here, or straight away if it already is. It starts the entry if it has not started. Draw something else until then.
model( name ) : { scene, animations }
The model's scene, cloned with its skeleton on each call so every row that draws it has a skeleton of its own, and its animation clips. Fails if the model has not landed.
sound( name ) : AudioBuffer
file( name ) : Promise
A json, palette, pixels, binary or tilemap entry's value, or a sheet entry's data, with no image decoded. The editor reads a running game's files with it.
beside( name, path ) : Promise<ArrayBuffer | undefined>
The bytes of a file no entry names that sits beside entry name's file, such as a music project's samples. path is read from the folder of the entry's url. A file that is not there is undefined and nothing is written to the console. The bytes of a file that landed are kept until it is written; a missing file is looked for again on every ask, so a file put there while the game runs is found.
onWritten( name, f )
Calls f each time a file that entry name read is written, once this page's copy holds the new text. Use it to build again what was built once from the file, such as a level's meshes. For a sheet or map it runs after the new images have landed, and it does not run for a write whose images failed or that a later write overtook.
written( url ) : Promise
Refills every file of this page that read url in place from the file as the process now holds it, so the process's written is called first. A sheet's or map's new images are decoded first and swapped in together only once every one has landed; if one fails the old ones stay and the failure is written to the console. A name at that url that had failed may start again.
progress() : { done, started }
How many of the entries started and not failed have landed, and how many started and have not failed.
catalogued( name ) : entry | undefined
The catalogue's entry for name, for a module that asks what kind of file a name is without reading world.loader itself.
uv( frame, width, height ) : [u, v] × 4
Exported from #plugins/loader/loader.plugin.ts. Where the corners of a frame's kept pixels, upright, lie on a page of width × height pixels, for three.js: top left, top right, bottom left, bottom right, eight numbers in all, each from 0 to 1 with the page's top at v = 1.
Sheets and maps on the page
What get hands back, once every page image has landed.
| Key | Meaning |
|---|---|
sheet.data, .pages | The Sheet a rule reads, and the decoded page images. |
sheet.texture( id ) | One frame as a PixiJS texture, cut from its page once. Its whole frame is orig, the pixels kept are trim, and a frame that lies turned on the page is turned back upright, so a sprite's anchor is a fraction of the whole frame. Fails for a sheet decoded for three. |
sheet.uv( id ) | The corners of the kept pixels on their page, for a quad that spans their place in the whole frame. |
sheet.pick, .frameOf | pick( clip, turns ) and frameOf( clip, run, i ) as above, with the sheet filled in. |
map.map | This page's read of the Tiled map. |
map.texture( gid ), .image( url ) | One tile as a PixiJS texture, cut from its image once; an image layer's image. |
map.version | How many times the map has been written, so whatever drew it knows to draw it again. |
A sheet or tilemap entry fails in a scene with no renderer to cut frames for.
Used by
Every module that draws a picture or reads a data file asks the loader for it, among them sprite2d, tilemap, skydome3d and scatter3d. Rules read the files it decodes through the functions above.
Works with
- scenemanagerWaits for the loader's preloaded files before it boots a scene and before a page makes its client, so the page's actors find their files from the first frame.
- rewindA saved truth carries which preloads were started and which names rules asked for, so loading a save starts nothing twice.