Appearance
Events
Events trigger scripts. Scenery has two kinds: object events that fire for a specific object, and scene events that fire globally.
Object events
Attach to individual objects. The triggering object is available in scriptContext.sourceEvent.
| Event | Fires when |
|---|---|
| On Will Appear | Object is about to be added to scene |
| On Did Appear | Object has appeared |
| On Removal | Object is removed from scene |
| On Tap Gesture | Object is tapped |
| On Gesture | Object is dragged, rotated, or resized (needs a gesture enabled) |
| On Camera Collision | Camera enters/exits object bounds |
| On Object Collision | Physics collision with another object |
| Distance Field | Camera or target crosses distance threshold |
| Look Direction | User looks at/away from object (or object looks at target) |
| Media Playback | Video/audio begins, reaches timestamp, or ends |
javascript
// On Tap Gesture – scale up the tapped object
scriptContext.sourceEvent.objectEntity.animateTo(
{ scale: Vector3(1.2, 1.2, 1.2) },
0.2
);Scene events
Fire regardless of which object is involved.
| Event | Fires when |
|---|---|
| On Experience Start | Experience begins |
| On Experience Load | Experience finishes loading |
| On Screen Tap | User taps anywhere on screen |
| On Render | Every frame |
| On Schedule | After a delay or at specific time |
| On Variable Change | A variable is modified |
| On Audio Analysis | Audio levels update (ambient or microphone) |
javascript
// On Render – rotate every frame
var cube = scene.findEntity("YOUR_OBJECT_ID");
cube.representation.rotation = cube.representation.rotation.multiply(
Rotation(0, scriptContext.sourceEvent.deltaTime, 0)
);Subscribing in code
You can also subscribe to events programmatically:
javascript
var cube = scene.findEntity("YOUR_OBJECT_ID");
// Object event
cube.on('tap', function(e) {
e.objectEntity.animateTo({ scale: Vector3(1.2, 1.2, 1.2) }, 0.2);
});
// Scene event
scene.on('render', function(e) {
cube.representation.rotation = cube.representation.rotation.multiply(
Rotation(0, e.deltaTime, 0)
);
});
// One-time listener
scene.once('start', function() {
console.log('Experience started');
});Throttling
For expensive operations, throttle the render loop:
javascript
scene.on('render', { throttle: 0.1 }, function(e) {
// Runs every 100ms instead of every frame
});Unsubscribing
javascript
var eventId = cube.on('tap', function() {});
// Later...
cube.off(eventId);objectEntity vs representationEntity
Tap and collision events give you both objectEntity (the top-level container) and representationEntity (the specific visual that was interacted with). Use objectEntity when you need to find sibling representations like audio sources.
Gesture and screen events
Beyond taps, you can react to live manipulation, multi-tap gestures, and raw screen gestures.
Manipulating an object
If an object has a gesture enabled (Drag, Rotate or Resize), on('gesture') fires continuously while the user manipulates it, so you can react to the movement itself. Read e.phase ('began' / 'changed' / 'ended') and, by gesture kind, e.translation / e.rotation / e.scale (world-space):
javascript
box.on('gesture', function(e) {
if (e.phase === 'ended') {
console.log('let go');
}
});doubleTap and longPress are discrete object gestures – these are available in code (not as editor triggers):
javascript
box.on('doubleTap', function() { /* ... */ });
box.on('longPress', { minimumDuration: 0.5 }, function() { /* ... */ });Screen gestures
pan, pinch and rotate fire on any such gesture anywhere on screen, independent of scene objects – the raw input for mechanics like flick-to-launch. Read e.position (normalized) and, by kind, translation / scale / rotation plus velocity:
javascript
// Flick up in the bottom of the screen to launch the ball
scene.on('pan', { area: [0, 0.6, 1, 0.4], phase: 'ended' }, function(e) {
// e.velocity is in screen points/sec – scale it to a world-space push (tune to taste)
var power = Math.max(0, -e.velocity.y) * 0.002;
ball.applyImpulse(new Vector3(0, power, -power * 2), { space: 'world' });
});Outside AR (the desktop preview, the Mac app), a one-finger pan also orbits the camera by default. So if your handler reacts to a pan, that one gesture does two things at once – your mechanic runs and the camera swings. consume prevents that: return true from its predicate and the gesture is handled by your script alone, so the camera ignores it. The predicate runs as the gesture begins, so you can claim only the pans that start on your object and leave the rest for the camera:
javascript
// Claim the pan only when it starts on the ball; every other pan still orbits the camera
scene.on('pan', { consume: function(e) { return e.hits(ball); } }, function(e) { /* ... */ });Omit consume for the smart default – a gesture is claimed automatically when it begins on a scene object. See scene.on for the full payload and consume options.
Screen gestures are for touch screens
pan, pinch and rotate fire on iPhone, iPad and Mac. On Apple Vision Pro input is spatial (you look and pinch), so screen gestures aren't delivered – drive interaction from UI panels and buttons there instead. Gate a portable script with environment.features.has('screenGestures') and provide a fallback.
Event data
How you access event data depends on how your script runs:
Run Script action – use scriptContext.sourceEvent:
javascript
var entity = scriptContext.sourceEvent.objectEntity;
var dt = scriptContext.sourceEvent.deltaTime;Event subscriptions – data is passed to your callback:
javascript
scene.on('render', function(e) {
var dt = e.deltaTime;
});
cube.on('tap', function(e) {
var tapped = e.objectEntity;
});Properties by event type:
| Event | Properties |
|---|---|
render | deltaTime, time |
tap | objectEntity, representationEntity, screenPosition |
gesture | objectEntity, representationEntity, phase, translation/rotation/scale |
doubleTap, longPress | objectEntity, representationEntity |
pan, pinch, rotate | position, beganPosition, phase, translation/scale/rotation, velocity |
screenTap | position |
distance | objectEntity, distance, crossed |
lookAt | objectEntity, isLooking |
cameraCollision | objectEntity, representationEntity |
physicsCollision | objectEntity, representationEntity, position, impulse |
variableChange | variableId, value |
mediaPlayback | objectEntity, status, time, duration |
audioAnalysis | kind, loudness, bass, lowMid, mid, upperMid, presence, brilliance, air, treble |
add, appear, remove | objectEntity, representationEntity |
TIP
Use console.log(scriptContext.sourceEvent) or console.log(e) to inspect all available properties.