Skip to content

Getting Started

Scenery is a visual editor for spatial experiences. You design scenes, place objects, configure materials, and wire up behaviors – all without code.

Scripts handle what the visual editor doesn't: state, logic, external data, real-time connections, procedural behavior.

How scripts work

Scripts attach to events. When something happens – a tap, an object appearing, a frame rendering – Scenery can run your code.

To add a script:

  1. Create an event (on an object or the scene)
  2. Add a "Run Script" action
  3. Write your code

When the event fires, your script runs with event data in scriptContext.sourceEvent:

javascript
// Tap event – scale up the tapped object
scriptContext.sourceEvent.objectEntity.animateTo(
    { scale: Vector3(1.2, 1.2, 1.2) },
    0.2
);

Different events provide different data – position, deltaTime, distance, etc.

TIP

Use console.log(scriptContext.sourceEvent) to inspect what data is available for any event.

Action order matters

Actions run sequentially by default. If your script uses scene.findEntity to find an object, that object must already be in the scene – so place the "Add to Scene" action before "Run Script":

Add to Scene (MyBox) → Run Script (finds MyBox)

If the script runs first, the object won't exist yet and findEntity returns null. Alternatively, attach your script to the object's On Will Appear or On Did Appear event – then the object is guaranteed to be available.

Example: Continuous rotation

Attach to "On Render":

javascript
// Replace with your object's ID or use { name: "..." }
var cube = scene.findEntity("YOUR_OBJECT_ID");
cube.representation.rotation = cube.representation.rotation.multiply(
    Rotation(0, scriptContext.sourceEvent.deltaTime, 0)
);

Finding objects

javascript
// By name (set in Identity panel)
var box = scene.findEntity({ name: "MyBox" });

// By ID
var box = scene.findEntity("ABC123");

// Access the representation (visual content)
box.representation.position = Vector3(0, 1, 0);
box.representation.opacity = 0.5;

To get an ID: right-click an object or representation in the editor → Copy Development ID.

Entity vs Representation

An entity is the top-level object (the anchor container you see in the scene list). A representation is the visual content inside it (model, shape, image, audio, etc.). Use entity for finding and referencing objects, use entity.representation for visual changes (position, rotation, scale, opacity).

scene.findEntity finds top-level objects only – not nested representations. If your object contains multiple representations (e.g. a model and an audio source), find the parent entity first, then navigate:

javascript
var entity = scene.findEntity({ name: "MyObject" });
var audio = entity.findRepresentation({ name: "BGMusic" });
audio.isEnabled = true;

See Examples → Entity hierarchy for the full pattern.

Rotations

All rotation values are in radians. Use Math.toRadians() and Math.toDegrees() to convert:

javascript
Rotation(0, Math.PI / 2, 0);          // 90° around Y axis
Rotation(Math.toRadians(45), 0, 0);   // 45° around X axis

var degrees = Math.toDegrees(Math.PI); // 180

Other math helpers: Math.lerp(a, b, t), Math.clamp(value, min, max), Math.map(value, inMin, inMax, outMin, outMax), Math.smoothstep(edge0, edge1, x).

Error handling

Script errors are logged to the debug console but don't crash the scene – other scripts and actions continue to run. Use try/catch for operations that may fail:

javascript
try {
    var response = await http.get("https://api.example.com/data");
} catch (error) {
    console.error("Request failed: " + error);
}

Using await

You can use await directly at the top level of your script – the runtime wraps async code for you, so there's no need for an (async function(){…})() wrapper or a helper function.

javascript
var box = await scene.createEntity(createBox(0.3, 0.3, 0.3));

Configuring entities – use the descriptor builder

scene.createEntity(...) takes one descriptor argument. Configure anchor, name, traits, and other settings via the descriptor's chainable builder methods, not as a second options argument.

javascript
// ✅ Correct – chain builder methods on the descriptor
const box = createBox(0.3, 0.3, 0.3)
    .anchor(Anchor.position(0, 0, -1))
    .name("hero-box");
const entity = await scene.createEntity(box);

// ❌ Wrong – second argument throws at call time
await scene.createEntity(box, { anchor: Anchor.position(0, 0, -1) });

Same rule for scene.runAction(action) – single argument. Configure inside the action data, not via a second arg.

Debugging

Tap the terminal button in the bottom-right corner to open the debug console. It shows console.log output and errors from your scripts. You can also type and run scripts directly in the console input field.

javascript
console.log("score:", experience.getVariable("score"));
console.error("something went wrong");

Next steps


Using AI to write scripts

Want to use ChatGPT, Claude, or another LLM to help write scripts? Feed it llms-full.txt – it contains the complete guide and API reference in a single file.