Skip to content

Sandbox

The sandbox example in examples/src/examples/sandbox/ is a first-person walk through one level authored in Blender, SAGE_dev-box.blend. In one scene it puts a physics character controller, a GLB level with colliders, doors and buttons and crates adopted from the scene, a key to carry, a four-floor elevator with a floor-selection panel, spatial and level-scoped sound, entity picking, and two input contexts. Pooling, saves, the state machine behavior, and tag queries are not part of it.

Walk to the button and press E to open the sliding door. The key is on the table in the next room; carrying it unlocks the locked buttons and crates. The elevator's platform button opens a panel to pick a floor, and the wall buttons call it to theirs. The last room is full of pushable props.

How each piece is built

What the sandbox doesHow, on 0.10
The first-person playerPlayerEntity spawns at the player_start marker. FirstPersonBehavior builds Havok's PhysicsCharacterController in onMeshLoaded, takes over the free camera the level declared in YAML, and moves from action:moveForward, action:moveRight, and action:jump events. See Input.
Doors, buttons, crates, the key, the elevator, and the propsEntity markers in Blender, one entities entry per marker value in sandbox.yaml. Each entity adopts its node. See Authoring.
What a button doesAuthored on the node as custom properties, target, prompt, locked, and context, read from this.entity.node.metadata in InteractBehavior.onMeshLoaded the first time the node is adopted.
The interact promptengine.simulation.pickEntityForward(camera, 3) every frame, then hasBehavior(InteractBehavior) or hasBehavior(PickupBehavior) on the hit to read its prompt. See Picking.
Pressing EThe controller emits interact:use targeted at the picked entity. InteractBehavior answers by broadcasting interact:activate with its target node's name; DoorBehavior, CrateBehavior, and ElevatorBehavior each answer when the name is their own node's. See Talking to One Entity.
Unlocking with the keyInteractBehavior declares an unlock operation. The controller calls it through the narrowed ops when the player is carrying the key, which it tracks from pickup:heldChanged.
Door and unlock soundsSoundBehavior, composed onto DoorEntity, ButtonEntity, and CrateEntity. The door's two sounds are spatial, attached to the adopted node; InteractBehavior.unlock plays through this.ops.play('unlock'). See Audio.
The factory ambienceA level-scoped sound in sandbox.yaml, looping on the ambient channel. The HUD's mute button toggles the channel. See Level Sounds.
The elevator movingElevatorBehavior switches its adopted node's physics body to animated in onMeshLoaded and drives it with setTargetTransform every fixed step, writing travel into state.
The player riding the elevatorThe elevator broadcasts elevator:moved with the distance it moved that step; FirstPersonBehavior subscribes and carries the capsule the same distance.
The floor panel and its input contextpanelOpen is a field in the elevator's state, and ElevatorBehavior broadcasts elevator:panelChanged on every transition. The controller answers that event: elevator_ui active and the pointer released while the panel is open, gameplay active and the pointer locked again when it closes. Activating one context deactivates the other, and Escape is bound in elevator_ui only. See Action contexts.
Collider visualizationF3, or the HUD button, calls engine.debug.colliders.showColliderForNode for every node with a physics body. See Collider Debugging.
The controller hearing actionsSandboxController registers on engine.simulation.bus for action:interact and action:toggleColliders, and for the level's own elevator:panelChanged, elevator:arrived, and pickup:heldChanged, as a subscriber outside the entity system. It pushes the HUD's state from those handlers instead of rebuilding it every frame. See Events.

The level config

yaml
name: Sandbox
scene: /assets/sandbox/SAGE_dev-box.glb
physics: true

cameras:
    PlayerCamera:
        type: free
        attachControl: true
        minZ: 0.1

spawns:
    player_start:
        entity: PlayerEntity

entities:
    door:
        entity: DoorEntity
    button:
        entity: ButtonEntity
    elevator_button:
        entity: ButtonEntity
    crate:
        entity: CrateEntity
    key:
        entity: KeyEntity
    elevator:
        entity: ElevatorEntity
    prop:
        entity: PropEntity

postProcessing:
    renderer: frameGraph
    bloom:
        weight: 0.3
        threshold: 0.8
    tonemap:
        operator: aces

sounds:
    ambient:
        url: /assets/sandbox/ambient-factory.mp3
        channel: ambient
        loop: true
        autoplay: true
        volume: 0.04

The colliders come from the collider custom property on the Blender nodes, so the example registers the built-in property handlers before loading:

typescript
registerAllPropertyHandlers(engine.managers.levelManager);

Next steps

Released under the MIT License.