Basic Interactivity
Welcome to Lesson 1.7! In this lesson, you'll learn how to make your VR scenes interactive using event handlers.
Event Handlers Overview
Event handlers respond to user actions and system events:
composition button {
@clickable
onClick: {
// This code runs when clicked
console.log("Button clicked!")
}
}Mouse/Pointer Events
onClick
Triggered when the user clicks or triggers:
composition clickable {
@clickable
geometry: "cube"
color: "#0066ff"
onClick: {
this.color = "#00ff00"
}
}onHoverEnter / onHoverExit
Triggered when pointer enters or leaves:
composition hoverable {
@hoverable
geometry: "sphere"
scale: 1.0
onHoverEnter: {
this.scale = 1.2
this.color = "#ffff00"
}
onHoverExit: {
this.scale = 1.0
this.color = "#ffffff"
}
}onPoint
Triggered while being pointed at:
composition target {
@pointable
geometry: "sphere"
onPoint: {
// Called each frame while pointed at
this.material.emissive = "#ff0000"
}
}VR Grab Events
onGrab / onRelease
Triggered when grabbed or released in VR:
composition grabbableItem {
@grabbable
geometry: "cube"
onGrab: {
console.log("Picked up!")
this.color = "#00ff00"
}
onRelease: {
console.log("Put down!")
this.color = "#ff0000"
}
}Event Data
Events provide context information:
composition detailed {
@grabbable
onGrab(event): {
console.log("Hand:", event.hand) // "left" or "right"
console.log("Position:", event.position) // [x, y, z]
console.log("Controller:", event.controller)
}
}Physics Events
onTriggerEnter / onTriggerExit
Triggered when objects enter/exit a trigger zone:
composition doorZone {
@trigger
geometry: "cube"
scale: [2, 2, 1]
opacity: 0.2
onTriggerEnter(event): {
if (event.object.name === "player") {
door.open()
}
}
onTriggerExit(event): {
door.close()
}
}onCollision
Triggered when physics objects collide:
composition ball {
@physics
@collidable
geometry: "sphere"
onCollision(event): {
console.log("Hit:", event.other.name)
console.log("Force:", event.force)
console.log("Point:", event.contactPoint)
// Play sound based on impact force
if (event.force > 5) {
audio.play("impact_hard.mp3")
} else {
audio.play("impact_soft.mp3")
}
}
}Animation Events
onAnimationStart / onAnimationEnd
Track animation lifecycle:
composition animated {
@animated
animation fadeIn {
property: "opacity"
from: 0
to: 1
duration: 1000
}
onAnimationStart: {
console.log("Animation started")
}
onAnimationEnd: {
console.log("Animation complete")
}
}Lifecycle Events
onCreate
Called when object is created:
composition managed {
onCreate: {
console.log("Object created!")
this.startTime = Date.now()
}
}onUpdate
Called every frame:
composition rotating {
geometry: "cube"
rotationSpeed: 1
onUpdate(deltaTime): {
this.rotation.y += this.rotationSpeed * deltaTime
}
}onDestroy
Called before object is removed:
composition cleanup {
onDestroy: {
console.log("Cleaning up...")
this.saveState()
}
}Modifying Properties
Event handlers can modify any property:
composition interactive {
@clickable
@hoverable
geometry: "cube"
color: "#ffffff"
scale: 1.0
isActive: false
onHoverEnter: {
this.scale = 1.1
this.color = "#ffffaa"
}
onHoverExit: {
this.scale = 1.0
this.color = "#ffffff"
}
onClick: {
this.isActive = !this.isActive
this.color = this.isActive ? "#00ff00" : "#ffffff"
}
}Accessing Other Objects
Reference other objects in the scene:
composition button {
@clickable
geometry: "cube"
color: "#ff0000"
onClick: {
// Reference another object by name
lamp.visible = !lamp.visible
door.rotation.y = 90
}
}
composition lamp {
@glowing
geometry: "sphere"
visible: true
}
composition door {
geometry: "cube"
scale: [1, 2, 0.1]
}Playing Audio
Trigger sounds in event handlers:
composition button {
@clickable
geometry: "cube"
onClick: {
audio.play("click.mp3")
}
}
composition enemy {
@collidable
onCollision: {
audio.play("hit.mp3", {
volume: 0.8,
spatial: true,
position: this.position
})
}
}Conditional Logic
Use if/else in handlers:
composition toggle {
@clickable
state: "off"
onClick: {
if (this.state === "off") {
this.state = "on"
this.color = "#00ff00"
audio.play("on.mp3")
} else {
this.state = "off"
this.color = "#ff0000"
audio.play("off.mp3")
}
}
}Complete Example
Here's a complete interactive scene:
composition "Interactive Room" {
// Light switch
composition lightSwitch {
@clickable
@hoverable
geometry: "cube"
scale: [0.1, 0.15, 0.05]
position: [2, 1.2, 0]
color: "#cccccc"
isOn: false
onHoverEnter: {
this.color = "#ffffff"
}
onHoverExit: {
this.color = "#cccccc"
}
onClick: {
this.isOn = !this.isOn
roomLight.intensity = this.isOn ? 1.0 : 0.1
audio.play("switch.mp3")
}
}
// Room light
composition roomLight {
@glowing {
color: "#ffffee"
distance: 10
}
geometry: "sphere"
scale: 0.1
position: [0, 2.5, 0]
intensity: 0.1
}
// Grabbable ball
composition ball {
@grabbable
@throwable
@physics { mass: 0.3, restitution: 0.9 }
geometry: "sphere"
scale: 0.15
position: [0, 1, -1.5]
color: "#ff6600"
onGrab: {
haptic.feedback("light")
}
onRelease: {
haptic.feedback("medium")
}
onCollision(event): {
if (event.force > 2) {
audio.play("bounce.mp3", { volume: event.force / 10 })
}
}
}
// Target that reacts to ball
composition target {
@collidable
@trigger
geometry: "cylinder"
scale: [0.3, 0.02, 0.3]
position: [0, 0.01, -3]
color: "#ff0000"
onTriggerEnter(event): {
if (event.object.name === "ball") {
this.color = "#00ff00"
audio.play("score.mp3")
score.value += 10
}
}
onTriggerExit: {
this.color = "#ff0000"
}
}
// Score display
composition score {
@billboard
geometry: "plane"
position: [0, 2, -3]
value: 0
}
}Quiz
- What event fires when grabbing an object in VR?
- How do you access another object in an event handler?
- What's the difference between
onTriggerEnterandonCollision? - How do you play a sound in an event handler?
- What event is called every frame?
Answers
onGrab- Reference it by name:
otherObject.property onTriggerEnteris for trigger zones (no physics),onCollisionis for physics collisionsaudio.play("filename.mp3")onUpdate
Estimated time: 30 minutes
Difficulty: ⭐ Beginner
Next: Lesson 1.8 - Templates & Reuse