Project Mammut Wiki
Project Mammut is currently in open beta. The UI is being updated a lot during this time, and you may run into bugs. If you'd like to help develop the engine or suggest features, come find us on Bluesky or Discord.

Scene Script

Written 9 Aug 2026 · Updated 15 Nov 2026 · Requires Mammut 0.6.2+

Every scene has its own script, which runs while the scene is shown. Use it to react to buttons and other inputs, change what's on screen, and start chapters. Scripts are written in TypeScript or JavaScript.

This page covers how a scene script is put together and when each part runs. For everything the engine gives you to call, see Coding.

Editing a scene's script

Open the scene in the Scene Editor and click the code button in its title bar to switch to the script. Click it again to go back to the scene.

New scenes start with an empty onStart() and onExit().

  • To change the language, select the scene root and set Script language in the inspector's Scene section.
  • To use the same script in several scenes, move it into a Shared Script.

How a script is written

A scene script is a list of members, separated by commas. Members can be lifecycle hooks, your own methods and your own values:

// A value, kept on this.retries
retries: 0,

// A lifecycle hook
onStart() {
  this.dom.title.textContent = "Welcome";
},

// Your own method
onPlayPressed() {
  this.retries++;
  SceneManager.SwitchScene("level_1");
},
  • Every member ends with a comma, including methods.
  • Inside any member, this is the scene. this.dom holds the scene's nodes, and this.vars holds its scene variables.
  • Don't write members as arrow functions (onPlayPressed: () => {...}), because this isn't the scene inside them.
  • Scene scripts can't use import or export. Put shared code in Project Scripts instead.

See Script Types for how each of these is typed.

Lifecycle hooks

Hook When it runs
onEnter() The scene has been built and is about to start. this.dom is ready, but the sub-scenes inside it haven't started yet.
onStart() After onEnter(), once every sub-scene inside this one has run its own onEnter() and onStart().
onExit() The scene is being removed, because the game switched to another scene, the scene was detached, or the scene containing it was removed.

For a menu scene with a sub-scene card inside it, the hooks run in this order:

  1. menu onEnter()
  2. card onEnter()
  3. card onStart()
  4. menu onStart()

When the game leaves menu, its onExit() runs, then card's.

  • All three hooks are optional.
  • A hook can be async, but nothing waits for it. onStart() runs as soon as onEnter() reaches its first await.
  • The scene's music and ambient sound, set in its inspector, start before onEnter().
  • The script is only loaded once. Values you keep on this are still there the next time the scene is shown, so reset them in onEnter() if you need a fresh start (see Variable Scope).

Calling your methods from the scene

Many inspector fields take the name of a method in the scene's script, which is called when something happens to that node:

Field Your method receives
Button Action set to Event Nothing
A node's Events list (see Shared Node Properties) The browser event
Slider On Change and On Change End The slider's value, as a number
Checkbox On Change true or false
Text Input On Change The text
Sprite Loop On Event The event's name, its data and the full event
// Slider's On Change is "onVolumeChanged"
onVolumeChanged(value: number) {
  AudioManager.SetMasterVolume(value / 100);
},

You can add your own arguments in brackets, like onChoose(2) or onChoose("left"). In an Events list they come before the browser event. Everywhere else they come after the value.

If the name doesn't match a method in the script, nothing happens and no error is shown, so check the spelling. Fields with a + button next to them add the method to your script for you.

Calling your methods from a chapter

A tag like [OpenShop(sword, price=10)] in a chapter calls every handler registered for OpenShop when the text reaches it. Register your method in onStart():

onStart() {
  Revealer.Register("OpenShop", this.openShop, this);
},

openShop(event: RevealerEvent) {
  const item = event.args[0]; // "sword"
  const price = Number(event.kwargs.price); // arguments arrive as text
},

Passing this as the last argument makes the scene this inside your method, and removes the handler when the scene exits. If your method returns a promise, the text waits until it finishes. See Coding for the built-in events.

Working with other scenes

Every loaded scene can be reached by name, so one scene can call another's methods:

SceneManager.GetScene("hud")?.showMessage("Saved");
  • this.attachedScenes lists the sub-scenes inside this scene.
  • A Scene node with an id is in this.dom, as the element the sub-scene is drawn into.
  • SceneManager.SwitchScene("level_1") moves to another scene, which runs this scene's onExit().

Cleaning up

Handlers you register with this as the owner are removed for you when the scene exits. This works for Store, Revealer, InputManager, AudioManager and Logger.

Listeners on the scene's own nodes go away with the scene. Anything else keeps running after the scene has gone, so stop it in onExit():

timer: 0,

onStart() {
  this.timer = setInterval(() => this.tick(), 1000);
},

onExit() {
  clearInterval(this.timer);
},

tick() {
  log("tick");
},

That includes timers, requestAnimationFrame loops, and listeners you add to window or document.