Editor basics
Navigate the viewport, select objects, use transform gizmos, and run a scene.
Viewport controls
The editor uses a Three.js viewport with a scene hierarchy, inspectors, and floating viewport tools. Select an object in the viewport or hierarchy to edit its transform and type-specific settings.
Camera movement uses FlyControls and is always available:
| Shortcut | Action |
|---|---|
W A S D | Move horizontally |
Q / E | Descend / elevate |
| Arrow keys | Look around |
| Drag in the viewport | Look around (hold Shift to draw a selection marquee instead) |
Every keyboard shortcut
This is the complete list. The editor's hotkeys and the shortcuts shown in the command palette are generated from one set of command definitions, so anything below is exactly what the palette will tell you.
Editing
| Shortcut | Action |
|---|---|
Cmd/Ctrl + K | Open the command palette |
Cmd/Ctrl + Z | Undo the last editor change |
Cmd/Ctrl + Shift + Z or Cmd/Ctrl + Y | Redo |
1 / 2 / 3 | Switch transform gizmo mode (translate / rotate / scale) |
Delete or Backspace | Delete every selected object. This can be undone. |
Cmd/Ctrl + Shift + L | Open the editor Lua REPL |
Selection
| Shortcut | Action |
|---|---|
Shift + drag | Draw a marquee to select everything inside it |
Cmd/Ctrl + A | Select every object in the scene |
Esc | Deselect everything |
F | Frame the selected object |
/ | Open the Objects tab and focus its search field |
Simulation
| Shortcut | Action |
|---|---|
Cmd/Ctrl + Enter | Start the simulation, or stop it and restore the authored state |
P | Pause or resume physics and scripts without stopping |
Esc | Stop the simulation and restore the authored state |
Camera and view
| Shortcut | Action |
|---|---|
Home | Exit an authored camera preview, or reset the editor camera |
Shift + F | Toggle the frame-rate meter |
Shift + L | Reset the panel layout |
| No shortcut (toolbar button or command palette) | Set project thumbnail — capture the current viewport as the image shown on the project card |
Esc appears twice on purpose. While a simulation is running it stops the simulation; while one is not, it clears the selection.
Selecting more than one object
Hold Shift and drag in the viewport to draw a marquee — everything inside it is selected. Cmd/Ctrl + A selects the whole scene, and Esc clears the selection. Marquee selection only works while the simulation is stopped, and holding Shift suppresses camera movement for the duration of the drag so the view does not slew while you are dragging a box.
With several objects selected, the transform gizmo operates on all of them around a shared pivot, and Delete removes all of them as a single undoable change.
Workspace panels
The Objects, Properties, Materials, Assets, and Scripts panels are independently draggable and dockable, not fixed to a three-column layout. Panels float freely and are allowed to overlap each other, the same way panels work in Photoshop or Figma — the editor does not try to auto-avoid collisions between them. Drag a panel by its header to reposition it, drag its bottom edge to resize its height, and press Shift + L (or use the reset-layout command) to snap every panel back to its default corner. The collapse button in a panel's header folds it to just that header; Properties starts collapsed, so expand it the first time you select something. Each panel remembers its position and collapsed state.
The left panel has two tabs. Objects lists what is in the current scene; Scenes lists every scene in the project, and is where you switch between them, add one, duplicate one, or set which scene a player starts in.
When an object fails to load
If a scene object's stored data cannot be read, the editor skips that object rather than refusing to open the scene, and a panel appears at the top of the viewport saying how many objects failed to load. The rest of the scene is fully editable while the panel is showing.
This is the answer to "an object has vanished from my scene": it has not been deleted, it was skipped, and it will be skipped again on the next load until its data is fixed. Each row offers five actions:
- Retry — attempt to load that object again, which is enough if the failure was transient.
- Select — select the object so you can fix its fields in the inspector.
- Copy — copy the object's id, the error, and the object's stored data, formatted to paste straight into the AI assistant or an MCP client. Copy all does the same for every failed object at once.
- Delete — remove the object permanently, if it is not worth repairing.
- Dismiss — hide the row. This repairs nothing: the object is still broken and the panel returns the next time the scene loads.
The panel has a second kind of row, amber rather than red: an object that did load, but was built from something other than what it references. The usual one is a terrain whose height map could not be read, which comes out as procedural noise of the right size rather than the shape you authored — a failure that is otherwise invisible, because the terrain is there and looks like terrain. Those rows are marked in the scene and offer everything above except Delete: the object is standing and rendering, and deleting it would take its children with it. The header counts the two kinds separately.
The same panel also lists objects the server refused to save, marked not saved to server with the server's reason — for example an invalid material binding, a parent the object may not have (a water volume, or a link between a GUI element and a 3D object), or an id already in use. That object is on screen now but exists only in this browser, so it will be gone on the next load. These rows offer Select, Copy, and Dismiss; nothing is rolled back or retried for you. Fix the cause and edit the object again, and the row clears once the server accepts the save. A server that simply cannot be reached (offline, or an error on its side) does not add a row.
Pasting what Copy produces is usually the fastest fix, because the text already contains the stored data the object failed to load with, which is what a repair has to change.
GUI element visibility in the viewport
A group of three toolbar buttons controls how GUI objects (labels, panels, images, buttons) render in the 3D viewport, since they can otherwise sit on top of and block clicks on regular scene objects. The highlighted one is the current mode:
- Show GUI elements (default) — GUI elements render normally and can be selected like any other object.
- Hide GUI elements — GUI elements are hidden so they don't obscure the 3D scene underneath.
- Edit GUI overlay — the 3D scene is dimmed and 3D picking is disabled, so you can select and position GUI/overlay elements without fighting object selection underneath them.
GUI element positioning and layout
GUI elements live in screen space rather than the 3D world. In Free layout, Position (X, Y) is a normalized 0–1 point inside the element's container: the canvas for a root element, or its parent panel for a child. 0, 0 is the top-left and 1, 1 the bottom-right. Z is unused; zIndex controls front-to-back order.
Anchors and size modes
Anchor chooses which point of the element's own box lands on its position. The default is topLeft. To center an element without knowing its size, use position 0.5, 0.5 and anchor center. The other anchors pin any edge or corner, and Margin pushes an edge-anchored element inward in pixels.
Width and height each have a size mode: Fixed uses pixels, Hug follows the contents, Fill takes the container, and Percent interprets the stored width or height as 0–100% of the container. Font size, padding, borders, and gaps remain pixel values.
Panel flow layout
A guiPanel can place its children freely, in a row, or in a column. Row and column layouts ignore each child's position and anchor; the panel uses Gap, Across/Down justification, cross-axis alignment, and optional wrapping instead. Children use Order and their margins and size modes. This is the reliable way to build menus and toolbars that survive different viewport sizes.
Dragging a freely positioned element writes its normalized position. Dragging a child in a flowing panel rewrites its order. Older scenes may contain out-of-range positions such as 310 from a brief pixel-based format; those values still render compatibly, but new work should use normalized positions, anchors, and flow.
Scenes and objects
Objects belong to a scene, not directly to a project. Materials, assets, scripts, and shaders are project resources that can be reused by objects across that project’s scenes.
Simulation
Use the Run control to start simulation. Script init() functions run once, update(deltaTime) runs each frame, and physics begins stepping. Stop simulation to call destroy() and restore the authored state.
Run keeps the editor around the game. To play it the way a player will, switch from Build to Preview in the page header. Preview is also where desktop mouse look and on-screen touch controls work: the editor's Run reads the keyboard and mouse buttons, but not mouse movement or a touch drag.
Editor Lua REPL
The REPL runs short pieces of Lua against the scene you currently have open. Open it from the script icon in the toolbar, with Cmd/Ctrl + Shift + L, or from the command palette entry Open editor Lua REPL. Code you run there is never attached to an object and never saved as a project script — the Lua session exists only while the dialog is open.
Because nothing is attached, the REPL does not see the runtime globals an object script gets. There is no gameObject, scene, input, or gameStorage. Instead it exposes a single global, editor:
| Call | What it does |
|---|---|
editor.scene.list() | Every authored object in the scene, as plain tables |
editor.scene.get(id) | One object by id, or nil if there is no such object |
editor.scene.create(type, options) | Adds a new object and returns its id |
editor.scene.update(id, fields) | Writes fields onto an existing object; returns whether anything changed |
editor.scene.delete(id) | Removes an object; returns whether it existed |
editor.navigation.bake():await() | Bakes the current navigation mesh and waits for completion |
editor.navigation.stats() | Returns the current bake statistics |
editor.navigation.show() / hide() | Shows or hides the navigation-mesh overlay |
editor.navigation.findPath(from, to) | Returns a path across the baked mesh, or nil |
editor.log(message) | Writes a line to the output pane |
Whatever the snippet returns is pretty-printed as JSON in the output pane, so return editor.scene.list() is the quickest way to dump the whole scene. Navigation inspection is read-only in either REPL mode; bake is asynchronous, so use the exact :await() form shown above.
Inspect first, apply deliberately
The REPL is read-only until you say otherwise. list and get always work; create, update, and delete do nothing until you tick Apply scene changes, and they stay inert while a simulation is running. In read-only mode create returns nil and update/delete return false, so a dry run reads like the real thing minus the writes.
Creating and updating objects
create starts from the same default object the editor's own Add menu would produce, then applies your options. The accepted type names are cube, sphere, cylinder, capsule, empty, camera, ambientLight, directionalLight, pointLight, hemisphereLight, water, guiLabel, guiPanel, guiImage, and guiButton. Any other name raises an error.
local id = editor.scene.create("cube", {
name = "Crate",
position = { x = 0, y = 2, z = 0 },
})
editor.log("created " .. tostring(id))
editor.scene.update(id, { motion = "dynamic" })
return editor.scene.get(id)
Both create and update only write fields the object already has — plus, on GUI elements, the layout fields such as anchor, margin, widthMode, and a panel's layoutMode, which an element does not carry until it is given one — and both ignore id and type — you cannot reassign an object's id or change what kind of object it is. A field name the object does not carry is skipped silently, so update returning false usually means every field you passed was misspelled or does not apply to that type.
Applied changes are ordinary edits: creating, updating, and deleting all go through the editor's normal history and are saved like any other change, so Cmd/Ctrl + Z reverses a REPL edit exactly as it reverses one you made by hand.
The AI assistant and connected MCP clients can run the same ephemeral Lua against a scene you have open — see scripting_run_editor_lua in the MCP tool guide.
Command palette
Press Cmd/Ctrl + K to open the command palette. It searches every editor command by name and shows the shortcut for each one.
Editor hotkeys and the shortcuts the palette displays are generated from the same command definitions, so the palette cannot fall out of step with what the keyboard actually does. If you forget a shortcut, it is the canonical in-product guide — and the table above is the same list.