Back to the referenceScenes
Declaring scenes, switching between them, pausing one without stopping the loop, the transitions, and the scene document.
48 symbols
#createScenefunctionnacatamalon
createScene(children: typeOperator) => TScene
Ends a scene's body, and is what it returns: return createScene().
Everything the body made (sprites, models, objects from useSpawn) is already in the scene, so
it is usually called with nothing. children is for objects built somewhere else and handed in.
It touches nothing itself: it only describes.
It takes no name. A scene is named by its key in what createGame is given, so the game knows
every scene by name as soon as it starts, before any of them has run.
Parameters
childrentypeOperatorObjects made outside this body that should hang from the scene too.
Example
const Menu: TSceneFn = () => createScene();
const Level: TSceneFn = () => {
const hero = createSprite({ width: 16, height: 16 });
useUpdate((delta) => {
hero.transform.x += 30 * delta;
});
return createScene();
};
createGame('#app', { width: 320, height: 240 })({ Menu, Level });
View source · scene/create_scene.ts:36#fadefunctionnacatamalon
fade(duration: number, color: TColor) => TTransition
The screen sinks into one colour, the scenes swap behind it, and it comes back.
The one every game reaches for, and the one to reach for when nothing else fits: it says "we went
somewhere else" without saying anything about where, so it never looks wrong.
Note that a fade runs before the rest of the chain, so on a game that reduces its colours the
middle of it is quantized like everything else: stepped, not smooth. That is how a fade looked on
the hardware this engine is aimed at, and it is the reason the transition goes first.
Parameters
durationnumberHow long the whole thing takes in milliseconds, covering and uncovering together.
colorTColorWhat the screen sinks into. Black unless you say otherwise.
View source · transition/builtin/fade.ts:25#irisfunctionnacatamalon
iris(duration: number, color: TColor, centre: { x, y }) => TTransition
A circle closes over the screen and opens again on the scene behind it.
The centre is a parameter, and that is the whole point of the effect. An iris that can only
close on the middle of the screen is a decoration; one that closes on where the character is
standing is the shot that ends a level, and it is what this has been used for since it was done
with a piece of card in front of a lens.
The circle is corrected for the shape of the window, so it stays a circle on a wide screen instead
of becoming an ellipse, and it opens far enough past the furthest corner that "open" really means
nothing covered, wherever the centre is.
Parameters
durationnumberHow long the whole thing takes in milliseconds, closing and opening together.
colorTColorWhat the outside of the circle is painted. Black unless you say otherwise.
centre{ x, y }Where it closes on, in screen fractions: { x: 0, y: 0 } is the top left corner
and { x: 1, y: 1 } the bottom right. The middle unless you say otherwise.
View source · transition/builtin/iris.ts:30#parseSceneDocfunctionnacatamalon
parseSceneDoc(value: unknown, src: string) => TSceneDoc
Reads a scene document, whatever state it is in.
Total and never throws. A field that is missing, of the wrong shape or plain nonsense falls
back to its default and the scene still opens. A scene is written by tools and edited by hand, so
treating it as a promise would mean one typo costs you the level.
What it does say out loud, once per document rather than once per field:
- a file that is not a scene of this engine, which comes back empty, because carrying on would
mean quietly presenting somebody else's file as an empty level;
- one written by a newer version, which is read anyway: most of it is still readable, and what
is not is kept rather than lost;
- components and assets this version does not know, which are counted and named.
Nothing is deleted on the way in. An unknown component is kept beside its box and an unknown field
is kept on it, so a document opened here and written back out again is the document that came in.
Parameters
valueunknownWhatever JSON.parse gave back.
srcstringWhere it came from, named in every warning. A game opens a dozen scenes and they all
fail the same way; a message that does not say which one costs you the afternoon.
View source · scene/document/parse_scene_doc.ts:835#pixelatefunctionnacatamalon
pixelate(duration: number, color: TColor, minBlocks: number) => TTransition
The picture falls apart into bigger and bigger blocks, then into flat colour, then back.
The only one of the four that reads the frame again rather than painting over what it was
handed: it snaps the point it reads to a grid, so one block's worth of screen all comes back the
same colour.
Two numbers here are not arbitrary and both were arrived at by looking at it:
- The block size is what moves in a straight line, not the number of blocks. Size is one over
count, so a straight line in size is a doubling of the count each step, which is what reads as
the picture breaking down. Moving the count instead crawls for most of the transition and then
jumps at the end.
- The colour comes in on
amount * amount, so the blocks are seen for a good while before the
colour swallows them. Bringing it in straight hides the effect under the paint.
At rest it snaps to the centre of each pixel, which reads back exactly what was there: with
nothing to cover it changes nothing.
Parameters
durationnumberHow long the whole thing takes in milliseconds, breaking down and coming back.
colorTColorWhat it flattens into once the blocks are as big as they get.
minBlocksnumberHow many blocks across the short side of the screen at the worst of it. Six.
View source · transition/builtin/pixelate.ts:35#SCENE_FORMATvariablenacatamalon
SCENE_FORMAT: 'nacatamalon-scene'
What this format is called on disk, so a file can say what it is.
It is checked on the way in, and a file that does not say this is refused rather than half read.
That sounds obvious and it is the thing the draft this engine replaces got wrong: its scene
documents carried no discriminant at all, so a .scene.json could not be told apart from any
other JSON by looking at it, and the only way to know was where it was found.
View source · scene/document/types/t_scene_doc.ts:17#SCENE_VERSIONvariablenacatamalon
SCENE_VERSION: 1
The version of this format, raised when a document written today would be misread by an older
engine.
It is read, not merely written. A document claiming a higher number is loaded anyway and
warned about once, because refusing outright helps nobody: most of a newer document is still
readable, and what is not lands in extra rather than being lost.
View source · scene/document/types/t_scene_doc.ts:31#sceneFromDocfunctionnacatamalon
sceneFromDoc(doc: TSceneDoc, src: string, options: TSceneFromDocOptions) => TSceneFn
Turns a scene document into an ordinary scene function.
That is the whole design, and it is what keeps this to one code path. A scene from a file is
handed to createGame like any other, and from then on change, a transition, a pause and a
destroy all work on it without knowing where it came from. The body it
returns calls the same createSprite, createText and createMesh a person would have written,
so the format cannot express anything the engine's own API cannot build. A second path that
assembled records by hand would be a second path free to drift from the first.
Nothing is thrown. A missing asset costs you that asset and not the scene, because a level that
refused to open over one broken path is a level nobody can fix.
Parameters
docTSceneDocThe document, as parseSceneDoc gave it back.
srcstringWhere it came from, named in any warning it causes later.
optionsTSceneFromDocOptionsHow its behaviours are treated: see TSceneFromDocOptions.
Example
const response = await fetch('/scenes/level1.scene.json');
const doc = parseSceneDoc(await response.json(), '/scenes/level1.scene.json');
createGame('#app', { width: 320, height: 240 })({ Level1: sceneFromDoc(doc) });
View source · scene/document/scene_from_doc.ts:676#useScenefunctionhooknacatamalon
useScene() => TSceneHandle
Returns the controls for the game's scenes: launch, stop, change, pause, resume and
isPaused.
Call it in the scene body, keep the handle, and use it later from useUpdate: it holds the game
and the scene it was called in, so it keeps working after the body is over.
Example
export const Intro: TSceneFn = () => {
const scene = useScene();
let elapsed = 0;
useUpdate((dt) => {
elapsed += dt;
if (elapsed > 3) scene.change('Level');
});
return createScene();
};
View source · hooks/scene/use_scene.ts:98#wipefunctionnacatamalon
wipe(duration: number, direction: TWipeDirection, color: TColor) => TTransition
A straight edge crosses the screen, and crosses it again the same way to uncover.
The direction travels to the card as a vector, not as the word it was written with. A shader
that branched on a name would need one branch per direction and a fifth for anything mistyped;
projected onto an axis, all four are one line of arithmetic and a diagonal would cost nothing.
This is the one built-in that does not ask coverAmount(). It reads progress and phase
itself and flips which side of the edge is covered, so the edge goes on travelling the same way in
both halves. Taking the leftover of progress instead would bring the edge back the way it came.
Parameters
durationnumberHow long the whole thing takes in milliseconds, covering and uncovering together.
directionTWipeDirectionWhich way the edge travels. To the right unless you say otherwise.
colorTColorWhat the covered side is painted. Black unless you say otherwise.
View source · transition/builtin/wipe.ts:42#TAssetEntrytypenacatamalon
TAssetEntry: { type, key, source } | { type, key, src } | { type, key, json, atlas } | { type, key, src } | { type, key, src } | { type, key, src } | { type, key, src } | { type, key, src }
One thing the scene needs loaded before it can be built, named by the key everything in the
document refers to it by.
The manifest exists so the loading can start before the tree does. Every loader in this engine
registers its slot the moment it is asked and fills it in later, so walking this list first means
that by the time a sprite says "my texture is hero", hero is already there, possibly still
arriving. Without it, the first frame of a scene would be the frame that discovers what it needs.
A file is named, never inlined. The pixels of an image, the text of a shader, the cells of a
map and the numbers of an effect all stay in their own file. A document that carried them would be
a document that goes stale the moment somebody edits the file, and a diff nobody can read.
key is the name and src is where it is. They are usually the same string, and they are two
fields because they are two different jobs: one is what the scene calls it, the other is where it
is fetched from. Two scenes naming one file the same thing share one fetch.
View source · scene/document/types/t_asset_entry.ts:26#TAudioListenerComponenttypenacatamalon
The game's ears, on this object rather than on the camera.
Properties
type'audio-listener'idstringenabledoptionalbooleanAbsent for on.
View source · scene/document/types/t_component_doc.ts:657#TBoxNodetypenacatamalon
One box, written down: an identity, a place, what it is, and what is under it.
Properties
idstringStable for the life of the box, and the only thing anything outside the engine can address it
by. A tool that remembers a selection, an undo step or a link between two boxes remembers
this, so regenerating it on load would quietly break every one of them.
namestringWhat it is called where it was placed. Not unique: two enemies can share it.
transformTTransform3d | nullWhere it is, which moves everything under it.
null means it is not anywhere in particular, which is what an empty box used only to
group things should cost: nothing. Writing an identity transform instead would make every
grouping box pay for a placement it does not have and cannot use.
componentsTComponentDoc[]What this box is: its picture, its shape, its camera, its lamp.
childrenTBoxNode[]The boxes placed relative to it. A child is another object, never a part of this one.
dataoptionalTDataEntry[]The state the box asked for with useData, in the order it asked for it.
Absent when there is none, which is most boxes: a data: [] on every node in a level would
be noise in the file and one more thing to read past.
visibleoptionalbooleanAbsent means drawn. Only ever written when it is false.
screenSpaceoptionalbooleanAbsent means it lives in the world. Only ever written when it is true.
View source · scene/document/types/t_scene_doc.ts:57#TCamera2dComponenttypenacatamalon
The camera the flat half of a scene is seen through.
This one carries its own placement, and it is the exception to the rule above. A camera is
what everything else is measured against, so nothing above it in the tree moves it; putting its
position on its box would claim otherwise, and the claim would come true the moment somebody gave
that box a parent with a transform of its own.
Properties
type'camera2d'idstringtransform{ x, y, rotation }zoomnumber
View source · scene/document/types/t_component_doc.ts:419#TCamera3dComponenttypenacatamalon
The camera the three-dimensional half is seen through. Carries its own placement, for the reason
given on its flat twin.
Properties
type'camera3d'idstringprojection'perspective' | 'orthographic'fovnumbernearnumberfarnumberzoomnumber
View source · scene/document/types/t_component_doc.ts:434#TComponentDoctypenacatamalon
TComponentDoc: TSpriteComponent | TTextComponent | TMeshComponent | TTilemapComponent | TParticlesComponent | TParticles3dComponent | TCamera2dComponent | TCamera3dComponent | TFogComponent | TLightComponent | TScriptComponent | TSoundComponent | TMusicComponent | TAudioListenerComponent | TStoreComponent | TPhysicsBody2dComponent | TPhysicsBody3dComponent | TPhysicsWorld2dComponent | TPhysicsWorld3dComponent | TParticleCollider2dComponent | TParticleCollider3dComponent | TSpriteTextureComponent
One thing a box is: its picture, its shape, its camera, its lamp.
Three rules hold across every member here, and each one is a format migration avoided:
- A drawable carries no placement. Where a picture is belongs to its box, so a sprite and the
shadow under it move together by saying it once. A camera and a lamp are the exception and for
one reason: composition deliberately does not reach them. Nothing moves a camera, because it is
what everything else is measured against; and a lamp is moved but never turned, so that a
lantern held by someone who spins does not sweep the room. What the tree cannot decide, the
component has to carry.
- A file is named, never inlined. Shader source, atlas tables, map cells and effect numbers
all stay in their own file and appear here as an asset key.
- A field left out is a field at its default. There is no second convention:
visible absent
means visible and flipX absent means not flipped, because those are what they start at.
The list is uniform even though the runtime keeps typed slots for a camera and a light. A second
light on one box is not an error anywhere in the engine (the last one wins, quietly), and a format
that could not write that would be a format that cannot describe what the engine allows.
View source · scene/document/types/t_component_doc.ts:113#TDataEntrytypenacatamalon
One piece of state a box asked for with useData, written down.
Matched by position, which is the whole design and its whole risk. There is no name to match
on because useData does not take one: the first useData in a body is the first entry here,
the second is the second. Reading a scene back therefore assumes the body still asks for them in
the same order, and adding one in the middle of a body that has already been saved hands the old
values to the wrong places.
View source · scene/document/types/t_scene_doc.ts:46#TFogComponenttypenacatamalon
The fog a scene is seen through. It belongs to the view the way the camera does, so it is written
on the box the camera is: the scene's root, or a box drawn into a picture.
Properties
type'fog'idstringnearnumberfarnumberenabledoptionalbooleanLeft out when on, which is the default.
View source · scene/document/types/t_component_doc.ts:453#TLightComponenttypenacatamalon
A lamp.
Its own placement, and five fields of it rather than nine. A lamp is moved by the tree and
never turned by it, so where it ends up is its own place composed with its box's, while which way
it faces stays exactly what is written here. A lamp has no scale and no roll, so writing those
would be writing down three numbers that are structurally always one and one that is always zero.
An ambient light is the whole room at once: no place, no direction, no reach.
Properties
type'light'idstringkind'directional' | 'point' | 'spot' | 'ambient'intensitynumbertransformoptional{ x, y, z, rotationX, rotationY }Where it is and which way it shines. Not on an ambient one, which is everywhere at once.
ambientoptionalnumberHow much of this light reaches a surface facing away from it. Not on an ambient one.
rangeoptionalnumberHow far it reaches. Point and spot only.
angleoptionalnumberHow wide the cone is, and how soft its edge. Spot only.
penumbraoptionalnumbercastShadowoptionalbooleanWhether the scene's shadows are drawn from this one. Left out is no.
Written down because it is a decision somebody took, and because it is the kind of decision
that disappears quietly: a light that came back without it still lights the scene exactly as
before, and the only thing missing is every shadow in the level.
shadowBiasoptionalnumberHow far the shadow test is pushed off the surface, against a surface striping itself.
shadowStrengthoptionalnumberHow dark what the light cannot see goes, 0 to 1.
shadowAreaoptionalnumberHow far the shadows reach in front of the camera. Directional only.
shadowDistanceoptionalnumberHow far back the light stands to look at that square. Directional only.
View source · scene/document/types/t_component_doc.ts:479#TMaterialDoctypenacatamalon
What a material is, written down.
Two ways in, and the first is the one to use: effect names a .wgsl file, and the hooks
below are left empty. The source itself is only written when there is no file to name, which
happens for a material built in code with its shader typed at the call site.
That preference is the same one a script gets: the file is the truth, and a copy of its contents
living in a scene is a copy that goes stale the moment somebody edits the file. A document that
carried compiled shader source would be as wrong as one carrying a compiled script.
There is no signature here. Which kind each knob is, is worked out from the values themselves on
the way back in, so writing it down would be saying the same thing twice, and two statements of
one fact are two statements that can disagree.
Properties
namestring | nulleffectstring | nullThe .wgsl it came from, by asset key. When this is set, the four hooks below are null.
fragmentstring | nullfragmentGlslstring | nullvertexstring | nullvertexGlslstring | nulluniformsTUniformValues | nullWhat its knobs are set to. null when it has none.
View source · scene/document/types/t_component_doc.ts:32#TMeshComponenttypenacatamalon
A shape in three dimensions.
There is no skeleton here. A rig belongs to the model file it was read out of, and it is adopted
from there when the file lands: writing it into a scene would be writing down a copy of something
the artist owns, in a place they will never look for it.
Properties
type'mesh'idstringtransformoptionalTTransform3dWhere it sits on its box, in three dimensions, left out when that is the box's own origin.
geometrystringThe shape, by asset key.
visibleoptionalbooleanzIndexoptionalnumbercastShadowoptionalbooleanWhether it is drawn into the shadow map. Left out casts, which is what nearly everything
should do; false is the floor, a decal, a blob shadow's own disc.
Only false is ever written, by the rule this whole format keeps: a field that is absent is
its default, so writing true would record a decision nobody took.
View source · scene/document/types/t_component_doc.ts:254#TMeshMaterialDoctypenacatamalon
TMeshMaterialDoc: TMaterialDoc & { texture, tint, emissive, specular, shininess, alpha, smooth, wrap, vertexSnap, affine }
A model's material, which is a material plus a surface: what the light finds when it gets
there.
A sprite's has none of this, and that asymmetry is deliberate rather than an omission. In the
plane, the colour, the sheet and how it is read belong to the object: two sprites of one character
tinted differently are two sprites, not two materials. In three dimensions they belong to the
surface, because that is what a light is asking about.
View source · scene/document/types/t_component_doc.ts:61#TMusicComponenttypenacatamalon
A piece of music in layers this object carries: the same tune in several files, played together,
each at its own volume.
Written like a sound: the files by asset key and how loud each layer is meant to be, never how far
into the tune it was.
Properties
type'music'idstringlayers{ name, audio, volume }[]In the order they were given. A layer's name is what the game moves it by.
volumeoptionalnumberchanneloptionalstringAbsent for 'music'.
autoplayoptionalboolean
View source · scene/document/types/t_component_doc.ts:635#TParticleCollider2dComponenttypenacatamalon
Something solid to particles in a flat scene. Its shape is measured on its object, which places it.
enabled is written only when it is off, the way every field at its default is left out.
Properties
type'particle-collider-2d'idstringenabledoptionalboolean
View source · scene/document/types/t_component_doc.ts:761#TParticleCollider3dComponenttypenacatamalon
Something solid to particles in a scene in three dimensions. The same as the flat one.
Properties
type'particle-collider-3d'idstringenabledoptionalboolean
View source · scene/document/types/t_component_doc.ts:775#TParticles3dComponenttypenacatamalon
TParticles3dComponent: Omit<TParticlesComponent, 'type' | 'transform'> & { type, transform }
An emitter in three dimensions: the flat one's fields, placed in space.
Its own type rather than a flag on the flat one, for the reason the two physics bodies are apart:
the file it names is a different kind of thing, and a component that could name either would make
pointing it at the wrong one impossible to report.
View source · scene/document/types/t_component_doc.ts:399#TParticlesComponenttypenacatamalon
An emitter.
Nothing about how the effect behaves is here: the rate, the lifetimes, the curves and the
shape of the emission are all in the .particles file, which is what makes three torches one
document and one picture. What a scene decides is where it is, what colour it is laid in, whether
it starts lit, and how this one differs in size or speed from the file's own.
emitting and paused are deliberately absent. They are what the effect is doing right now, and
a document records what was authored: autoplay is the authored intent, and a scene saved while
an explosion happened to be mid-burst must not come back permanently mid-burst.
Properties
type'particles'idstringtransformoptionalTTransform2dWhere the emitter sits on its box, left out when that is the box's own corner.
namestringeffectstringThe effect, by asset key.
alphanumberautoplaybooleanWhether it starts lit. The authored intent, never what it is doing at the moment of saving.
seednumber | nullFixed, so the same cloud comes back every time. null takes it from the clock.
smoothoptionalbooleanvisibleoptionalbooleanzIndexoptionalnumber
View source · scene/document/types/t_component_doc.ts:360#TPhysicsBody2dComponenttypenacatamalon
TPhysicsBody2dComponent: { type } & Omit<TPhysicsBody2d, '_type'>
A flat collider, written down: what this object is made of, physically.
A component and not a child object, because it is something the object is, the same as its
picture and its sound. Where it is comes from the object, by the standing rule that a component
carries no placement of its own, which is also why moving the object moves its collider.
The engine can keep this, write it and read it back while being completely unable to move it: a
scene with physics opens and saves losslessly with nothing installed to simulate, and a tool can
draw a collider's outline without loading a physics engine.
View source · scene/document/types/t_component_doc.ts:714#TPhysicsBody3dComponenttypenacatamalon
TPhysicsBody3dComponent: { type } & Omit<TPhysicsBody3d, '_type'>
A collider in three dimensions, written down: the sibling of TPhysicsBody2dComponent,
differing only in the shapes it can name (a sphere is not a circle).
View source · scene/document/types/t_component_doc.ts:724#TPhysicsWorld2dComponenttypenacatamalon
TPhysicsWorld2dComponent: { type } & Omit<TPhysicsWorld2d, '_type'>
The flat simulation's settings, written down. Lives on the scene's root: gravity belongs to
the scene rather than to anything in it, and the root is an object like any other, so it needs no
new home in the file.
Only the root's is read, the same way only one camera is.
View source · scene/document/types/t_component_doc.ts:737#TPhysicsWorld3dComponenttypenacatamalon
TPhysicsWorld3dComponent: { type } & Omit<TPhysicsWorld3d, '_type'>
The simulation's settings in three dimensions, written down. See
TPhysicsWorld2dComponent for why it lives on the root, and TPhysicsWorld2d for
why its gravity is in metres with +y up while the flat one is in pixels with +y down.
View source · scene/document/types/t_component_doc.ts:748#TScenetypenacatamalon
What a scene body returns (createScene): the nodes that hang from its root.
Properties
childrentypeOperator
View source · scene/types/t_scene.ts:10#TSceneChangeOptionstypenacatamalon
What useScene().change accepts beyond the name.
One field, and leaving it out is the hard cut change has always done.
Properties
transitionoptionalTTransition
View source · transition/types/t_transition.ts:76#TSceneDoctypenacatamalon
One scene, written down.
The unit is the scene and never the game. What belongs to the whole game (the size of the
window, its background, the input map, the look of the screen) is the project's, because "this
game looks like a Mega Drive" is not a fact about whichever room happens to be open.
The three fields, in the order they are read:
format and version say what this is, so a file that is not one of these is refused instead
of half read.
assets is everything the scene needs fetched, named. It is walked first, before a single
box is built, so that by the time something says "my texture is hero" the slot for hero
already exists, possibly still arriving.
root is the tree.
Properties
formatqueryversionnumbernamestringWhat the scene is called, and what it is started by.
Here rather than smuggled in as the root box's name, which is where the draft this replaces
kept it. A scene in this engine is named by the key it was registered under, and
createScene() deliberately takes no name at all, so without this field a document would
have no way to say what it is and would only be identifiable by its file name.
assetsTAssetEntry[]Everything the scene needs fetched, by the key the tree refers to it by.
Example
{
"format": "nacatamalon-scene",
"version": 1,
"name": "Level1",
"assets": [{ "type": "texture", "key": "hero", "src": "/assets/hero.png" }],
"root": {
"id": "root", "name": "Level1", "transform": null, "components": [],
"children": [{
"id": "player", "name": "Player",
"transform": { "x": 40, "y": 120, "z": 0, "rotation": 0, "rotationX": 0, "rotationY": 0, "scaleX": 1, "scaleY": 1, "scaleZ": 1 },
"components": [{ "type": "sprite", "id": "art", "texture": "hero", "tint": { "r": 1, "g": 1, "b": 1, "a": 1 } }],
"children": []
}]
}
}
View source · scene/document/types/t_scene_doc.ts:154#TSceneFntypenacatamalon
TSceneFn: () => TScene
A scene: a function that runs once, when the scene starts, and returns createScene(). What it
makes and asks for in between (sprites, useUpdate, useKeyboard...) is what the scene has.
View source · scene/types/t_scene_fn.ts:11#TSceneFromDocOptionstypenacatamalon
What sceneFromDoc can be told besides the document.
Properties
scriptsoptional'run' | 'attach'Whether the objects' behaviours run, or are only recorded as attached.
'run' is the default and is what a game is. 'attach' is for showing a scene rather than
playing it: each object still carries its behaviours and still saves them, and none of them
is called, so nothing walks away from where it was put. A behaviour that asked to be seen
while building runs in both.
View source · scene/document/scene_from_doc.ts:634#TSceneHandletypenacatamalon
What a scene can do with the game's scenes, returned by useScene. Every name is one of the keys
passed to createGame. Where the name is optional, leaving it out means the scene that called
useScene.
Properties
launchstopchangepauseresumeisPaused
View source · hooks/scene/use_scene.ts:16#TScriptComponenttypenacatamalon
A behaviour this object carries: which one, and what it was set to.
It holds a name and not code, for the same reason a sprite holds the name of its picture: a
file cannot carry a function, and the behaviour lives in the project's own source where somebody
can read and change it. What is written down is the link.
Properties
type'script'idstringrefstringThe behaviour's name, as registerScript was given it.
propsoptionalTScriptPropsWhat this attachment was set to, which is what turns a fixed behaviour into a configurable
one: two objects can carry the same patrol at different speeds.
Absent when the behaviour declares no settings, rather than an empty object. A behaviour
with nothing to tune is the common case and its entry should stay as narrow as it ever was.
Only the values are written. Which keys exist is the behaviour's to say, and it says so in
code, so a setting it gains later is filled from its own default rather than from here.
View source · scene/document/types/t_component_doc.ts:539#TSoundComponenttypenacatamalon
A sound this object carries.
Unlike everything else on the list there is nothing to look at, and that is exactly why it has
to be written down: a torch that crackles and a level with music are things the objects are,
and a format that could only describe what is drawn would leave half of a game unauthorable.
What is written is how it was asked to play, never what it is playing. A scene saved while
the music was halfway through opens ready to start it, because where a voice had got to is a
fact about one run and a level is not. The knobs go the other way: a volume an options screen
turned down is written down turned down, the same as a sprite is written with the tint it has
rather than the one it was made with.
Properties
type'sound'idstringaudiostringThe clip, by asset key.
volumeoptionalnumberloopoptionalbooleanrateoptionalnumberHow fast it plays, which also changes the pitch.
channeloptionalstringWhich volume group it belongs to, so an options screen can move all the music at once
without knowing what is in the level.
Core's format has no such field and it should: which group a sound belongs to is a decision
somebody took while authoring, not a detail of playing it back.
autoplayoptionalbooleanStarts as soon as the file is there, with nothing having to ask.
spatialoptionalbooleanComes from a side and fades with distance, following the object it hangs off.
refDistanceoptionalnumberHow far it is heard at full volume. Placed sounds only. In the scene's measure (pixels, or
units under a perspective camera), and absent to take that measure's own default.
maxDistanceoptionalnumberPast this distance it is not heard. Placed sounds only, and absent for the default.
coneoptionalTSoundConeLouder ahead of the object than behind it. Placed sounds only.
zoneoptionalTSoundZoneFills an area around the object instead of coming from a point. Wins over spatial.
View source · scene/document/types/t_component_doc.ts:576#TSpriteAnimationDoctypenacatamalon
A sprite's animation as a document keeps it: which run of its sheet it starts on and how fast.
The runs themselves are in the .atlas file and are never copied here.
Properties
autoplaystring | nullThe run to start on, or null to rest on the sprite's frame.
speednumberHow fast, where 1 is the run's own fps.
View source · scene/document/types/t_component_doc.ts:203#TSpriteComponenttypenacatamalon
A flat picture.
width and height may be left out, and leaving them out is not the same as writing the numbers
the texture happens to have: it means take the texture's size, decided when the texture lands.
Writing today's numbers down would freeze a sprite at the size of the art it had when it was saved.
Properties
type'sprite'idstringtransformoptionalTTransform2dWhere it sits on its box, left out when that is the box's own corner.
A box places the whole object and a drawable places itself inside it, and both are real: a
box can carry a picture and the shadow under it, each at its own offset. It is also what
createSprite({ transform }) writes into, which is how nearly every scene in this engine is
written, so without this field a room saved from a running game comes back with everything
piled at the origin, having failed at nothing.
texturestring | nullThe sheet, by asset key.
atlasoptionalstringA sheet cut into a grid, by asset key, and which cell of it to show.
frameoptionalnumberanimationoptionalTSpriteAnimationDocThe run of atlas it plays from the start, if any. Needs atlas: the runs are the sheet's.
widthoptionalnumberheightoptionalnumberanchoroptional{ x, y }uvOffsetoptional{ x, y }Which part of the sheet to show, when it is not cut by an atlas.
uvScaleoptional{ x, y }flipXoptionalbooleanflipYoptionalbooleansmoothoptionalbooleanvisibleoptionalbooleanzIndexoptionalnumbermaterialoptionalTMaterialDocuniformsoptionalTUniformValuesThis sprite's own values for that material's knobs, laid over the material's own.
View source · scene/document/types/t_component_doc.ts:148#TSpriteTextureComponenttypenacatamalon
A picture the object and everything under it is drawn into, instead of the screen.
The picture is named by key, and that is how a model shows it: its surface asks for key the
way it asks for a loaded image. It is made before any object in the scene, so a model that comes
earlier in the file than the screen it shows still finds it.
sees is written only when it is 'scene', the way every field at its default is left out.
Properties
type'sprite-texture'idstringkeystringwidthnumberheightnumberseesoptional'scene'
View source · scene/document/types/t_component_doc.ts:795#TStoreComponenttypenacatamalon
An object saying that it uses one of the game's stores.
The important word is uses. A store is not in the object and cannot be: it outlives every
scene, and one living inside an object would die with its scene and be two the moment a second
object named it. So this carries a name and nothing else, exactly as a behaviour does, and the
store itself lives in its own file and in the engine's index.
It buys two things an ordinary import cannot. A behaviour attached in an editor can be handed the
state through the scene (storeOf(self)), so a scene put together by hand can wire behaviour to
state with no file to write. And the tree becomes able to answer "what touches this state?",
which nothing else could.
Properties
type'store'idstringWorked out from the object and the name rather than kept, which is the one identity in this
format that is not: a link has exactly one field, so two links to one store on one object
are the same link, and deriving it keeps a file identical through a round trip instead of
issuing a fresh id on every save. The object's own id is part of it so that duplicating an
object does not give two components the same name.
refstringThe store's name: what its file is called, and the key createGameStore was given.
View source · scene/document/types/t_component_doc.ts:683#TTextComponenttypenacatamalon
Words drawn with a bitmap font.
Properties
type'text'idstringtransformoptionalTTransform2dWhere it sits on its box, left out when that is the box's own corner.
textstringfontstringThe font, by asset key.
anchoroptional{ x, y }smoothoptionalbooleanvisibleoptionalbooleanzIndexoptionalnumbermaterialoptionalTMaterialDocuniformsoptionalTUniformValues
View source · scene/document/types/t_component_doc.ts:221#TTilemapComponenttypenacatamalon
A map.
One of these per map, not per layer, and that is what the engine can actually be asked for:
createTilemap reads the file and puts every layer of it on the box in one go. A document with
one entry per layer would describe something no call can make.
The cells are not here either: they live in the .tilemap, which is the file a person edits and a
map editor writes. What a scene decides is where the map sits, how it is tinted, how its sheet is
read, and then whatever it changed about individual layers.
transform is the map's own and is here rather than on the box, for a reason worth knowing: a map
is cached and shared, so its placement is shared too, and two scenes showing one level are
looking at the same object. Writing it as the box's placement would say each scene had its own.
Properties
type'tilemap'idstringmapstringThe map, by asset key.
transformoptionalTTransform2dWhere the map's top-left corner sits. Shared by its layers: they sort apart, not move apart.
tintoptionalTColorMultiplies the colour of every layer.
smoothoptionalbooleanlayersoptionalTTilemapLayerOverride[]What this scene changed about individual layers, by the name the file gave them.
Absent, and absent for a layer, means the layer is exactly as the file has it. So a map
dropped into a scene and left alone is four fields, not one entry per layer of description
repeating what the file already says.
View source · scene/document/types/t_component_doc.ts:298#TTilemapLayerOverridetypenacatamalon
What one scene changed about one layer of a map. Everything optional, because everything left out
is the file's own answer.
Properties
namestringWhich layer, by the name the file gave it.
visibleoptionalbooleanzIndexoptionalnumbermaterialoptionalTMaterialDoc
View source · scene/document/types/t_component_doc.ts:332#TTransitiontypenacatamalon
A way of getting from one scene to another: a full-screen effect plus how long it lasts.
The same shape a post effect has, plus a duration, and that is deliberate. Writing your own
transition should cost exactly what writing your own effect costs, because it is the same hook
over the same finished picture. What the engine adds is not the shader, it is the lifecycle: the
scene coming in exists without being seen or updated, and the swap waits for the screen to be
covered and for its assets to land.
The hook is handed progress, which runs 0 to 1 twice, and phase, which says which of the two
runs it is in: 0 while the screen is being covered, 1 while it is being uncovered. Both halves
count up, never down, because an uncover that counted back down would play a wipe in mirror
image.
Properties
nameoptionalstringWhat a warning about it will call it, and what an editor lists. Never an identity.
durationnumberHow long the whole thing takes, in milliseconds, covering and uncovering together.
So fade(300) covers for 150 and uncovers for 150, and a stopwatch agrees with the number
that was written. Each half still runs progress from 0 to 1 over its own 150.
Zero is allowed and means the screen is covered for exactly as long as the incoming scene
takes to load, which is a loading screen with no animation and a perfectly good thing to ask
for.
fragmentstringfn effect(color: vec4<f32>, uv: vec2<f32>) -> vec4<f32>, and anything it calls.
fragmentGlsloptionalstringThe same hook in GLSL.
Without it there is no transition on WebGL2 and no lifecycle either: the change is made as
the hard cut it was before, with a warning. A cover nobody can draw is worse than no cover,
because the scene coming in would sit held back and invisible for the whole duration and then
appear all at once.
uniformSigoptionalTUniformSignatureWhat kind each one is. Worked out from the values when left out.
Example
const scene = useScene();
scene.change('Level2', { transition: fade(300) });
scene.change('Level2', { transition: wipe(400, 'right') });
View source · transition/types/t_transition.ts:28#TWipeDirectiontypenacatamalon
TWipeDirection: 'left' | 'right' | 'up' | 'down'
Which way the edge travels. uv.y points down, so 'down' is a curtain falling.
View source · transition/builtin/wipe.ts:13