Author interactivity in the editor: tap an object and something happens, states,
timelines, visual graphs, interface cards. This covers most of what a project needs.
Start with events
β¨οΈ Build with code
Write TypeScript against a small, friendly API. Reach for it when you need game rules,
real maths, multiplayer, or anything genuinely custom.
Write your first script
Not sure which? Start without code. You can always add a script later, and the two work side by
side in the same project.
:::tip Patches sit comfortably between the two
A **patch** is behaviour you draw as a graph instead of typing. It has values, conditions and
loops like a script, but you build it by connecting nodes. Many people never need anything
else. β [Patches](/creators-engine/authoring/patches)
:::
---
## π§ Find your way around
| If you want to⦠| Go to |
| ------------------------------------------------- | ------------------------------------------------------------- |
| See the range of what people build | [What you can build](/docs/what-you-can-build) |
| Understand what makes this engine different | [Why this engine](/creators-engine/why-this-engine) |
| Understand how a project is put together | [Objects and scenes](/creators-engine/concepts/objects-and-scenes) |
| Know what a particular setting does | [Components](/creators-engine/concepts/components) |
| Make something happen when a visitor taps | [Events](/creators-engine/authoring/events) |
| Move, fade or choreograph something | [Animation](/creators-engine/authoring/animation) |
| Let people walk around as a character | [Game controls](/creators-engine/authoring/game-controls) |
| Add buttons, menus, a score display | [UI cards](/creators-engine/authoring/ui-cards) |
| Make things fall, collide and bounce | [Physics](/creators-engine/concepts/physics) |
| Add fire, smoke, sparks or rain | [Particles](/creators-engine/authoring/particles) |
| Build a material the standard types cannot express | [Material graphs](/creators-engine/authoring/shader-graphs) |
| Fill a scene with grass, crowds or debris | [Prefabs and instancing](/creators-engine/concepts/prefabs-and-instancing) |
| Draw a pipe, a rope, or a route to follow | [Paths](/creators-engine/authoring/paths) |
| Set up a minimap or a security monitor | [Cameras](/creators-engine/concepts/cameras) |
| Build something multiplayer | [Multiplayer](/creators-engine/scripting/multiplayer) |
| Import the platform's own packages in your code | [Shared packages](/creators-engine/concepts/shared-packages) |
| Look up every trigger, step, node or setting | [Reference](/creators-engine/reference/components) |
| Work out why something is not behaving | [Troubleshooting](/creators-engine/troubleshooting) |
---
## β οΈ One thing to know up front
**The editor does not run your logic.** It draws and edits your scene, but scripts, patches,
physics and timers stay switched off while you author.
That is on purpose β an animation playing while you work would fight you for control, and a
timer would quietly write changes into your project.
So when you want to *see* your work behave, open **Preview**. If you ever catch yourself
thinking "this should be working", check this first: it explains most of them.
---
# Building with an AI assistant
Source: https://docs.arclip.design/creators-engine/ai
# Building with an AI assistant
There are three ways an AI model can help you build on AR Clip, and they need very different
things from you.
| Way | The model can⦠| Needs |
| ------------------------------------- | ------------------------------------------------------------- | ------------------------------ |
| **The built-in assistant** | build and change your scene directly | nothing β it is in the editor |
| **Your own tools over MCP** | the same, from Claude, Cursor, or anything that speaks MCP | an API token |
| **Any chat model** (GPT, DeepSeek, Perplexity, β¦) | advise, write scripts and patch graphs for you to paste | the [brief](/creators-engine/ai/brief-for-assistants) |
## The built-in assistant
The quickest path: the top icon in the editor's left rail. Describe what you want, it builds
it, and everything it makes is ordinary project content you can edit by hand afterwards.
β [AI assistant panel](/studio/panels/assistant)
## Your own tools, over MCP
If you already work in a tool that speaks **MCP** β Claude Desktop, Claude Code, Cursor and
others β you can point it at your project and let it build while you watch in the editor.
β [Connecting over MCP](/creators-engine/ai/mcp)
## Any other model
Models without MCP cannot touch your project, but they can still be genuinely useful β writing
a script, designing a patch graph, working out why something is not firing.
They need context, because none of them know this platform. Point the model at **one page**:
β [Brief for assistants](/creators-engine/ai/brief-for-assistants)
:::tip Paste the brief, then ask your question
That page is written to be handed to a model as-is. It contains the mental model, the naming
rules and the traps that most often produce confidently wrong answers.
:::
## What it is worth asking for
Concrete jobs, from running a workshop on one shared canvas to generating a project from a
spreadsheet: [what agents are good for here](/creators-engine/ai/use-cases).
## What to expect
**An assistant is good at** the tedious parts: making forty objects, wiring the same event onto
a list of things, writing a script you could have written but would rather not, working out
which step does what.
**It is weaker at** judgement: how your experience should feel, whether a marker will track in
the room it will actually live in, how big something should be so it reads from three metres.
:::tip Assistants can find this on their own
The site publishes [`/llms.txt`](https://docs.arclip.design/llms.txt) β the convention AI tools
look for at a site root β with the brief as its first entry, and
[`/llms-full.txt`](https://docs.arclip.design/llms-full.txt) containing every page in one file.
So "read the AR Clip docs" is often enough of an instruction; you do not have to paste anything.
:::
:::danger Nobody legitimate will ask you for a key in a chat
Not us, not a plugin, not an assistant. Tokens go in your account settings.
:::
---
# Brief for assistants
Source: https://docs.arclip.design/creators-engine/ai/brief-for-assistants
# Brief for assistants
**This page is written to be given to an AI model.** Paste it into a conversation, or point the
model at this URL, before asking it to help with an AR Clip project.
Assistants that look for machine-readable context will find it by themselves at
[`/llms.txt`](https://docs.arclip.design/llms.txt), with the whole documentation in one file at
[`/llms-full.txt`](https://docs.arclip.design/llms-full.txt).
It exists because no model has been trained on this platform. Without it they reach for
whatever engine they do know (Unity, three.js, A-Frame) and produce answers that look right
and are not.
---
## The mental model
Everything in a project is an **entity**. An entity is a name plus a set of **components**, and
the components decide what it is. There is no class hierarchy and no object types to choose
from.
```
Project
βββ Space shared background, lighting, grid, units
βββ Scene an entity carrying an Anchor β the trigger that makes it appear
βββ Entity
βββ Entity entities nest
```
Two relationships that must not be confused:
- **Composition** β an entity *has* components. One of each kind. Components are not children.
- **Containment** β an entity *contains* other entities. Moving a parent moves its children.
A **scene** is an entity with an Anchor and no Transform. **Space-level logic** is an entity
with a Script or Patch and no parent.
You never write systems. The engine reacts to components; your job is to decide which
components exist and what their values are.
## Four ways to add behaviour
| Layer | Lives in | Use for |
| ----------- | ------------------------------ | ---------------------------------------------------- |
| **Events** | an Events component | trigger β list of steps; most interactivity |
| **Patches** | a patch graph or resource | logic with values and conditions, built visually |
| **Scripts** | a script resource | anything genuinely programmatic |
| **UI** | a DivKit card | all 2D interface |
All four write into the same components. **The same trigger handled in two of them fires
twice** β a very common generated bug.
## Naming rules
- **Triggers** are kebab-case: `on-click`, `on-launch`, `on-collide`.
- **Steps** are snake_case: `play_animation`, `set_visibility`, `scene_transit_action`.
- Resolution is exact. A mistyped name does not error β **it silently never matches.**
- The editor shows human labels ("Show / hide object"); the ids above are what code uses.
## Do not guess β look it up
If you are connected over MCP, these answer from the live engine:
| Call | Returns |
| ------------------------ | ------------------------------------------ |
| `list_component_schemas` | every component and its fields |
| `list_event_types` | every trigger and step with parameters |
| `list_patch_nodes` | every patch node with its ports |
| `describe_*_api` | prose guidance per area |
Call them before writing anything that names a component, a trigger, a step or a node.
Inventing a plausible name is the single most common failure mode here.
Without MCP, use the generated reference:
[components](/creators-engine/ai/../reference/components) Β·
[triggers](/creators-engine/ai/../reference/triggers) Β·
[steps](/creators-engine/ai/../reference/steps) Β·
[patch nodes](/creators-engine/ai/../reference/patch-nodes) Β·
[shader nodes](/creators-engine/ai/../reference/shader-nodes).
---
## Traps that produce confidently wrong code
:::danger Writing a nested value replaces all of it
`update({ position: { y: 2 } })` sets `x` and `z` to zero. Always spread:
```ts
t.update({ position: { ...t.$data.position, y: 2 } });
```
:::
:::danger A material is a list of slots
`material.update({ color })` does nothing β `color` lives inside a slot:
```ts
material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});
```
:::
:::danger Never assign into `$data`
It appears to work and the change is dropped. Only `update()` and `updateAt()` write.
:::
:::danger Setting a dynamic physics body's transform does nothing
Physics owns its position and overwrites it next step. Use `ctx.physics.teleport` to place and
`applyImpulse` / `applyForce` to move.
:::
:::danger An imported model has no collision shape
A GLB is not solid until you give it a collider. A dynamic one falls through the world.
:::
Further rules that catch generators out:
- **Rotation is radians in scripts, degrees everywhere a human looks** β the editor, patch node
ports, MCP tooling.
- **Multiply by `dt` in `ctx.tick`**, or motion runs at the device's frame rate.
- **There is no "animation finished" event** in any mechanism. Time it yourself.
- **Scripts have no DOM, no `fetch`, no timers, no rendering library.** Use `ctx.tick`,
`ctx.audio`, `ctx.store`, and a UI card for interface.
- **The editor does not execute logic.** Scripts, patches, physics and timers only run in
preview or a publication. Never tell a user their script "should run in the editor".
- **While a state is active, edits to that object are recorded into the state**, not the object.
- **The timeline stores `channels` for authoring and a baked `keyframes` list for playback.**
Writing channels without re-baking means nothing plays.
- **One object, one animation mechanism.** The timeline overwrites a transition every frame.
## Prefer the built-in step to reimplementing it
`ctx.step(name, params, { targets })` runs any step the editor offers β animation, state
switching, scene transitions, transitions. Check the step reference before writing code by hand.
## Validate a patch before claiming it works
Compile it and read the result. A graph that cannot compile reports a data cycle or broken
JavaScript, and the compiled source is exactly what will run. Over MCP that is
`preview_patch_code`.
## Units
| Quantity | In data | Where a human sees it |
| -------------- | ------------------------ | -------------------------------------- |
| Position | metres | project units |
| Rotation | radians | degrees |
| Animation time | seconds | seconds (milliseconds on state switches) |
| Opacity | 0β1 | 0β100 in keyframes and the opacity step |
| Font size | pixels, 1000 px = 1 m | pixels |
| Clip frames | 30 fps | frames |
## Answering a user well
1. **Ask which layer they want.** "Without code" and "in a script" lead to completely different
answers to the same question.
2. **Prefer the simplest layer that works.** An event beats a patch; a patch beats a script.
3. **Say where to click.** Panel names matter more than concepts to someone in the editor.
4. **Remind them to preview.** Most "it does not work" reports are the editor not running logic.
5. **Do not invent names.** If unsure, say so and point at the reference.
---
# Connecting over MCP
Source: https://docs.arclip.design/creators-engine/ai/mcp
# Connecting over MCP
AR Clip exposes an **MCP server**, so a tool that speaks the Model Context Protocol can work on
your project directly β reading the scene, adding objects, wiring events, writing patches.
## The endpoint
```
https:///api/mcp/v1
```
Authentication is a bearer token:
```
Authorization: Bearer
```
Create the token in your account settings, under **API & MCP**.
:::danger Treat the token like a password
It can change your projects. Keep it out of anything you share, and revoke it if it leaks.
:::
## Scopes
A token carries only what you give it:
| Scope | Allows |
| ---------------- | ----------------------------------------------------- |
| `editor:read` | reading projects, scenes and resources |
| `editor:write` | creating and changing them |
| `editor:ai` | the generation tools |
| `editor:publish` | publishing |
**Give a read-only token when you only want analysis.** A model that cannot write cannot break
anything, and a great deal of useful work β auditing a scene, explaining why something does not
fire, planning changes β needs nothing more.
## What connects
Anything implementing MCP over streamable HTTP. In practice: Claude Desktop, Claude Code,
Cursor, and a growing number of editors. Each has its own place to declare a server β you give
it the URL above and your token.
## Working alongside the editor
Changes an assistant makes **appear live in your editor**, the same way a colleague's would,
and they land in the collaboration history like any other edit.
So the productive shape is: keep the editor open on one screen, ask for changes, and watch them
happen. You see immediately when something is not what you meant, instead of discovering it
afterwards.
A session remembers which project and space you are working on, so you do not have to repeat it
in every request.
## What the tools cover
Grouped by what you would ask for:
| Area | The assistant can |
| ------------- | --------------------------------------------------------------------------- |
| **Scene** | list, create, move, parent, duplicate and delete objects |
| **Components**| read and set any component on any object |
| **Events** | list triggers and steps, add and edit events |
| **Patches** | read, write and validate node graphs, and preview the code they compile to |
| **Animation** | author timeline presets |
| **Physics** | set bodies, colliders and starting velocities |
| **Resources** | browse the project tree, create folders, import and generate assets |
| **Interface** | read and write UI cards |
| **Publishing**| publish, with the right scope |
Four more that are easy to miss, because they reach out of the server:
| Also | What it does |
| ------------------ | ------------------------------------------------------------------------- |
| **Screenshots** | capture the viewport, so an assistant can look at what it built |
| **Measurement** | an object's real bounding size in metres β how it checks scale |
| **Scene export** | take a scene out as data |
| **Blender bridge** | talk to a running Blender: list objects, push a scene to it, pull one back |
:::note A screenshot is not proof
An assistant can see its own work, which helps β but a still frame says nothing about whether
scripts run, whether physics settles, or whether a marker tracks. Those need a preview on a real
device.
:::
:::tip Several tools describe the platform to the model itself
`list_component_schemas`, `list_event_types`, `list_patch_nodes` and the `describe_*_api` tools
answer **from the live engine**, so an assistant can look up exactly what exists rather than
guessing from training data. If a model is inventing component names, tell it to call those
first.
:::
## Ready-made tasks
The server also offers a few prompts an MCP client shows as commands:
| Prompt | Does |
| ------------------- | ----------------------------------------------------- |
| `build-scene` | build a scene from a description |
| `add-interactivity` | wire up taps, triggers and steps |
| `animate` | put together a timeline |
| `audit-scene` | look for problems |
| `prepare-publish` | check the things worth checking before publishing |
## Reading without changing
The server publishes its own reference as resources a client can read:
```
was://project/info was://scene/current was://scene/selection
was://scripting/api was://patch/api was://events/api
was://physics/api was://timeline/api was://resources/api
was://ui/api
```
These are generated from the engine, so they never describe a version of the platform that no
longer exists.
---
**Next:** [The assistant's skills](/creators-engine/ai/skills)
---
# The assistant's skills
Source: https://docs.arclip.design/creators-engine/ai/skills
# The assistant's skills
The assistant does not carry the whole manual in its head. It holds a one-line index and
**fetches a guide when the work calls for it** β so a request about physics pulls the physics
guide and nothing else.
Knowing the list is useful for two reasons: you can name one directly, and it tells you what the
assistant is actually good at.
## Building a scene
| Skill | Covers |
| -------------------------- | --------------------------------------------------------------------------- |
| **scene-building** | composing a scene: classifying objects, placing them, lighting, real-world sizing |
| **assets** | where objects come from β the library, web search, AI generation, and the two resource stores |
| **materials** | slots, types, and the maps that are silently never drawn |
| **ar-anchors** | each anchor type and what it needs, plus making the camera scriptable |
| **figma-import** | bringing a Figma frame in as a scene rather than redrawing it |
| **rebuild-from-reference** | you attach a photo or a clip and want *that thing* built out of primitives |
## Adding behaviour
| Skill | Covers |
| --------------------- | --------------------------------------------------------------------------- |
| **interactivity** | choosing a layer β events, patches, scripts or UI β and trigger casing |
| **event-behaviours** | branching events, variables, signals, timers, proximity, game controls, follow |
| **scripting** | the shape of a script, properties, templates, the two kinds of raycast |
| **animation** | which of the four homes a movement belongs in, and the degrees-versus-radians trap |
| **physics** | bodies, colliders, velocity β and why none of it is visible in the editor |
| **ui-divkit** | HUDs, panels, buttons β and where game state must **not** live |
| **plugins** | extending the editor itself rather than a scene |
## Games
| Skill | Covers |
| ----------------- | ------------------------------------------------------------------------------ |
| **game-loop** | the five parts of a playable loop, where each lives, and how to add juice |
| **fps-shooter** | camera rig, mouse look, WASD, hitscan shooting, enemies, HUD |
| **multiplayer** | turning it on, shared state and channels, and what does **not** replicate |
## Using them
Usually you do not have to β describe the work and the right guide is read for you.
Where naming one helps is when a request could go several ways:
```
Build this as a game loop, not just an object that reacts.
```
```
Use the rebuild-from-reference approach for this photo.
```
:::tip The skills are guides, not the reference
They describe **how to approach** a job. The facts β every component field, every trigger, every
node β come from tools that read the live engine, so they cannot describe a version of the
platform that no longer exists.
:::
## Why the assistant sometimes asks first
Several skills exist because the same request has genuinely different right answers. "Make it
move" might be a transition, an animation step, a timeline or a script β and picking wrongly
produces something that works today and fights you next week.
If it asks which you meant, that is the skill doing its job.
---
**Next:** [Brief for assistants](/creators-engine/ai/brief-for-assistants)
---
# What agents are good for here
Source: https://docs.arclip.design/creators-engine/ai/use-cases
# What agents are good for here
Grounded in what the platform actually provides, and what mechanism makes each one work.
---
## Working together on one canvas
A space is **one live world shared by everyone in it**, so an assistant editing over MCP is
just another participant. Its changes appear in your editor as they happen, and land in the
edit history like anyone else's.
That makes a workshop practical: a room full of people, one scene, and an assistant doing the
typing.
| You say | It does |
| ---------------------------------------------- | ---------------------------------------------------- |
| "Put a table here and four chairs around it" | builds it while everyone watches |
| "Make the chairs the same blue as the logo" | finds the colour and applies it across all four |
| "Undo the last three things" | or you undo them yourself β same history |
:::tip Keep the editor open while it works
The value is not that it builds unattended β it is that you see each change land and can say
"no, smaller" immediately, rather than reviewing a finished thing you did not want.
:::
## Running the session itself
Plugins have a **project room**: shared state everyone in the project sees, with the server's
clock and a record of who wrote what. It is meant for the meeting rather than the document β
nothing there is published or undoable.
That is enough to build real facilitation, and the shipped examples do exactly that:
| Example plugin | Does |
| ------------------ | ---------------------------------------------------------------------- |
| **Session timer** | a countdown everyone sees, on the viewport overlay |
| **Session voting** | polls with one vote per person, counted with the author check |
An assistant can drive these the same way a person can β start a timeboxed round, open a vote
on two options, read the result, and carry on building with the winner.
:::note Why the server clock matters
Two laptops can disagree about the time by minutes. Shared deadlines are stored as an absolute
server time and each screen counts down itself β so "4:12 left" means the same thing to
everybody in the room.
:::
## Building a game with you
The division that works: **you decide how it should feel, the assistant does the assembly.**
| It is good at | You stay in charge of |
| ------------------------------------------------------ | ----------------------------------- |
| scaffolding the scene, spawning the objects | how it should look |
| wiring the same event onto twenty things | whether the idea is fun |
| writing the script you could write but would rather not | difficulty and pacing |
| converting an event into a patch when it outgrows one | when to stop adding things |
Because events, patches and scripts share one trigger vocabulary, an assistant can start simple
and upgrade in place β an `on-click` event today becomes a patch with a condition tomorrow and
a script when it needs real logic, without anything being rebuilt.
## Turning a brief into an AR scene
"A poster that shows the product spinning above it when you point a phone at it" is a complete
specification here: a scene, an image anchor with the printed size, a model, a rotation.
An assistant can set all of that up, and the parts it cannot judge are the ones worth your
attention anyway β whether the artwork tracks well, whether the size reads right in the room.
## Turning designs into experiences
With a Figma account connected, a frame becomes either a 3D layout or an interface card, with
sizes, colours and text intact. An assistant can take the link, do the import, and then wire the
buttons to scene steps β the tedious half of "make the design do something".
β [Figma and Sketchfab](/studio/integrations)
## The work nobody wants to do by hand
This is where an assistant pays for itself fastest:
- build forty objects from a list and lay them out on a grid;
- rename everything consistently so scripts can find things;
- point every material at a new texture after an art change;
- generate a scene per product in a catalogue;
- produce the same experience in five languages.
## Auditing before you publish
The platform ships debugging plugins that look for exactly the things that go wrong quietly,
and an assistant can run the same checks:
| Check | Catches |
| ---------------------------- | -------------------------------------------------------------- |
| **Scene doctor** | objects outside the scene, broken parents, hierarchy loops, zero scale, NaN in a transform, references to deleted resources |
| **Resource usage** | where each resource is used, and which are used by nothing |
| **World inspector** | the entity tree and what each component actually holds |
Add the everyday mistakes an assistant can spot by reading the scene: a model with no collider
that is meant to be solid, a trigger handled in both an event and a script, a state left
selected, an animation driven by two mechanisms at once.
## Explaining the platform to a newcomer
A model that has read the [brief](/creators-engine/ai/brief-for-assistants) can answer "how do I make this
button open the next scene" with the actual panel names and the actual step β which is usually
faster than finding it in the documentation.
## Multiplayer experiences
The runtime has rooms, shared state and synchronised physics. An assistant can build the
scaffolding (who owns what, what replicates, how remote players are drawn) which is the part
people get wrong most often.
β [Multiplayer](/creators-engine/ai/../scripting/multiplayer)
## Generating projects from data
Plugins can create scenes and spaces through the editor's own functions, and mark what they
made with tags so they can find it again. That makes an assistant a reasonable way to turn a
spreadsheet of products, a folder of photographs or a list of locations into a finished
project β and to update it when the data changes.
The shipped **photo revival** example does a version of this: a batch of images paired by
filename, assembled into AR scenes.
---
## Where a human still has to decide
Being honest about the limits saves disappointment:
- **whether it works in the room it will live in** β lighting, marker size, how far people stand;
- **whether it is enjoyable** β no model can tell you the puzzle is boring;
- **what to cut** β assistants add;
- **anything with consequences** β publishing, spending, sharing.
---
**Next:** [The assistant's skills](/creators-engine/ai/skills)
---
# Animation
Source: https://docs.arclip.design/creators-engine/authoring/animation
# Animation
There are four ways to make something move. They are genuinely different tools, not layers of
one thing, so the first job is picking the right one.
| Way | Best for | You set it up in |
| ------------------ | ----------------------------------------------------------- | ---------------------------------- |
| **Transition** | one smooth change from here to there | a duration on a step or a state |
| **Animation step** | a reusable move attached to an event | a step inside an event |
| **Timeline** | choreography β several objects, several tracks, one clock | the Animation Controller |
| **Model clip** | animation that came with an imported model | the model's own settings |
A rough rule: **fading a button** is a transition, **a door opening when tapped** is an
animation step, **a cutscene** is the timeline, and **a character walking** is a model clip.
:::danger One object, one mechanism
Never animate the same object two ways at once. The timeline writes to its objects every frame
and will quietly overwrite a transition. If an animation seems to fight itself, this is why.
:::
## Transitions
Give a change a duration and it eases instead of jumping. That applies to a state switch with a
duration and to an event step with one.
A transition has a duration, an easing, and optionally a repeat count and ping-pong. What can
ease is listed under [what animates smoothly](/creators-engine/authoring/../concepts/lights#what-animates-smoothly);
anything else changes at the end.
:::note A repeat count of 1 is a single pass
Ping-pong starts at 2. This keeps older projects from suddenly pulsing.
:::
## Animation steps
An animation step moves, rotates, scales or fades whatever you target.
Give it a single destination and it eases there. Give it **two or more keyframes** and it plays
the whole curve.
| Step | Animates |
| --------------------- | ------------------------------------------------- |
| Transformation | position, rotation, scale |
| Opacity | fading in and out |
| Skeletal transform | a single bone |
| Material | copies a saved material's look onto your targets |
| Play / stop animation | starts or stops a timeline preset |
## Keyframes and easing
A keyframe is a time and a value.
:::info Easing belongs to the keyframe you are moving *into*
Not the one you are leaving. Set the easing on the destination keyframe. No easing means a
straight line.
:::
Easing is a curve with two handles, exactly like CSS `cubic-bezier`. The named presets β quad,
quart, circular, expo, back, each as In, Out or InOut β are just useful handle positions.
| Easing | Feels like |
| ----------- | ------------------------------------------------------------- |
| **In** | starts slowly, builds speed β something setting off |
| **Out** | arrives and settles β the most natural for UI |
| **InOut** | eases at both ends β good for long moves |
| **Back** | overshoots slightly and comes back β playful |
| **Elastic** | overshoots and wobbles, with its own amount and period |
| **Step** | holds, then snaps at the end β for switching, not sliding |
If you are unsure, **Out** is almost always the right first guess.
## The timeline
The Animation Controller is for choreography β several things moving together on one clock.
Its structure is three levels:
- a **preset** is one animation you can play by name;
- a **bar** is one object's part in it, sitting on a **track**;
- inside a bar, each property has its own keyframes.
A bar can also be a **playback bar**: instead of animating anything itself, it starts another
object's preset when the playhead reaches it. That is how a character whose limbs are separate
presets stays in sync. Looping and speed pass down to everything it starts, and stopping a
preset stops everything it started.
Individual properties inside a bar can be muted, so a bar that animates position and rotation
can have its rotation switched off without losing the keyframes.
:::note Imported clips keep their original curves
Animation that arrives with a model is not re-approximated, so an imported walk cycle plays
exactly as the artist authored it.
:::
## Model clips
A model brings its own named clips. Pick one, then set speed, looping (once, a count, or
forever) and a starting frame β counted at 30 frames per second whatever the source used.
Changing the clip or the start frame rewinds. Pausing holds the current time instead of
resetting it.
## Units, and one thing that is missing
- time in **seconds** (durations on state switches are in milliseconds);
- rotation in **degrees** everywhere you can see it;
- start frames counted at 30 fps.
:::warning There is no "animation finished" event
None of the four mechanisms provides one. To follow an animation with something else, use a
**wait** step of the same length, or track elapsed time in a script.
:::
---
**Next:** [States](/creators-engine/authoring/states) β named snapshots of an object.
---
# Events
Source: https://docs.arclip.design/creators-engine/authoring/events
# Events
An event is one sentence: **when this happens, do these things.**
```
WHEN someone taps the door
THEN play the creak sound
swing it open over 600 ms
disable the "locked" sign
```
The *when* is a **trigger**. The *then* is a list of **steps**. That is the whole idea β and it
covers a surprising amount of what a project needs.
An object can hold as many events as you like, and each one runs independently.
:::note These are the names you see in the editor
If you also write scripts or build patches, each one has a short code name too β the
[trigger](/creators-engine/authoring/../reference/triggers) and [step](/creators-engine/authoring/../reference/steps) references list both side
by side. Building in the editor, you never need it.
:::
:::tip Two generated lists to keep open
Every trigger you can react to: **[triggers](/creators-engine/authoring/../reference/triggers)**.
Every action you can take: **[steps](/creators-engine/authoring/../reference/steps)**.
Both come straight from the engine, so they are never out of date.
:::
## Every trigger, and what it is for
The generated [trigger reference](/creators-engine/authoring/../reference/triggers) has each one's parameters. This is
what they *mean*, and where they surprise people.
### Input
| In the editor | Fires when |
| ---------------- | --------------------------------------------------------- |
| **Mouse button** | a tap or click β branches for click, press and release |
| **Hover** | the cursor enters or leaves the object |
| **Double click** | two clicks on the same object within 300 ms |
| **Long press** | held for 450 ms without moving |
| **Key** | a key β branches for press and release |
| **Scroll** | the wheel turns; filterable by direction |
| **Drag** | placed AR content is dragged |
| **Pinch** | placed AR content is pinched to scale |
| **Rotate** | placed AR content is twisted |
The three gesture triggers only exist on **surface** scenes β they are about content the
visitor placed. They repeat continuously while the finger moves, so treat them as a stream
rather than a single moment.
### Lifecycle
| In the editor | Fires when |
| ------------- | ------------------------------------------------ |
| **Launch** | the scene starts β reaches every object |
| **Resize** | the window or viewport changes size |
| **Pause** | the app or tab goes to the background |
| **Resume** | it comes back |
:::tip `on-pause` is where you stop things politely
Sounds keep playing when a visitor switches apps unless you stop them. Pausing audio and
timers on `on-pause` and restoring them on `on-resume` is a small touch people notice.
:::
### Tracking
| In the editor | Fires when |
| --------------------------- | ------------------------------------------------ |
| **Detect** | the marker or surface is found |
| **Tracking lost** | tracking is lost |
| **Placed** | the visitor placed the content |
| **VPS ready** | positioning started up |
| **VPS localized** | the visitor's real position was found |
| **VPS localization failed** | it was not β **this is normal, not an error** |
| **VPS error** | positioning could not run at all |
Each is offered only on scenes whose anchor could produce it. Detection is reported **on the
edge**, when tracking starts, not every frame.
:::note A missed VPS localisation is expected
`on-vps-not-localized` fires routinely while the visitor walks around looking for a spot the
map recognises. Treat it as "keep trying", not as a failure to announce.
:::
### Time and proximity
| In the editor | Fires when |
| ------------------- | -------------------------------------------------------------- |
| **Delay** | once, after a delay |
| **Every N seconds** | repeatedly β a repeat count of 0 means forever |
| **Distance** | something comes within a distance, or leaves β two branches |
| **Visibility** | the object is shown or hidden |
| **Variable change** | a variable's value changes |
`on-distance` has a "check at start" option. Without it, an object that is *already* close when
the scene opens will not fire until something moves.
### State, physics and interface
| In the editor | Fires when |
| -------------------- | -------------------------------------------------------------- |
| **State active** | this object enters the state you picked |
| **State inactive** | it leaves that state |
| **Collision** | this object touches another β optionally only specific ones |
| **UI action** | a button on an interface card was pressed |
| **Signal** | another event emitted a signal |
| **Game controls** | the character changed state |
## One event, both directions
Some triggers have **branches**, so a single event handles every outcome. Set a distance once,
and answer both approaching and leaving:
| Trigger | Branches |
| ---------------- | --------------------------------- |
| Distance | entered Β· left |
| Key | pressed Β· released |
| Hover | entered Β· left |
| Mouse | click Β· pressed Β· released |
| Visibility | shown Β· hidden |
| Game control | idle Β· moving Β· jumping Β· running |
You get one section per branch in the editor. Triggers without branches have a single section,
"on activation".
## Which objects a step affects
Every step has **targets**.
Leave them empty and the step acts on the object holding the event β the usual case. Fill them
in and it acts on those objects instead, which is how one event on a button opens three doors.
## Taps travel up to parents
A tap, a hover or a card action reaches the object **and its parents**.
This saves a lot of repetition: put one event on a group and it catches taps on anything inside
it, instead of copying the same event onto forty objects.
Two pleasant side effects:
- "tap anywhere in this panel" is one event on the panel;
- sliding between two children of the same parent does **not** fire hover/blur on the parent,
because you never actually left it.
Collisions are the exception β they fire only where they happened. And keyboard, scroll, resize
and scene-start are not about any single object, so they reach everything.
## Several keys for one event
Jump on Space **or** W is one event, not two copies. The first combination lives in the
trigger; the rest go in an "or" list, written as text:
```
ArrowUp ctrl+KeyW KeyA+KeyS Space+double
```
Modifiers belong to the combination they are written in, and an alternative **replaces** the
binding rather than adding to it. Mouse buttons use the same format (`mouse:middle`).
Better still: name a **project binding** instead of a key. Rebind "jump" once in project
settings and every event that mentions it moves with it β which is what you want the day
someone asks for left-handed controls.
## Every step, and what it does
Parameters are in the generated [step reference](/creators-engine/authoring/../reference/steps).
### Navigation and the outside world
| In the editor | Does |
| --------------------- | --------------------------------------------------------------- |
| **Scene transition** | go to another scene |
| **Space transition** | go to another space, optionally to a specific scene |
| **Open URL** | open a link, in this tab or a new one |
| **Open messenger** | Facebook, VK, OK, Instagram, Telegram, WhatsApp or Viber |
| **Call phone number** | start a phone call |
| **Compose email** | start an email |
| **File download** | download a file from your project |
| **Contact download** | offer a contact card, built from the fields you fill in |
The messenger step takes a page id for Facebook, VK and OK, a username for Instagram and
Telegram, and a phone number for WhatsApp and Viber.
### Changing the scene
| In the editor | Does |
| ----------------------- | ----------------------------------------------------------------- |
| **Show / hide object** | show or hide the targets β on a scene this activates it |
| **Set state** | switch into a named state, optionally easing over a duration |
| **Override properties** | apply a one-off set of values as a temporary state |
### Animation
| In the editor | Does |
| ---------------------- | ------------------------------------------------------------ |
| **Play animation** | play a timeline preset β loop mode, repeats, speed, delay |
| **Stop animation** | stop one |
| **Transformation** | move, rotate or scale over time |
| **Opacity** | fade |
| **Material** | blend the targets' materials toward a saved one |
| **Bone transform** | move a single bone |
| **Built-in animation** | play a clip that came with a model |
**Play animation** can start an animation belonging to **any** object, not just the one holding
the event β which is how one button starts a sequence involving several things at once.
### Movement
| In the editor | Does |
| --------------------- | ----------------------------------------------------------------- |
| **Follow object** | follow continuously β distance, offset, smoothing and speed |
| **Look at object** | turn to face something without moving |
| **Stop following** | stop both |
| **Fly along path** | travel along a path |
| **Stop path flight** | stop that |
### Logic
| In the editor | Does |
| ------------------ | ------------------------------------------------------------ |
| **If β otherwise** | check a condition, then run one of two nested step lists |
| **Set variable** | set, add to, or toggle a variable |
| **Send signal** | fire a signal β to the targets, or to everyone |
| **Wait, then** | pause before running the steps nested inside |
### Pointer
**Lock pointer** and **Release pointer** hide the mouse cursor and hand its movement to your
scene β what a first-person game needs so the pointer does not wander off the window.
## Conditions
`if_action` is a step, not a property of the event β so conditions nest, and "if inside if" is
how you get more than two outcomes.
What the condition looks at is set by its **source**:
| Source | Checks | Using |
| ---------- | --------------------------------------------------------------- | --------------------------- |
| `always` | nothing β always true | β |
| `variable` | a variable's value | variable, operator, value |
| `distance` | how far the event's object is from another | target, operator, threshold |
| `state` | which state another object is in | target, state |
| `visible` | whether something is visible | target, flag |
| `random` | chance | threshold, as a percentage |
Values are read loosely: `3` becomes a number, `true` becomes a boolean, anything else stays
text. `==` and `!=` compare exactly; the other operators compare as numbers.
## Variables, signals and timers
**Variables** last for the visit, not for the project β they start empty each time someone
opens your experience. Give them their starting values from an event on scene launch. They are
the same variables a script sees as globals.
**Signals** (`emit_signal` β `on-signal`) chain events without copying steps: several events
listen for `level-complete`, and anything can emit it. A signal that re-emits itself stops after
eight rounds rather than freezing the frame.
**Timers** start when their object becomes live and reset when it is disabled. Re-enabling
restarts the count instead of firing a burst of missed ticks.
## States
A **state** is a named snapshot of an object's settings: position, colour, visibility, whatever
you like. Switching to a state applies the snapshot, and switching with a duration makes it ease
smoothly.
States are the tidy way to build a switch, a highlight, a door that is open or closed β without
animating anything by hand.
:::warning While a state is selected, your edits go into it
Change an object while one of its states is active and you are editing **that state's**
snapshot, not the object. This is exactly what you want when building states, and thoroughly
confusing when you did not realise a state was selected.
:::
## Re-tracking
For AR scenes, each event's re-tracking mode decides what happens when the marker is lost and
found again: start over immediately, never restart, or restart once the current steps have
finished.
## If you also write scripts
Events, patches and scripts all share one vocabulary β the same tap reaches all three.
:::danger Handle each trigger in one place only
The same trigger on the same object, wired as both an event and a script, runs twice. If
something fires double, this is almost always why.
:::
---
**Next:** [Animation](/creators-engine/authoring/animation) β making things move.
---
# Game controls
Source: https://docs.arclip.design/creators-engine/authoring/game-controls
# Game controls
Game controls turn an object into something your visitor drives: walking, flying, or moving
around a top-down map.
You add it as a **trigger**, not a step. Put the game-control trigger on an object and the whole
controller lives in its settings β and its branches (idle, moving, jumping, running) let you
react to what the character is doing.
The controller lives as long as that object does. Disable the object and it is gone; enable it
again and it comes back.
:::tip You need three things for a character
A **controlled object** (this trigger), **something solid to stand on** (a floor with a rigid
body and a collider), and **a camera** β or none, and one will be provided.
:::
## Movement
| Setting | What it controls |
| ---------------------- | --------------------------------------------------------------- |
| Mode | walk, fly, or top-down |
| Forward is | where the camera looks, or the scene's own axes |
| Speed Β· run multiplier | metres per second, and how much faster while holding Shift |
| Acceleration Β· braking | how long it takes to reach speed, and to shed it |
| Turn to movement | whether the object rotates to face where it is going, and how fast |
| Key scheme | WASD, arrows, or both |
| Jump height | in metres β zero means no jumping |
| Air control | how much steering you keep mid-jump |
| Reset height | falling this far below the start puts the character back |
| Click to move | walk to a tapped point |
| Turn the view with | mouse, keys, or both |
:::note Acceleration is a time, not a speed
"Half a second" means the controller applies whatever push closes the gap in half a second.
Slopes, shoves and mass all argue with that push, which is what makes movement feel physical.
Zero means *as fast as possible* β which is not quite instant, because the character still has
mass.
:::
## The body
The controller gives your object a capsule and drives it with forces, so it collides with the
world properly instead of sliding through it.
| Setting | What it controls |
| ---------------- | ------------------------------------------------------ |
| Shape | capsule or sphere |
| Radius Β· height | in metres |
| Offset | moves the capsule relative to the object |
| Mass | both how hard it pushes and how hard it is to shove |
| Show helper | draws the capsule in the editor |
Already built your own rigid body? The controller leaves it alone and simply steers it, taking
its mass. Otherwise it makes one for you.
:::danger Only declared bodies are solid
Your floors and walls stop the character when they have a rigid body and a collider. Plain
decorative geometry is scenery, and the character walks straight through it.
β [Physics](/creators-engine/authoring/../concepts/physics)
:::
Gravity comes from the space, and jump height is measured against it. A scene with game controls
switches physics on even if the space had it off β otherwise nothing would move at all.
## The follow camera
| Setting | What it controls |
| --------------------- | -------------------------------------------------------- |
| Camera | which camera follows; leave empty for a built-in one |
| Orient to | keep looking at the character, or hold a fixed angle |
| Position Β· aim lag | how far behind the camera runs, in seconds |
| Up-down Β· left-right | free, locked, or limited |
| Limits | how far it may rise or swing, in degrees |
:::tip Frame the shot, do not type it
On the first frame the rig remembers the distance and angles between your camera and the
character, and holds them from then on. There are no distance or height numbers to guess β put
the camera where it looks right in the viewport and it stays there.
:::
A few behaviours are deliberate and worth expecting:
- **The camera turns toward the character rather than chasing it.** There is a little sideways
slack; inside it the camera stays put and rotates, and only slides once the character reaches
the edge. A camera welded to its subject reads as a tripod, not a camera.
- **Lag applies to framing, not distance.** The camera does not drift backwards as the
character speeds up β that reads as an unrequested zoom, and it is what makes soft following
feel queasy.
- **The shot eases around behind the character** while they move and you are not turning the
view yourself. Locking left-right, or using top-down, switches it off.
- **The camera refuses to be blocked.** If something comes between it and the character, it
moves in close and eases back out afterwards. It never comes nearer than half a metre.
- **The wheel zooms**, between roughly a third and two and a half times the distance you framed.
While the rig is driving a camera, that camera's own controls (orbit, fly) are ignored β they
would fight it every frame.
## Touch and gamepad
On-screen controls are drawn only on touch screens, so you can leave them on.
| Setting | What it controls |
| ------------------------- | -------------------------------------------------------- |
| On-screen controls | show a stick and a jump button |
| Turn the view by | swiping the scene, or a second stick |
| Stick corner Β· jump corner| which cell of a 3Γ3 grid they sit in |
| Offset Β· button size | margin from the edge, size in pixels |
| Visibility | hide the graphics β swiping to turn still works |
A gamepad works alongside everything else with no setup at all: left stick moves, right stick
looks, A jumps, bumpers and triggers run.
## Reacting to what the character is doing
The same state change that runs a branch also reaches scripts and patches β a script can listen
for it, and there is a patch node with `pulse` and `state` outputs.
All three hear it at the same moment, so you can drive footstep sounds from a script while the
branches handle animation, and they stay in step.
## Following, without a controller
For something simpler (an enemy trailing the player, a pet, a camera target) there are
**follow**, **look at target** and **stop following** steps.
They move the object directly, so they will not work on something physics is already driving.
For that, use impulses from a script.
---
**Next:** [UI cards](/creators-engine/authoring/ui-cards) β buttons, menus and score displays.
---
# Particles
Source: https://docs.arclip.design/creators-engine/authoring/particles
# Particles
A particle emitter gives you fire, smoke, sparks, dust, rain, magic β anything made of many
small things moving together.
Particles are born on a shape, fly under gravity and drag, and fade out over their lifetime.
All of it runs on the GPU and draws in a **single call**, so a thousand particles cost roughly
what one sprite does.
:::info A fresh emitter already looks like something
The defaults are a fire preset, so you can add an emitter and immediately see what you are
tuning rather than staring at an empty object.
:::
## How many, and for how long
| Setting | Does |
| ----------- | --------------------------------------------------------------- |
| `count` | how many particles exist at once |
| `lifetime` | how many seconds each one lives |
| `emitting` | switch the emitter on and off |
The spawn rate falls out of these two: `count Γ· lifetime` particles per second. A short
lifetime with a high count gives you a dense, fast effect; a long lifetime with the same count
gives you a slow, sparse one.
## Where they are born
| `shape` | Particles start⦠|
| -------- | ------------------------------------------------------- |
| `point` | all from one spot |
| `sphere` | anywhere within `radius` |
| `box` | anywhere within `extents` |
| `cone` | in a cone around the object's up axis, width by `angle` |
`angle` is a half-angle in degrees, so 15 is a tight jet and 180 sprays in every direction.
Cone is the default because it suits fire, jets, sparks and fountains alike.
## How they move
| Setting | Does |
| --------------------- | ------------------------------------------------------------ |
| `speedMin` Β· `speedMax` | starting speed, picked between the two |
| `gravity` | constant pull β negative Y for falling, positive for rising |
| `drag` | how quickly they slow down |
| `turbulence` | how much they wander; this is what stops smoke looking mechanical |
| `spin` | rotation speed |
:::tip Gravity does not have to point down
The default preset pulls particles **upward** β that is what makes fire and smoke rise.
Sideways gravity gives you wind-blown rain or a spray in a draught.
:::
## How they look
| Setting | Does |
| ---------------------------- | ------------------------------------------------------ |
| `sizeStart` Β· `sizeEnd` | size at birth and at death |
| `sizeJitter` | how much sizes vary between particles |
| `colorStart` Β· `colorEnd` | colour over the lifetime |
| `opacityStart` Β· `opacityEnd`| fade in and out |
| `sprite` | an image; its transparency gives the particle shape |
| `blending` | `additive` or `normal` |
**Blending is the setting that decides whether an effect reads as light or as matter.**
`additive` accumulates brightness where particles overlap β fire, sparks, magic, anything
glowing. `normal` layers them like paint β smoke, dust, rain, debris.
Without a sprite you get a soft circle, which is genuinely fine for smoke and sparks.
## A few starting points
| Effect | Roughly |
| ---------- | ------------------------------------------------------------------------------------ |
| **Fire** | cone, narrow angle, upward gravity, additive, yellow to red, shrinking |
| **Smoke** | cone, wide angle, gentle upward gravity, normal blending, grey, growing, fading out |
| **Sparks** | point or cone, high speed, downward gravity, additive, tiny, short lifetime |
| **Dust** | box, near-zero speed, no gravity, high turbulence, normal blending, long lifetime |
| **Rain** | box overhead, downward gravity, high speed, low turbulence |
## Everything is a setting, nothing is state
No particle is ever stored or synchronised. The same settings produce the same look for every
viewer, which means an emitter costs nothing to have in a multiplayer scene and never drifts
out of step between participants.
The `seed` controls the randomness: change it for a different arrangement from the same
settings, keep it and everyone sees the same one.
---
**Next:** [Cameras](/creators-engine/authoring/../concepts/cameras) β what your visitor is looking through.
---
# Patches
Source: https://docs.arclip.design/creators-engine/authoring/patches
# Patches
A patch is behaviour you **draw**. It sits neatly between events, which are simple but cannot
hold a value or do arithmetic, and scripts, which can do anything but ask you to write code.
Reach for a patch the first time you catch yourself wanting "β¦but only if the score is above
ten" or "β¦move it a bit further each time".
:::info Your graph becomes a real script
Patches are compiled, not interpreted, so they run exactly as fast as hand-written code. You
can read the generated script at any time in the code panel β which is also the quickest way to
check that a graph does what you meant.
:::
Every node, with its ports: **[patch node reference](/creators-engine/authoring/../reference/patch-nodes)** β 203 nodes
across 24 categories.
## Four kinds of node
| Kind | How you spot it | What it is |
| ----------- | ------------------------------------- | ------------------------------------------------------- |
| **Event** | only outputs, one of them a pulse | where the graph starts β on launch, on tap, every frame |
| **Effect** | a pulse in and a pulse out | does something, in order |
| **Control** | splits or repeats the pulse | branch, gate, repeat, for-each, once, delay |
| **Data** | no pulses at all | a value β a number, a sum, a component's field |
The **pulse** wire is the order things happen in. Follow it from an event node and you are
reading the sequence. Data wires just carry values; they have no order.
## Ports and wires
Ports are typed (number, boolean, string, vector, colour, object) and mismatched types will
not connect, which catches a lot of mistakes before you run anything.
A **vector** port unfolds into `x`, `y` and `z`, so you can wire one and type the other two.
An input you leave unconnected uses whatever value you typed into the node. That is usually
what you want: **wire only the parts that change.**
## Telling a node which object to use
Component nodes ("get transform", "set material") need to know *which* object.
Leave the binding empty and it means **this object**, the one the patch is attached to. Set it
and the node works on that object instead.
Two conveniences worth knowing:
- **Get nodes are forgiving.** Reading a component an object does not have gives you nothing,
rather than breaking the graph.
- **Set nodes only write what you filled in.** Everything you left alone stays as it was.
:::warning Rotation ports are in degrees
Which matches everything else you can see. Just remember it if you are passing values between a
patch and a script, where rotation is in radians.
:::
## What is in the catalogue
| Category | What you will find |
| --------------------------- | ---------------------------------------------------------------------- |
| events | 30 starting points β launch, frame, tap, keys, collisions, tracking |
| components | read and write any component |
| math Β· vector Β· logic Β· string | arithmetic, comparisons, vectors, text |
| flow | branch, gate, repeat, for-each, once, delay, sequence |
| state | counter, toggle, timer, smooth |
| entities Β· lifecycle | find objects, create, spawn, destroy |
| physics | impulses, forces, velocity, raycast |
| net | multiplayer β send, receive, shared state, spawn |
| globals Β· variables Β· messaging | values shared with other patches and scripts |
| ui | read and write an interface card's variables |
| functions | call another patch, or a script |
## Reusing a patch
Save a graph as a resource and you can drop it onto other canvases. How it behaves depends on
how you built it:
| You built | You get |
| ---------------------------------- | --------------------------------------------------------------------- |
| no input or output nodes | a **behaviour** β drop it in and it runs, no wires needed |
| inputs, outputs and a pulse input | a **step** β it runs when the pulse arrives, outputs ready afterwards |
| inputs and outputs, no pulse | a **function** β its outputs are worked out wherever they are read |
Its sockets come from the input and output nodes you put inside it: add one and it appears on
every caller. Edit the patch and everything calling it updates live.
:::note A function patch has no object of its own
Inside a pure function there is no "this object", so object and component nodes do not belong
there. Keep functions to data, maths and logic, and do the scene work in the caller.
:::
Patches can call **scripts** too, which is the usual way to reach something the catalogue does
not cover.
## Values that outlive the graph
Three ways to keep something, differing in how far it reaches:
| Store | Shared with |
| ------------- | -------------------------------------------------------- |
| **variables** | the nodes of this one patch |
| **globals** | every patch, script and event in the project |
| **messages** | anyone listening, as a one-off notification by name |
Globals come in two scopes: project-wide (they survive moving between spaces) and space-only
(they start fresh). Names are typed in rather than wired, which keeps the routing predictable.
## The escape hatch
Two nodes take JavaScript directly: **expression** returns a value, **action** runs statements.
Everything a script can reach is available inside them.
They are there for the one small thing the catalogue lacks β not as a way of life. If you find
yourself writing a lot in them, write a script and call it from the graph instead.
## When something is not right
Open the code panel. A graph that cannot compile β a loop in the data wires, broken JavaScript
in an action node β tells you there.
And a broken patch never takes the scene down with it: the others carry on running.
---
**Next:** [Game controls](/creators-engine/authoring/game-controls) β let people walk around.
---
# Paths
Source: https://docs.arclip.design/creators-engine/authoring/paths
# Paths
A path is a curve you draw from points. It does two quite different jobs, and it is worth
knowing both:
1. **A shape** β sweep a profile along the curve and you get a pipe, a rope, a road, a railing,
a ribbon, a neon tube.
2. **A route** β send an object along it with the follow-path step: a train, a camera move, a
patrolling guard.
The points are stored relative to the object, so moving, rotating or scaling the object carries
the whole path with it.
## Shaping the curve
| Setting | Does |
| ---------- | ------------------------------------------------------------- |
| `points` | the points the curve runs through |
| `curve` | `smooth` rounds through them, `linear` joins them with straight segments |
| `tension` | how tightly a smooth curve hugs its points |
| `closed` | join the end back to the start β a loop |
## Turning it into a shape
By default a path is invisible β just a route. It becomes geometry in one of two ways.
**As a tube:** give it a `radius` above zero and you get a round tube along the curve.
`radialSegments` controls how round it looks, `tubularSegments` how smooth it is along its
length.
**As a swept profile:** pick a `profile` (circle, rectangle or star) and that outline is
carried along the curve.
| Setting | Does |
| ------------------------- | ----------------------------------------------------------------- |
| `profileSize` | how big the outline is |
| `sides` | corners on a circle or points on a star |
| `ring` | hollows the profile out β 0 solid, approaching 1 a thin shell |
| `arc` | below 360Β° the outline opens into a slice |
| `rotation` Β· `twist` | the profile's angle at the start, and how much it turns by the end |
| `startScale` Β· `endScale` | taper from one end to the other |
| `depth` Β· `offset` | which share of the path is covered, and where it begins |
| `caps` | flat or rounded ends |
:::tip This is where the interesting shapes come from
A hollow rectangle profile with a twist gives you a spiral duct. A star with a ring makes a
cable channel. A circle tapering to nothing produces a horn or a tentacle. `depth` below 1 lets
you animate a shape drawing itself along its own path.
:::
## Using it as a route
A path belongs to **its own object**. To send something along it, put a **Fly along path** step
on the thing that should travel, and point its `path` field at the object carrying the curve.
| Setting | Does |
| ---------- | ------------------------------------------------------------- |
| `path` | the object holding the curve |
| `duration` | how long one pass takes, in seconds |
| `loop` | once, a number of times, or forever |
| `orient` | turn to face along the curve as it goes |
| `reverse` | travel the other way |
**Stop path flight** stops it again.
:::tip A patrol route in three steps
Make an empty object, draw the path on it, then give your guard a **Fly along path** step
pointing at it with `loop: forever` and `orient` on. Several guards can share one path β start
them apart by giving each a different `duration`, or by delaying their start.
:::
Because the curve belongs to its object, moving that object moves the whole route. A patrol path
parented to a moving platform keeps working when the platform moves.
---
**Next:** [Your first script](/creators-engine/authoring/../scripting/first-script) β if you want more control.
---
# Material graphs
Source: https://docs.arclip.design/creators-engine/authoring/shader-graphs
# Material graphs
The standard material types cover most of what a project needs. When they do not, you can build
a material as a **graph** instead: connect nodes, and the editor compiles them into a real
shader.
Reach for one when you want something a fixed material cannot express β a scrolling texture, a
dissolve, a hologram, water, a colour that changes with viewing angle, a flag that ripples.
Every node with its ports: **[shader node reference](/creators-engine/authoring/../reference/shader-nodes)** β 95 nodes.
:::info Every material is already a graph
The built-in types are not a separate system with a graph bolted on β `standard`, `physical`,
`toon` and the rest are themselves points inside this same space. So a graph material is not a
second-class citizen: it goes through the same path, gets the same lighting, and works on mobile.
:::
## How a graph is shaped
A graph runs from **inputs**, through **maths**, into **outputs**.
| Category | What lives there |
| ---------- | ------------------------------------------------------------------------------ |
| **input** | UVs, the surface normal, time, position, textures, which instance is drawing |
| **const** | fixed numbers and colours you type in |
| **math** | arithmetic and curves on single numbers |
| **vector** | building, splitting, mixing and transforming vectors and colours |
| **light** | lighting terms you shape yourself |
| **post** | nodes that sample the rendered frame β see [screen effects](#screen-effects) |
| **output** | what the surface actually looks like |
## The surface outputs
The output node is where your graph ends, and its inputs are the physical description of the
surface:
| Output | Means |
| -------------------- | -------------------------------------------------------------- |
| `baseColor` | the colour of the surface |
| `metallic` | 0 for a dielectric, 1 for bare metal β rarely anything between |
| `roughness` | 0 mirror-smooth, 1 completely matte |
| `normal` | feed your own normal to add surface detail |
| `emissive` | light the surface gives off by itself |
| `alpha` | transparency |
| `clearcoat` Β· `clearcoatRoughness` | a lacquer layer over the top |
| `ior` Β· `transmission` Β· `thickness` | refraction, for glass and liquids |
Anything you leave unconnected keeps its default, and the compiler folds it away β **an output
you do not use costs nothing**.
## Working with instances
Two input nodes exist specifically for [instanced objects](/creators-engine/authoring/../concepts/components):
`instanceId` gives you which copy is being drawn, and `instanceColor` gives you that copy's
tint.
:::tip This is what makes a thousand objects stop looking like one object
Feed `instanceId` into a hash and every copy gets its own tint, size or animation phase β from
a single draw call. A field of grass where every blade sways slightly differently costs the
same as a field where they all sway together.
:::
Both read sensible defaults on an ordinary mesh, so a graph using them never breaks by being
used somewhere uninstanced.
## Screen effects {#screen-effects}
A graph that samples the **rendered frame** is not a surface material at all β it is a
full-screen effect applied over the whole scene. Colour grading, vignettes, blurs, scanlines.
The engine notices this by itself: use a node that reads the frame and the graph becomes an
effect rather than something you put on an object. Several effects on a scene run in the order
you give them.
## Textures and colour space
A texture node samples an image resource. Colour images are decoded for you so the maths works
on real colour values.
:::warning Mark data textures as data
A texture that carries **numbers** rather than colour β a roughness map, a mask, a height
map β must be marked as such, or it will be colour-corrected and your values will be subtly
wrong. This is the single most common cause of "my roughness map looks nothing like it should".
:::
:::warning A script cannot reach into a graph
Scripts have no access to the graph component, so you cannot set a graph parameter from code.
That is less limiting than it sounds, because the two things people want are both covered:
| You want | Do it |
| ------------------------------- | ---------------------------------------------------------------------- |
| Something that animates by itself | use the `time` input **inside** the graph β pulsing, scrolling, shimmering all belong there |
| Something driven by game state | drive an ordinary material slot instead: a script can write `emissiveIntensity`, `color`, `opacity`, `roughness` |
So a beacon that pulses forever is a graph reading `time`; a beacon that brightens when the
player scores is a script writing `emissiveIntensity` on a standard material.
:::
## Practical advice
**Start from a preset.** Duplicating a standard material as a graph gives you a correct,
complete starting point. Building from an empty canvas means re-deriving lighting you did not
need to.
**Watch what you add.** A graph runs for every pixel, every frame. A texture lookup is cheap; a
loop is not. If the frame rate drops after an edit, the last node you added is the suspect.
**Save it as a resource** and every object using it updates at once β which also means one
place to fix when the look is wrong.
---
**Next:** [Particles](/creators-engine/authoring/particles) β fire, smoke, sparks and dust.
---
# States
Source: https://docs.arclip.design/creators-engine/authoring/states
# States
A **state** is a named snapshot of everything an object currently is: where it stands, what
colour it is, whether it is visible, which animation is selected.
Switch to a state and the object becomes that snapshot again. That is the whole feature, and it
replaces a surprising amount of hand-written logic.
## When to reach for one
| You want | States are⦠|
| --------------------------------------------- | ------------------------------------------------- |
| A light switch: on and off | exactly right |
| A door: closed, open, locked | exactly right |
| A product in three colourways | exactly right |
| A character walking a path | wrong β that is [animation](/creators-engine/authoring/animation) |
| Something that changes by a calculated amount | wrong β that is a [patch](/creators-engine/authoring/patches) or script |
The rule of thumb: **a fixed number of named situations** is a state. Anything continuous is an
animation.
## Building them
Add a **States** component to an object, then add a state. It records the object as it stands
right now.
To build the next one: add a second state, then **change the object while that state is
selected**. Position it, recolour it, hide it β the state captures as you go.
:::warning This is the part that confuses people
While a state is selected, editing the object edits **that state's snapshot**, not the object
itself.
That is exactly what you want when building states, and thoroughly confusing when you had
forgotten a state was selected and wondered why your change "did not stick" after switching.
:::
## Switching between them
From an event, use the **Set state** step. It takes the state and, optionally, a duration and an
easing.
**With a duration, the change eases** β the object glides from wherever it is to the snapshot
rather than jumping. That single field is what turns a state switch into an animation you did
not have to author.
| Where you switch from | How |
| --------------------- | ------------------------------------------------------ |
| An event | the **Set state** step |
| A patch | the `set_states` node |
| A script | write `activeStateId` on the States component |
## Reacting to a switch
Two triggers fire on the object whose state changed:
| Trigger | Fires when |
| -------------------- | ----------------------------------------- |
| **State active** | the object entered the state you picked |
| **State inactive** | it left that state |
Each event names **which** state it is watching, so one object can react differently to each of
its states. This is how a door plays one sound opening and another closing without any
conditions.
:::note These fire on the object itself, not its parents
Unlike a tap, a state change does not travel up the hierarchy.
:::
## Changing a few things without a state
Sometimes you want "like now, but red" without defining a whole state for it. That is the
**Override properties** step: it applies a one-off set of values as a temporary state, with the
same duration and easing.
Use it for one-off emphasis (a highlight on hover, a flash on damage) and keep real states for
situations the object genuinely has.
## What a state covers
A snapshot holds the object's **components**, so position, rotation, scale, material,
visibility and the rest all travel together.
:::tip A state belongs to one object
It does not capture children. To switch a group, give each object its own states and switch
them from one event β the step's targets take a list.
:::
## Where they can catch you out
- **A state left selected in the editor** quietly absorbs your edits. Check which one is active
before wondering why an object keeps reverting.
- **States and animation on the same object** fight, like any two animation mechanisms. Pick one.
- **Removing a component** clears it from every state that held it.
- If the selected state no longer exists, the first one is used instead.
---
**Next:** [Patches](/creators-engine/authoring/patches)
---
# UI cards
Source: https://docs.arclip.design/creators-engine/authoring/ui-cards
# UI cards
**All your 2D interface is a card** β HUDs, menus, buttons, labels, dialogue. Not 3D text on a
plane, not a texture with words on it.
Cards are laid out like a normal interface, in front of the 3D view, so they stay crisp at any
distance and on any screen size. Text in a card is readable; text on a plane in the world is at
the mercy of the camera.
A card is a resource, and you attach it to an object.
## Two ways to place one
**Overlay** fills the screen. This is your HUD, your menu, your dialogue box.
**Anchored** pins the card to an object's position on screen β a name over a character, a price
over a product.
:::note An anchored card does not shrink with distance
It stays the size you designed and hides when its object goes behind the camera. It is a label
attached to something, not an object in the world. If you want something that gets smaller as
you walk away, use 3D text instead.
:::
## What you can build
The usual building blocks are all there:
```
container Β· text Β· image Β· gif Β· video Β· separator Β· grid Β· gallery
pager Β· tabs Β· state Β· indicator Β· slider Β· input Β· select Β· switch
```
:::tip There is no button component β and that is fine
A button is a `text` or a `container` with a background, some padding, a border and an action.
It sounds like extra work and it is actually the opposite: you get exactly the button your
design asks for, instead of fighting a default one.
:::
For anything repeated (a row in a list, a chip, a card in a gallery) build a **template**
once and reuse it.
A card can hold several **screens** of its own and switch between them without involving the
scene at all. That is the right way to build a tabbed panel or a multi-step dialogue: the scene
never needs to know.
Custom components and third-party extensions such as Lottie will not render.
## Making a card do something
A card has its own **variables** and can change itself with them β show and hide, swap text,
switch screens, run timers. Anything purely about the interface should stay inside the card.
When it needs to reach the scene, there are three ways, in increasing order of power:
| Way | Use it for |
| -------------------------------- | ------------------------------------------------------------------ |
| A built-in card action | interface state only β a tab, a toggle, a timer |
| A **scene step** on the action | running a scene step with no code: switch scene, play an animation |
| A named action plus a script | game logic |
An action on a card also raises an ordinary event that travels up the hierarchy, so an event on
the scene can catch a press from any card inside it β by name, or any press at all.
## Talking to a script
A script can read a card's variables, write them, and listen for presses:
```ts
const ui = ctx.getDivKit(entity);
ui.set('score', (v) => v + 1);
ui.subscribe('lives', (v) => {
/* the display changed */
});
ui.onAction('restart', () => {
/* the restart button was pressed */
});
```
This is the pattern worth internalising: **the game state lives in the script, and the card
displays it.** Not the other way round.
## Pointing at images and video
Inside a card, point at a project resource rather than a URL. References work inside expressions
too, so picking an image based on a condition behaves the way you would expect.
## Before you start designing
- A card only shows while its scene is active, so interface never leaks between scenes.
- **A screenshot of the 3D view will not include your cards.** Check interface in preview.
- Card variables are not shared between participants and reset on reload. Anything that must be
shared or survive belongs in a script.
- The visual editor shows a curated set of properties but keeps hand-written layouts intact β
they survive editing, they just have no properties panel.
---
**Next:** [Material graphs](/creators-engine/authoring/shader-graphs) β when the standard materials are not enough.
---
# Cameras
Source: https://docs.arclip.design/creators-engine/concepts/cameras
# Cameras
A camera object decides what your visitor sees and how they move around.
## Control modes
| Mode | The visitor⦠|
| ------------- | --------------------------------------------------------------------- |
| `none` | cannot move the camera β you drive it yourself |
| `orbit` | drags to rotate around a target, scrolls to zoom |
| `fly` | flies freely with keys and mouse |
| `firstPerson` | looks around from where they stand, pointer captured |
| `lock` | pointer captured, but the camera keeps following its object |
:::tip `lock` is the one you want for a character
`firstPerson` hands the camera itself to the pointer controls, which **detaches it from its
object** β fine for a viewer walking around a model, useless for a character, because the
camera stops following the body.
`lock` captures the pointer the same way but leaves the camera attached, delivering the mouse
movement to your script instead. That is a first-person game.
:::
## Orbit settings
`orbit` is the default because it suits product viewers, and it has the most to tune:
| Setting | Does |
| --------------------------------- | ----------------------------------------------------- |
| `target` | the point being orbited |
| `enableRotate` Β· `enablePan` Β· `enableZoom` | which gestures are allowed |
| `minDistance` Β· `maxDistance` | how close and how far the visitor may get |
| `minPolarAngle` Β· `maxPolarAngle` | how far they may swing above and below |
| `enableDamping` Β· `dampingFactor` | how much the motion glides after they let go |
| `autoRotate` Β· `autoRotateSpeed` | a slow idle spin |
Limiting the polar angle is worth doing on almost every product viewer: it stops people
orbiting underneath the floor and seeing the scene from below, which no amount of lighting
makes look intentional.
## Lens
`fov` is the field of view in degrees β 50 is a natural default, lower is more telephoto and
flatter, higher is wider and more dramatic.
`near` and `far` bound what gets drawn. Objects closer than `near` or further than `far` are
clipped away.
:::warning A huge far/near ratio causes z-fighting
Surfaces close together start flickering as the depth buffer runs out of precision. If you see
that, raise `near` before you lower `far` β `near` has far more effect on precision.
:::
## Fly settings
`fly` mode moves with keys, and the keys are configurable β including following one of the
project's named bindings instead of naming keys directly. Speed and a boost multiplier (held
Shift by default) control how fast.
## Extra views
An **extra view** renders a second pass from a camera, on top of the main one. This is how you
build a minimap, a rear-view mirror or a security monitor.
Put it on an object that already has a camera, and pick where it goes:
| Target | Renders into |
| --------- | ------------------------------------------------------------------------------- |
| `screen` | a rectangle of the canvas β position and size in fractions of the screen |
| `texture` | an offscreen image, which a material can then display |
The `texture` target is the fun one: render a camera's view into a texture, put that texture on
a screen object in your scene, and you have a working monitor showing another part of the
world.
Extra views draw **after** the main pass, in the order you give them.
:::note Every extra view is another full render of the scene
Two monitors mean the scene is drawn three times. They are worth it where they matter, and
worth switching off where they do not.
:::
## Which camera is active
A scene renders through one camera at a time. A script can ask which one and switch it:
```ts
ctx.camera.entity();
ctx.camera.setActive(securityCam);
ctx.camera.pose();
```
The camera's transform is always the source of truth β write to it to move the camera. Note
that in `orbit` and `firstPerson` the controls own the rotation, so a rotation you write will be
overwritten; position is respected. See [the ctx API](/creators-engine/concepts/../scripting/ctx-reference#camera).
And if [game controls](/creators-engine/concepts/../authoring/game-controls) are driving a camera, that camera's own
control mode is ignored β the rig would fight it every frame.
---
**Next:** [Resources](/creators-engine/concepts/resources) β models, images and sounds.
---
# Components
Source: https://docs.arclip.design/creators-engine/concepts/components
# Components
Components are where an object's settings live. This page explains what they *mean* and points
out the handful of places where the obvious reading is wrong.
Looking for a specific field, its type or its default? That is the
**[component reference](/creators-engine/concepts/../reference/components)**, generated straight from the engine.
## Transform
Position in **metres**, scale as a plain multiplier, rotation in **degrees** everywhere you can
see it.
:::note Scripts see rotation in radians
Every interface (the inspector, patch ports) shows degrees and converts for you. Only when
you read or write rotation *in a script* do you deal with radians. `Math.PI / 2` is your right
angle there.
:::
## Look-at
Points an object at a spot in the world, or along an offset from its own position. Handy for
signs that face the visitor and lights that track a target.
:::warning Look-at owns rotation while it is on
Animating or setting rotation elsewhere will appear to do nothing. Remove the look-at first.
:::
## Geometry
Fifteen built-in shapes β box, sphere, cylinder, capsule, circle, cone, plane, triangle, ring,
torus, three polyhedra, extrude and lathe β plus a reference to imported geometry.
Two are worth knowing about:
- **Extrude** takes an outline you draw and gives it depth. Logos, signs, cut-out shapes.
- **Lathe** takes a profile and spins it around an axis. Vases, bottles, columns.
Give a box a corner radius above zero and it becomes a rounded box.
## Material
How a surface looks. A material is a **list of slots** rather than a single material, with eight
types to choose from and a texture map set per type.
Full treatment: [Materials](/creators-engine/concepts/materials).
## Text
Font size is in **pixels**, where 1000 px works out as one metre in the world. So 72 px is
roughly 7 cm tall β small on a table, invisible across a room.
Text lays out asynchronously, so it appears a beat after the rest of the object. That is
deliberate: the alternative is a frame of text at the wrong size.
## Video and audio
Same shape, different defaults, and the defaults matter:
- **Video** starts **muted** and non-positional. Browsers refuse to autoplay video with sound.
- **Audio** starts **unmuted** and positional β it gets louder as you approach.
Several objects can play the same file independently, so two televisions showing one clip do
not fight over it.
:::danger Do not use the audio component for sound effects
It is a single voice, so playing it again cuts off the first one. Footsteps stutter and gunfire
sounds like one shot. Play effects **from a script** instead β every call starts its own
independent sound. β [ctx.audio](/creators-engine/concepts/../scripting/ctx-reference#audio)
:::
## 3D text
Separate from ordinary text: this one is **extruded geometry**, so it catches light, casts
shadows and can carry a material like any other object. Use it for signage and titles that need
to sit in the world; use ordinary text for anything that needs to stay readable at a distance.
## Skeletons and bones
An imported character arrives with a skeleton and its bones. You rarely touch these directly β
they are what lets a model deform when its animation plays, and what an animation step targets
when you move a single bone.
## Instancer and particles
Two components for "many of something":
- **Instancer** draws one object's geometry thousands of times in a single call β grass,
crowds, asteroid fields. β [Prefabs and instancing](/creators-engine/concepts/prefabs-and-instancing)
- **Particle emitter** gives you fire, smoke, sparks and rain, also in a single call.
β [Particles](/creators-engine/concepts/../authoring/particles)
## Path
A curve drawn from points. It can become geometry (a pipe, a rope, a road) or a route for
something to follow. β [Paths](/creators-engine/concepts/../authoring/paths)
## Camera and extra views
A camera decides what your visitor sees; an extra view renders a second pass for a minimap or a
security monitor. β [Cameras](/creators-engine/concepts/cameras)
## Shader graph
A material built as a node graph, for looks the standard types cannot express.
β [Material graphs](/creators-engine/concepts/../authoring/shader-graphs)
## Prefab
Marks an object as an instance of a saved tree you can reuse.
β [Prefabs and instancing](/creators-engine/concepts/prefabs-and-instancing)
## Shadows and morph targets
**Shadows** β whether an object casts and receives them, applied to everything inside it.
**Morph targets** are the blend shapes a model brings with it: facial expressions, a flag
furling, a character's weight shifting. Each is a weight from 0 to 1, and all of them can be
animated on the timeline.
## Physics components
**Rigid body** decides how the engine moves an object:
- **dynamic** β falls, collides, gets pushed;
- **static** β never moves, but stops everything else;
- **kinematic** β moves only when you move it, and pushes things aside as it goes.
It also carries a **multiplayer ownership** setting:
| Ownership | Means |
| ---------- | ------------------------------------------------------------------------- |
| **shared** | one object for the whole room; everyone sees the crate in the same place |
| **local** | everyone simulates their own copy |
A player character must be **local**. Making it shared moves every participant's avatar when
one person walks.
**Collider** is the shape physics actually uses β box, sphere, cylinder, capsule, plane, or
`auto` to work it out from the geometry.
**Initial velocity** is a starting push, applied once when the body is created rather than
continuously.
**Physics state** is read-only: the engine writes how fast the object is going and whether it
has come to rest, so a script can read them.
How these fit together is in [Physics](/creators-engine/concepts/physics).
## Tags
Little labels a plugin or a script leaves on objects it created, so it can find its own work
later. The editor does not show them. If you generate scenes from code, this is where to mark
what belongs to you.
---
**Next:** [Materials](/creators-engine/concepts/materials) β how surfaces look.
---
# Lights
Source: https://docs.arclip.design/creators-engine/concepts/lights
# Lights
Five types, all sharing a colour, an intensity, a cast-shadows switch and visibility.
| Type | What it does | Also has |
| ------------- | ---------------------------------------------------------------- | -------------------------------- |
| `ambient` | lifts everything evenly; no direction, no shadows | β |
| `directional` | parallel rays, like the sun | β |
| `point` | radiates in all directions from a spot β a bulb, a torch | distance, decay |
| `spot` | a cone β a lamp, a stage light | distance, decay, angle, penumbra |
| `hemisphere` | sky colour from above, ground colour from below | ground colour |
:::note You aim a light by rotating it
Directional and spot lights point along the object's own forward axis β there is no target
field to fill in. Want it to track something instead? Add a **look-at**.
:::
## A rig that works
If you are not sure where to start: **one hemisphere or ambient light, plus one directional
light.**
The directional light gives you shape and shadows; the ambient light stops the shadow side
going completely black. Tune the balance between them and most scenes look right. Add point and
spot lights afterwards for the places that need drama.
Shadows are the expensive part, so switch them on for the one or two lights that matter rather
than all of them.
## What animates smoothly
When something changes inside a transition β a state switch with a duration, an animated
step β these ease rather than jump:
| Where | What eases |
| ------------ | ------------------------------------------------------------------------------------ |
| **Transform** | position, rotation, scale |
| **Material** | opacity, colour, emissive colour and intensity, metalness, roughness, and the physical extras |
| **Light** | colour, intensity, distance, decay, cone angle, penumbra, ground colour |
| **Text** | font size |
| **Media** | video and audio volume |
Everything else switches at the end of the transition instead of easing into place. Worth
knowing: **chroma-key settings do not ease** β they snap.
## Coming from an older project
Two material kinds, Lambert and Phong, no longer exist β they belong to a lighting model this
renderer does not use.
Your project still opens. Materials using them are converted to `standard`, with shininess
turned into the matching roughness. Their specular colour is not carried over, so a very shiny
old material is worth a second look after you open it.
---
**Next:** [Resources](/creators-engine/concepts/resources) β models, images, sounds and how objects point at them.
---
# Loading and the launch screen
Source: https://docs.arclip.design/creators-engine/concepts/loading
# Loading and the launch screen
Your visitor waits once, at the start. What they wait *for* is a choice you make, and it is the
single biggest lever on how your experience feels to open.
## Three ways an asset can arrive
| | What happens | Good for |
| --- | --- | --- |
| **Preloaded** | fetched before the scene opens; the launch screen waits for it | anything visible in the first seconds |
| **Deferred** | the scene opens without it; it appears when it lands | content further into the experience |
| **Streamed** | played straight off the network, never fetched whole | video and audio |
:::note Deferring is not streaming
Only **video and audio** can start playing before they have fully arrived. A model, a texture or
a font still has to arrive complete before anything can use it β deferring one only means the
scene opens without waiting, and the asset pops in when it is ready.
:::
## The project default, and per-asset overrides
The project setting **Preload** is the default for everything. Each asset can then override it:
| The asset says | Result |
| -------------- | --------------------------------------------- |
| preload **on** | always waited for, whatever the project says |
| preload **off**| always deferred |
| inherit | follows the project |
New assets start sensibly: **video and audio default to streaming** β waiting for a whole video
before the scene opens buys nothing β and everything else follows the project.
:::tip The choice belongs to the asset, not to the object using it
Two objects sharing one file can no longer disagree about how it loads. Set it once on the file.
:::
### A practical recipe
For most projects:
1. **Preload** the first scene's models, its textures, and any font you show immediately.
2. **Defer** everything belonging to later scenes.
3. **Stream** all video and audio, which is already the default.
The result is a short wait followed by an experience that never blocks again.
## The launch screen
Before the world exists, two screens can appear: the one the visitor **taps to begin**, and the
one that **fills up while assets arrive**.
Both are configured in project settings β background, logo, the start button and its label, the
onboarding prompts, the loader style.
:::warning Keep the start button if you have any sound
Browsers refuse to play audio until the visitor interacts with the page. AR permissions work the
same way: they are granted inside the tap that asked for them. Without a button to press, your
first sound silently does not play and the camera prompt may never appear.
:::
## Replacing it with your own
The built-in screen is a preset. If you need your own, a **launch script** owns both screens
outright β and, unusually, it also declares its own settings panel, so whoever uses it gets
fields to fill in rather than code to edit.
```ts
init((ctx: PreloadContext) => {
// Declare the panel. Runs before anything is drawn β declare only, never draw.
ctx.on('describe', (p) => {
p.section('look', { en: 'Look' });
p.color('tint', { label: { en: 'Background' }, default: '#101014', section: 'look' });
p.text('caption', { label: { en: 'Caption' }, default: '', localized: true, section: 'look' });
p.boolean('hideWhenReady', { label: { en: 'Hide when ready' }, default: true });
});
ctx.on('launch', () => {
/* the screen they tap to begin */
});
ctx.on('loading', () => {
/* the screen that fills up */
});
ctx.on('progress', ({ loaded, total, percent }) => {
/* update your own bar */
});
ctx.on('ready', () => {
/* everything has arrived */
});
});
```
| Event | Fires when |
| ---------- | ------------------------------------------------------------------ |
| `describe` | the panel is being built β in the runtime and in the editor alike |
| `start` | the runtime is starting up |
| `launch` | show the screen the visitor taps |
| `loading` | show the screen that fills up |
| `progress` | assets are arriving β `loaded`, `total`, `percent` |
| `ready` | everything needed has arrived |
| `error` | something went wrong |
Parameters can be **localised**, so one launch script serves every language your project ships.
:::note Start the scene from inside the tap
Whatever you use to begin must call through from the button's own click handler. Permissions β
device orientation, the camera β are granted only inside the gesture that asked for them, and a
start deferred to the next frame loses that right.
:::
:::tip A broken launch script cannot lock your visitors out
If it throws, the runtime drops the preset and shows the built-in screen instead.
:::
## What does not wait
Physics is not downloaded at all unless a scene needs it, and the arrangement of instanced
objects and particles is generated rather than transferred. Both are covered in
[why this engine](/creators-engine/concepts/../why-this-engine) β they are part of the same "do not make people wait
for what they do not need" idea.
---
**Next:** [The shared packages](/creators-engine/concepts/shared-packages)
---
# Materials
Source: https://docs.arclip.design/creators-engine/concepts/materials
# Materials
A material decides how a surface looks. Every field, with its range and default, is in the
generated [component reference](/creators-engine/concepts/../reference/components); which maps each type accepts is in
[material maps](/creators-engine/concepts/../reference/material-maps). This page is how to use them.
## A material is a list of slots
Not one material β a **list**. Slot order follows the submeshes of an imported model, so slot 1
paints the first group, slot 2 the second. That is how one chair is wood and fabric.
Objects you build from a primitive have a single slot, and that is usually all they need.
:::note Slots are remembered by identity, not position
Reordering them does not rebuild anything, and a slot keeps its settings when you move it.
:::
## Choosing a type
| Type | Reach for it when |
| ----------- | -------------------------------------------------------------- |
| `standard` | you want it to look real β **the default choice** |
| `physical` | you need glass, varnish, a clear coat, refraction |
| `basic` | you want flat colour that ignores lighting |
| `toon` | you want stylised, banded shading |
| `normal` | you are debugging a surface |
| `chromakey` | you have video with a colour to remove β a green screen |
| `occluder` | you want an invisible shape that hides things behind it |
| `ref` | you want to reuse a material saved as a resource |
Switching type changes which settings exist, so the panel shows fewer or more fields.
## The settings every type shares
| Setting | Does |
| ------------- | ---------------------------------------------------------------- |
| `color` | the base colour |
| `opacity` | 0 to 1 |
| `transparent` | must be on for opacity below 1 to have any effect |
| `side` | which faces are drawn: front, back or both |
| `wireframe` | draw the edges only |
| `visible` | draw it at all |
## What each type adds
| Type | Also has |
| ----------- | -------------------------------------------------------------------------------------- |
| `standard` | `metalness`, `roughness`, `emissive`, `emissiveIntensity` |
| `physical` | all of the above plus `clearcoat`, `clearcoatRoughness`, `transmission`, `thickness`, `ior` |
| `toon` | `emissive`, `emissiveIntensity`, and a gradient ramp |
| `normal` | `flatShading`, and no colour |
| `chromakey` | `keyColor`, `similarity`, `smoothness`, `spill` β transparent by default |
| `occluder` | only `side` |
:::tip Roughness does most of the work
If a surface looks wrong, adjust roughness first. Metalness is close to binary in reality β
something either is bare metal or it is not β and values in between mostly read as mistakes.
:::
### The physical extras
`clearcoat` is a lacquer layer over the top: car paint, varnished wood, a phone screen.
`transmission` with `thickness` and `ior` is how you get glass and liquid β light passing
through and bending rather than bouncing off.
:::note They cost nothing until you use them
Left at their defaults they are folded away at compile time, so `physical` runs exactly as fast
as `standard`. Turning transmission on does cost real work β the browser draws the scene twice β
so use it where it earns its place.
:::
## Texture maps
Every map is **the image plus its options**. Setting just the image is usually enough; the
options are where you fix a texture that tiles wrongly or looks stretched.
| Option | Controls |
| -------------------------- | ------------------------------------------------------- |
| `repeat`, `offset`, `center` | how it tiles and where it sits |
| `rotation` | in degrees |
| `wrapS`, `wrapT` | repeat, clamp or mirror at the edges |
| `magFilter`, `minFilter` | how it is sampled close up and far away |
| `anisotropy` | 1β16 β sharpness at grazing angles |
| `generateMipmaps` | smaller versions for distance |
| `colorSpace` | how the image's values are interpreted |
| `channel` | which UV set to use, 0β3 |
| `flipY`, `premultiplyAlpha`| orientation and transparency handling |
:::warning Colour space is the setting that quietly ruins materials
An image carrying **colour** and one carrying **numbers** β roughness, a mask, a height map β
must be interpreted differently. Get it wrong on a roughness map and the surface looks subtly,
inexplicably off. If a map is not a picture, it is data.
:::
Which maps each type accepts: [material maps](/creators-engine/concepts/../reference/material-maps).
## Reusing a material
Two different mechanisms, and the difference matters:
| You want | Use |
| ------------------------------------------ | ------------------------------------------------------------ |
| A starting point you then tweak per object | **Replace from library** β it **copies** the values |
| One material shared, edited in one place | a **`ref` slot** pointing at a material resource |
The library has three sources: this **project**, your **team**, and the platform's
**creators hub**. Applying from any of them copies; editing afterwards affects only that object.
:::tip If you will want to repaint everything later, use a reference
Copying is right for "start from this and diverge". A reference is right for "these forty
objects are the same material" β change the resource once and all forty follow.
:::
## Two special ones worth knowing
**Occluder** is the AR trick: an invisible shape that hides whatever is behind it. Put one where
a real pillar stands and your virtual content disappears behind it properly, which is most of
what sells the illusion.
**Chroma key** removes a colour from a video β a green screen, so a filmed person can stand in
your scene without a rectangle around them. Tune `similarity` until the background goes,
`smoothness` to soften the edge, and `spill` to take the green tint off the subject's edges.
## Beyond the fixed types
If none of this expresses the look you need, build the material as a graph instead. The
built-in types are points in the same system, so a graph is not a downgrade.
β [Material graphs](/creators-engine/concepts/../authoring/shader-graphs)
---
**Next:** [Physics](/creators-engine/concepts/physics)
---
# Objects and scenes
Source: https://docs.arclip.design/creators-engine/concepts/objects-and-scenes
# Objects and scenes
Everything in your project is built from two ideas: things made of components, and scenes that
decide when those things appear. This page covers both.
:::info "Object" and "entity" are the same thing
The editor calls them **objects**, because that is what they look like on screen. The engine
and the scripting API call them **entities**. There is no difference β this page uses
*entity* where the distinction from components matters, and *object* everywhere else.
:::
## Two relationships, and they are not the same
Almost every misunderstanding of this engine comes from mixing up two things that both look
like "inside".
**An entity is MADE OF components.** Components are not children β they are the parts that
decide what the entity *is*. One of each kind, at most.
```
Entity "Lamp"
βββ Meta its name, whether it is on, whether it is visible
βββ Transform where it is, how it is turned, how big
βββ Model which 3D model to draw
βββ Events what it reacts to
```
**An entity can CONTAIN other entities.** Those are children, and they are entities in their
own right β each with its own components.
```
Entity "Lamp"
βββ Entity "Bulb" β a separate entityβ¦
βββ Transform β β¦with its own components
βββ Light
```
Put together, a real scene looks like this β components on the left of each entity, children
indented under it:
```
Entity "Living room" β Anchor β this one is a SCENE: it has an anchor
β
βββ Entity "Lamp" β Transform β Model β Events
β βββ Entity "Bulb" β Transform β Light
β
βββ Entity "Table" β Transform β Model β RigidBody β Collider
```
Everything in that picture is the same kind of thing: an **entity**. What makes one a scene,
one a lamp and one a physics body is only which components it carries.
:::note Why this matters in practice
Moving the lamp moves the bulb, because the bulb is *inside* it. Removing the lamp's model
component does **not** remove the bulb β that is composition, not containment.
If you ever find yourself asking "should this be a component or a child object?", the answer is:
a component when it is an aspect of this thing, a child when it is a separate thing that should
move with it.
:::
## An entity is whatever its components say
There is no menu of object types to pick from. You build what you need by combining components:
| Give an entityβ¦ | β¦and you get |
| -------------------------------- | -------------------------- |
| geometry + material | a visible shape |
| a model reference | an imported 3D model |
| a light component | a light |
| a text component | a piece of text |
| a rigid body + a collider | something physics can move |
| an anchor | **a scene** |
| a script or a patch | something with behaviour |
Say you want a bouncing ball. Start with a sphere geometry and a material β now you can see it.
Add a rigid body and a collider, now it falls and bounces. Add a script, now it does
something when it lands. Each component adds one capability, and removing it takes that
capability away again.
### The three shapes you will meet
The same entity model produces three things that feel different but are not:
| What it is | Has | Does not have |
| ------------------------- | ------------------------------------------ | ------------------------ |
| **A scene** | an anchor, and children | a transform |
| **An ordinary object** | a transform, plus whatever it needs | an anchor |
| **Space-level behaviour** | a script or a patch, and **no parent** | a transform, an anchor |
A scene has no transform because it has nowhere to be: its position comes from the real-world
thing it is anchored to. A space-level carrier has none because it is not in the world at all β
it is just logic that keeps running.
:::note One component of each type per entity
Need three materials on one model? Use the **slots** inside its single material component.
Need two of anything else? Use child entities β a lamp with two bulbs is a lamp entity with two
children.
:::
Every entity also has a **name** (how you find it) and a **parent**.
## Enabled and visible are different
These two look alike in the inspector and behave nothing alike.
**Disabled** takes the entity out of the running experience. Not drawn, not tappable, scripts
stopped, events silent. As if it were not there.
**Invisible** only stops it being drawn and tapped. It is still there: scripts keep running,
physics keeps colliding.
So: an invisible wall that still blocks the player? **Invisible.** A prize that appears later?
**Disabled** until you need it.
## Parents and children
Entities form a tree. Move a parent and its children come along, keeping their positions
relative to it β which is how you build anything with parts. A car body with four wheels
parented to it moves as one car.
An entity with no parent sits at the root of its scene.
:::warning Deleting an object deletes everything inside it
Children go too, and their children. If you want to keep one, drag it out first.
:::
You cannot make an entity a child of its own descendant β the editor refuses rather than
tangling the tree.
## Scenes and anchors
A **scene** is an entity with an **anchor** on it, and the anchor answers one question: *when
should this appear?*
| Anchor | Your scene appears when⦠|
| ----------- | -------------------------------------------------------------- |
| `image` | the camera recognises a printed picture |
| `qr` | a QR code is scanned |
| `geo` | the visitor is at a place |
| `face` | a face is detected |
| `surface` | the visitor places it on a floor, wall, ceiling or table |
| `panorama` | a 360Β° environment opens |
| `3d` | straight away β a normal 3D scene, no AR involved |
:::info VPS is not its own anchor
Positioning against a scanned map of a real location is an **option on the surface anchor**,
not a separate type. Set up a surface scene, then switch VPS on.
:::
### Several scenes, one marker
Scenes sharing the same trigger (the same image, the same QR code, the same location) form a
**group**, and only one scene in a group shows at a time.
That is how a story works: the same poster opens the first chapter, and later opens the second,
because you switched which scene in the group is active.
Showing and hiding on detection is just a pair of events the editor sets up for you
(`on-detect` shows, `on-lost` hides). They are ordinary events, so you can change them β for
instance to keep content on screen after the marker leaves the frame, which visitors usually
prefer to content that vanishes the moment their hand shakes.
### Surface scenes have three separate settings
They get mixed up a lot, so here they are side by side:
- **Which surfaces to accept** β floor, wall, ceiling, table.
- **How content is placed** β automatically on a detected surface, or where the visitor taps.
- **Which tracking engine** β only relevant on the web; native apps always use the platform's
own.
## Behaviour that outlives a scene
Give an entity behaviour and **no parent**, and it stops belonging to any one scene β it
belongs to the whole space. It keeps running while scenes come and go, and it hears every event
that happens anywhere.
That is where a score counter, a global controller, or "remember what the visitor picked"
belongs. A script inside a scene stops the moment that scene does.
## How the editor labels objects
You will see labels like *model*, *primitive*, *text*. The editor works out which by checking
what the object carries, in this order:
```
splat reference β splat
model + skinning β animated model
model β static model
video β video β checked before geometry
text β text
audio β audio
light β light
geometry β primitive
anything else β object
```
This explains a small surprise: a plane playing a video is labelled *video*, not *primitive*.
It also explains why material and geometry can only be edited on primitives and static models.
---
**Next:** [Components](/creators-engine/concepts/components) β what each one actually controls.
---
# Physics
Source: https://docs.arclip.design/creators-engine/concepts/physics
# Physics
Physics is opt-in and mostly automatic: the floor is solid without you asking, and things you
mark as dynamic fall. The rules below explain the two or three cases where it does not behave
the way people expect.
## What counts as solid
Something is solid if **you gave it a rigid body**, or if **it has a shape and nothing above it
already claimed one**. The floor stops you because it is a floor β you do not have to remember
to mark it.
Working up from an object:
| The object has⦠| Result |
| --------------------------------------- | ----------------------------------------------- |
| a rigid body | it is its own body |
| a rigid body set to **none** | nothing here is solid, including its children |
| a collider β even an invisible one | solid |
| geometry, and it is visible | solid |
| geometry, but it is hidden | not solid |
| none of the above (a group, a light) | not solid |
Two things follow from this, and both are useful:
**A collider means you meant it, so it stays solid while invisible.** That is exactly how you
build an invisible wall: a collider and no geometry. Visitors bump into nothing at all.
**Geometry is only solid while you can see it.** Hide a decorative shape and it stops blocking
things too.
:::note Hiding part of an assembled body does not remove it
Once a shape has joined a larger body, it stays in it even when hidden. To take it out, remove
the object or set its rigid body to **none**.
:::
## Where collision shapes come from
Give an object a collider and that is what it uses. Without one, the engine works a shape out
from the geometry:
| Geometry | You get |
| ------------------------------------ | ---------------------------------- |
| box, sphere, cylinder, capsule, cone | the matching shape |
| the three polyhedra | an exact hull |
| plane, circle, ring, triangle | a thin double-sided slab |
| torus | a ring of overlapping spheres |
| extrude, lathe | an exact mesh shape |
| an imported 3D model | **nothing β see below** |
:::danger An imported model is not solid until you give it a collider
This is the most common physics surprise by a wide margin. Drop a GLB into a scene, mark it
dynamic, and it falls through the world. Add a collider β a box or a capsule is nearly always
the right answer β and it behaves.
:::
:::warning Exact mesh shapes are for things that never move
A mesh shape (from extrude, lathe or imported geometry) is fine for a static floor or wall. On
anything that moves it passes through things and jitters. Use a box, sphere, capsule or
cylinder instead β simple shapes are also far faster.
:::
Scale is baked into the shape, so scaling an object scales what it collides with.
## Moving a physics body
:::danger Setting a dynamic body's position does nothing
Physics owns where a dynamic body is, and writes over your change on the next step. It looks
like the editor is ignoring you.
Move it with an **impulse** or a **force**, and put it somewhere with a **teleport** β
respawns, checkpoints, "back to the start".
:::
From a script that is `ctx.physics.teleport(...)` and `ctx.physics.applyImpulse(...)` β
see the [ctx reference](/creators-engine/concepts/../scripting/ctx-reference#physics). If you want a walking character,
[game controls](/creators-engine/concepts/../authoring/game-controls) handle all of this for you.
Reading how fast something is going is cheap and lags by about a frame β perfect for "am I
moving?", not for exact maths.
## World settings
Gravity, default friction, bounciness and solver quality are set per project and can be
overridden per space. Changes apply immediately β no restart.
An individual body can override friction and bounciness; leave them alone and it inherits the
world's.
## Collisions
When two things touch, both are told: an `on-collide` event fires on each, and each learns what
it hit. In an event you can narrow that to specific objects, or leave it open.
:::note A collision does not travel up to parents
Unlike a tap, it fires only on the objects that actually touched. Put the event on the thing
doing the colliding.
:::
## When physics runs at all
| Where | Physics? |
| ------------------- | -------------------------------------------- |
| Player (web) | yes |
| ARClip app (mobile) | yes |
| **Editor** | **no** β nothing falls while you are working |
Even in the player, the engine only starts physics when a scene needs it: something dynamic or
kinematic, game controls, or a body plus a script. A scene of static scenery does not pay for
it β on the web that saves your visitors a multi-megabyte download.
The check is live, so a ball a script creates mid-session brings physics up for itself.
---
**Next:** [Lights](/creators-engine/concepts/lights) β and what can animate smoothly.
---
# Prefabs and instancing
Source: https://docs.arclip.design/creators-engine/concepts/prefabs-and-instancing
# Prefabs and instancing
Both let you have many of something. They solve completely different problems, and choosing the
wrong one is a common source of either tedium or slow scenes.
| | **Prefab** | **Instancing** |
| --- | --- | --- |
| Copies | a tree of objects | one object's geometry |
| Each copy | fully independent, editable | drawn only, not editable |
| Good for | a chair used in twelve rooms | ten thousand blades of grass |
| Edit once, update everywhere | yes | n/a β they are all the same object |
## Prefabs
A prefab is a **saved tree of objects** you can drop into scenes again and again: a lamp with
its light and its glow, a door with its frame and its sound, a fully rigged character.
Change the prefab and every instance of it updates. That is the whole point β you fix the door
handle once, not in twelve rooms.
Each instance is a real part of your scene: you can move it, and you can change things on it
that are specific to where it sits.
:::tip Build the second one as a prefab, not the fifth
The moment you catch yourself copying a small group of objects for the second time, make it a
prefab. Retrofitting one after you have twelve scattered copies means reconciling twelve sets
of small differences.
:::
## Instancing
Instancing draws **one object's geometry many times in a single call**. A thousand copies cost
roughly what one does, which is what makes grass fields, crowds, asteroid belts and forests
possible at all.
Three ways to arrange the copies:
| Source | Arranges them |
| ---------- | ---------------------------------------------------------------------- |
| `grid` | on a regular lattice, with optional jitter to break up the regularity |
| `scatter` | loosely through a box or a sphere |
| `children` | one copy per child object, at each child's position |
The `children` source is the "make this fast" option: place things by hand where you want them,
then switch it on and they collapse into a single draw. The children stop drawing themselves
while it is on.
### Making copies look different
Copies are the same geometry, so without help they look identical and read as wallpaper. Three
settings break that up:
| Setting | Effect |
| ------------------------- | ----------------------------------------------- |
| `scaleMin` Β· `scaleMax` | each copy takes a random size between them |
| `rotationJitter` | each copy gets a random spin |
| `colorA` Β· `colorB` | each copy takes a tint between the two |
For more than that, a [material graph](/creators-engine/concepts/../authoring/shader-graphs) can read which copy is
being drawn and vary anything you like from it β swaying at a different phase, a different
worn-ness, a different pattern.
:::note The arrangement is generated, never stored
A count, a shape and a `seed` produce the same arrangement everywhere β in the editor, on a
phone, for every participant in a multiplayer room. Nothing is synchronised, so ten thousand
copies cost nothing on the network.
Change the `seed` for a different arrangement from the same settings.
:::
`maxCount` is a safety rail: copies beyond it are not drawn, so a slipped digit cannot try to
allocate an impossible amount of memory.
---
**Next:** [Loading and the launch screen](/creators-engine/concepts/loading)
---
# Resources
Source: https://docs.arclip.design/creators-engine/concepts/resources
# Resources
A **resource** is anything you upload or create once and use in many places.
| Resource | Used by |
| -------- | ---------------------------------------------------- |
| model | a 3D object |
| image | a texture, or a picture in an interface card |
| video | a video surface, or a card |
| audio | a sound source, or one-shot sounds from a script |
| font | text |
| material | reused across objects |
| geometry | reused across objects |
| prefab | a saved tree of objects you can drop in again |
| splat | a Gaussian-splat capture |
| card | an interface layout |
| patch | a visual graph you can reuse and call |
| script | TypeScript behaviour |
## Folders and paths
Resources live in folders, just like files. A **path** is the folder names joined with slashes:
```
Models/Trees/oak.glb
```
Names are unique inside a folder, so a path always points at exactly one thing.
Scripts import each other by path, which means **renaming or moving a resource rewrites the
imports** in every script that used it. You do not have to go and fix them by hand.
:::note Imports bring their dependencies along
A model that arrives with its own textures tucks them underneath itself. Move or delete the
model and they follow β you never manage them one by one.
:::
## Objects point at resources, they do not contain them
A model object holds a *reference* to a model. A material's colour map holds a reference to an
image. A text object holds a reference to a font.
This is what makes swapping cheap: change what a reference points at and **everything using it
updates at once**. Repaint every sign in your scene by editing one material.
It also explains a symptom: a missing resource shows up as an object that exists but does not
draw, rather than as an error message.
## Loading happens while the scene runs
Resources arrive after the scene has already opened, which you will notice:
- a model can pop in a moment after the scene starts;
- a texture can be briefly absent on a finished-looking material;
- text appears a beat late, because it lays itself out asynchronously.
For anything that has to be on screen at the very first frame (a title card, a logo) avoid
depending on a large download, or keep the scene hidden until the content has arrived.
---
**Next:** [Cameras](/creators-engine/concepts/cameras) β what your visitor looks through.
---
# The shared packages
Source: https://docs.arclip.design/creators-engine/concepts/shared-packages
# The shared packages
The editor, the runtime and your own code are built from the same packages, and several of them
are available to you. Which ones depends on where your code runs.
| Package | Gives you | In a script | In a plugin | In a component extension |
| ---------------- | -------------------------------------------------- | :---------: | :---------: | :----------------------: |
| `@was/ecs` | entities, components, the world | β | β | β |
| `@was/engine` | the component classes | β | β | β |
| `@was/signals` | the reactivity system | β | β | β |
| `@was/svdt` | schemas and validation | β | β | β |
| `@was/utils` | small helpers | β | β | β |
| `@was/ui` | the editor's own component library | β | β | β |
| `@was/icons` | the editor's icon set | β | β | β |
| `react` | React 19 | β | β | β |
:::note Scripts get reactivity through `ctx.effect`
A script cannot import the signals package directly β `ctx.effect` is the same mechanism with
the lifetime managed for you, so an effect dies with its object instead of leaking.
:::
## `@was/signals` β the reactivity system
This is the thing the whole platform is built on: values that know who is reading them, so a
change updates exactly what depended on it and nothing else.
```ts
import { signal, computed, effect, batch, untracked } from '@was/signals';
const score = signal(0);
const doubled = computed(() => score.value * 2);
const stop = effect(() => {
render(score.value); // re-runs only when score changes
});
batch(() => {
score.value += 1;
score.value += 1; // effects run once, not twice
});
stop();
```
| Function | Does |
| --------------------- | ------------------------------------------------------------------ |
| `signal(v)` | a value that can be watched; read and write `.value` |
| `computed(fn)` | a derived value, recalculated only when what it reads changes |
| `effect(fn)` | runs now, and again whenever what it read changes; returns a stop |
| `batch(fn)` | group writes so watchers run once at the end |
| `untracked(fn)` | read without becoming a dependency |
| `ref(obj)` | make a whole object reactive, down to its leaves |
| `snapshot(obj)` | a plain, non-reactive copy |
| `raw(obj)` | the underlying object, without tracking |
| `readonly(obj)` | a view that cannot be written |
For React panels there is a companion with `useSignal`, `useComputed`, `useSignalEffect` and
`useLiveSignal`, so a component re-renders from a signal without any wiring.
:::tip Why this matters even if you never import it
This is what makes the editor and the runtime behave the way they do: nothing polls, nothing
re-renders speculatively, and a panel updates because the data changed rather than because
something told it to. β [Why this engine](/creators-engine/concepts/../why-this-engine)
:::
## `@was/ui` β the editor's components
Plugin panels and component extensions can be built from the same library the editor uses, so
they look native instead of like an embedded web page. Thirty-odd components:
```
Accordion Β· Avatar Β· Button Β· Card Β· Checkbox Β· Chip Β· CloseButton
DimensionInput Β· Draggable Β· Dropdown Β· Flag Β· Icon Β· IconButton Β· Input
Menu Β· Modal Β· Outside Β· Panel Β· Popover Β· Portal Β· Render Β· Scroll
Search Β· Section Β· Segmented Β· Select Β· Skeleton Β· Switcher Β· Tabs
Toast Β· Toolbar Β· Tooltip
```
Plus `@was/icons` for the icon set.
:::warning The stylesheet is prebuilt
Panels use a fixed set of utility classes shipped with the library. Arbitrary values like
`text-[13px]` are not in it β use an inline `style` for sizes outside the scale.
:::
## `@was/ecs` β the world model
`Entity`, `Component`, `Space`. A plugin reads and changes a scene with exactly the same API the
engine uses internally; there is no separate, weaker "plugin API".
### Space β the world
| Call | Returns |
| ------------------------------------- | ------------------------------------------------------ |
| `getEntity(id)` | one entity, or nothing |
| `hasEntity(id)` | whether it exists |
| `getEntities()` | all of them |
| `queryEntities(A, B, β¦)` | every entity carrying all of those components |
| `makeQueryEntities(A, B, β¦)` | the same query, pre-built, for repeated use |
| `createEntity(id?, components?)` | a new entity, added to the world |
| `addEntity(β¦e)` Β· `removeEntity(β¦e)` | put one in or take it out |
| `getSystem(S)` Β· `hasSystem(S)` | reach a system |
| `execute()` | run one frame |
:::tip `queryEntities` is the one to reach for
It is backed by an index, so asking for "everything with a light and a transform" is cheap.
`getEntities()` is not the same thing β it hands you the whole world and makes you filter.
:::
### Entity β a thing in the world
| Call | Does |
| ----------------------------- | --------------------------------------------------------------- |
| `getComponent(Type)` | one component, or `null` |
| `getComponents(A, B)` | several at once, in that order |
| `hasComponent(A, B)` | whether it carries all of them |
| `addComponent(β¦c)` | add; adding a type it already has is ignored |
| `removeComponent(β¦c)` | remove, by class or instance |
| `component(fn)` | run `fn` for every component, now and in future |
| `clone()` Β· `clean()` | copy it, or strip it bare |
| `.id` Β· `.components` | its id, and its components keyed by type |
### Component β the data
| Call | Does |
| ----------------------- | --------------------------------------------------------------------- |
| `.x` or `get('x')` | read a field; inside an effect this also subscribes to it |
| `$data` | the whole thing as a plain object |
| `$rawData` | the stored object, without subscribing β read-only |
| `update({ β¦ })` | **the only way to write** |
| `updateAt(path, value)` | write deep inside a large component without re-validating all of it |
| `version(field?)` | a counter that ticks when something changes, so you can depend on a field **without reading it** |
| `reset(data?)` | back to defaults |
| `clone()` | a copy |
:::note `version()` is the trick behind big-list performance
Reading a large value subscribes you to every leaf inside it. Depending on its version counter
instead means you hear *that* it changed without watching all of it β which is how a panel
survives a list of twenty thousand items.
:::
## `@was/engine` β the component classes
Every component type as a class: `TransformComponent`, `MaterialComponent`, `RigidBodyComponent`
and the rest. You import the class and hand it to `getComponent`, `hasComponent` or `query`.
The full list with every field: [component reference](/creators-engine/concepts/../reference/components).
## `@was/svdt` β schemas
The validation layer components are described with. Compiled rather than interpreted, which is
why parsing is not a cost on the animation path.
```ts
import { s, compile } from '@was/svdt';
const schema = s.object({ speed: s.f64(1), name: s.string('') });
const codec = compile(schema); // compile once, at declaration β never per call
const value = codec.parse(input);
```
### Describing a shape
Numbers: `s.f32 s.f64 s.i8 s.u8 s.i16 s.u16 s.i32 s.u32`, each taking a default.
Scalars: `s.bool`, `s.string`, `s.color`, `s.literal`, `s.enum`, `s.unknown`, `s.ref`.
Vectors: `s.vec2`, `s.vec3`, `s.mat4`.
Composites: `s.object`, `s.variant`, `s.array`, `s.record`, `s.union`, `s.preprocess`, `s.lazy`.
Chainable on any of them: `.default(v)`, `.optional()`, `.nullable()`, `.min(n)`, `.max(n)`,
`.int()`.
### What a compiled codec gives you
| Call | Does |
| -------------------- | ------------------------------------------------------------- |
| `parse(input)` | validate and normalise, throwing on bad input |
| `safeParse(input)` | the same, returning success or an error instead of throwing |
| `parseAt(path, v)` | validate one field without touching the rest |
| `equals(a, b)` | deep comparison, generated for this shape |
| `diff(a, b)` | what changed |
| `apply(target, p)` | apply a diff |
| `invert(p)` | reverse one β the basis of undo |
| `pack` Β· `unpack` | to and from a compact binary form |
And for inspecting a schema rather than data: `introspect`, `keys`, `requiredKeys`.
## `@was/utils`
Small things everything uses: `pick`, `omit`, `assign`, `keys`, `values`, `entries`,
`capitalize`, `basename`, `extname`, `dispose` (glue cleanup functions together), and the MIME
helpers behind upload validation.
---
**Next:** [Glossary](/creators-engine/concepts/../glossary)
---
# Glossary and units
Source: https://docs.arclip.design/creators-engine/glossary
# Glossary and units
Words this section uses in a specific way, and every unit in one place. Worth a skim before you
read anything else β several of these terms mean something narrower here than they do
generally.
## Terms
| Term | What it means here |
| -------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Project** | the top container: spaces, resources, settings and publications |
| **Space** | a world holding one or more scenes |
| **Scene** | an object carrying an **anchor** β what decides when it appears |
| **Object** | a name plus a set of components |
| **Component** | one capability of an object, with its own settings |
| **Anchor** | a scene's trigger: image, QR, location, face, surface, panorama, or plain 3D |
| **Anchor group** | scenes sharing a trigger β only one of them shows at a time |
| **Event** | a trigger plus a list of steps |
| **Trigger** | when something happens: `on-click`, `on-launch`, `on-collide` |
| **Step** | what to do: `play_animation`, `set_state`, `set_visibility` |
| **Branch** | one of several outcomes of a trigger β entered and left, pressed and released |
| **State** | a named snapshot of an object's settings |
| **Preset / bar** | an animation on the timeline, and one object's part in it |
| **Patch** | behaviour drawn as a graph |
| **Script** | behaviour written in TypeScript |
| **Card** | a DivKit resource β the 2D interface layer |
| **Prefab** | a saved tree of objects, reusable as a resource |
| **Publication** | a snapshot of your project that the player opens |
| **VPS** | positioning against a scanned map of a real place β an option on a surface anchor |
## Units
| Quantity | Unit | Where it differs |
| -------------- | --------------------------------------------------- | -------------------------------------------------------- |
| Position | metres | β |
| Rotation | degrees everywhere you can see it | **radians** when read or written from a script |
| Scale | a multiplier, 1 being natural size | β |
| Animation time | seconds | milliseconds on state switches and transitions |
| Opacity | 0 to 1 | 0 to 100 in the opacity animation step |
| Clip frames | counted at 30 frames per second | β |
| Font size | pixels, where 1000 px is one metre | β |
| Texture rotation | degrees | β |
---
# Component reference
Source: https://docs.arclip.design/creators-engine/reference/components
{/* GENERATED by scripts/gen-engine-reference.ts β do not edit by hand. */}
# Component reference
Every component an object can carry, and every setting inside it. What these settings *mean* β and the ones that are easy to misread β is covered in [Components](/creators-engine/concepts/components).
39 components are available.
| Type | Schema | Fields | Field names |
| --- | --- | --- | --- |
| `AnchorComponent` | variant on `type` | 2 | type, β¦per variant |
| `AudioComponent` | object | 7 | audio, volume, play, loop, muted, positional, β¦ |
| `BoneComponent` | object | 2 | index, inverse |
| `CameraComponent` | variant on `projection` | 2 | projection, β¦per variant |
| `CameraViewComponent` | object | 8 | target, x, y, width, height, size, β¦ |
| `ColliderComponent` | variant on `type` | 2 | type, β¦per variant |
| `DivkitComponent` | object | 6 | referal, render, anchorX, anchorY, enabled, persistVariables |
| `EventsComponent` | object | 1 | events |
| `ExtensionComponent` | object | 1 | entries |
| `GeometryComponent` | variant on `type` | 2 | type, β¦per variant |
| `InitialVelocityComponent` | object | 2 | velocity, angularVelocity |
| `InstancerComponent` | variant on `source` | 2 | source, β¦per variant |
| `LightComponent` | variant on `type` | 2 | type, β¦per variant |
| `LookAtComponent` | variant on `mode` | 2 | mode, β¦per variant |
| `MaterialComponent` | object | 1 | materials |
| `MetaComponent` | object | 6 | name, type, sort, enabled, visible, isMain |
| `ModelRefComponent` | object | 8 | referal, clip, speed, repeatMode, repeatCount, startFrame, β¦ |
| `MorphComponent` | object | 1 | weights |
| `ParentComponent` | object | 3 | parent, order, lock |
| `ParticleEmitterComponent` | object | 23 | emitting, count, lifetime, shape, radius, extents, β¦ |
| `PatchGraphComponent` | object | 3 | signature, nodes, edges |
| `PatchRefComponent` | object | 2 | referal, inputs |
| `PathComponent` | object | 19 | points, closed, curve, tension, radius, radialSegments, β¦ |
| `PhysicsStateComponent` | object | 4 | velocity, angularVelocity, speed, sleeping |
| `PrefabComponent` | object | 1 | resourceId |
| `PrefabContent` | object | 1 | rootId |
| `RigidBodyComponent` | object | 8 | type, mass, linearDamping, angularDamping, fixedRotation, friction, β¦ |
| `ScriptComponent` | object | 3 | referal, enabled, props |
| `ShaderGraphComponent` | object | 5 | nodes, edges, output, order, source |
| `ShadowComponent` | object | 2 | castShadow, receiveShadow |
| `SkeletonComponent` | object | 0 | |
| `SplatRefComponent` | object | 1 | referal |
| `StatesComponent` | object | 2 | states, activeStateId |
| `TagsComponent` | object | 1 | tags |
| `TemporalStateComponent` | object | 2 | presets, activePresetId |
| `Text3DComponent` | object | 15 | text, font, fontWeight, fontSize, textAlign, verticalAlign, β¦ |
| `TextComponent` | object | 10 | font, fontStyle, text, fontWeight, fontSize, width, β¦ |
| `TransformComponent` | object | 4 | position, rotation, scale, lock |
| `VideoComponent` | object | 7 | video, volume, play, loop, muted, positional, β¦ |
## Schemas in full
### AnchorComponent
Tagged union on `type` β the fields depend on the tag.
**`type` = `image`**
| Field | Type | Notes |
| --- | --- | --- |
| `isMain` | `bool` β boolean | has default |
| `imageId` | `string` β string | has default |
| `physicalWidth` | `f64` β number | has default, min 0 |
| `physicalHeight` | `f64` β number | has default, min 0 |
| `showHints` | `bool` β boolean | has default |
**`type` = `qr`**
| Field | Type | Notes |
| --- | --- | --- |
| `isMain` | `bool` β boolean | has default |
| `text` | `string` β string | has default |
| `imageId` | `string` β string | has default |
| `physicalSize` | `f64` β number | has default, min 0 |
| `showHints` | `bool` β boolean | has default |
**`type` = `geo`**
| Field | Type | Notes |
| --- | --- | --- |
| `isMain` | `bool` β boolean | has default |
| `latitude` | `f64` β number | has default, min -90, max 90 |
| `longitude` | `f64` β number | has default, min -180, max 180 |
| `showHints` | `bool` β boolean | has default |
**`type` = `face`**
| Field | Type | Notes |
| --- | --- | --- |
| `isMain` | `bool` β boolean | has default |
| `physicalSize` | `f64` β number | has default, min 0 |
| `anchorPoint` | enum(`nose` \| `forehead` \| `chin` \| `leftEye` \| `rightEye` \| `leftEyebrow` \| `rightEyebrow` \| `leftEar` \| `rightEar` \| `noseBase` \| `bottomLip` \| `origin`) | has default |
| `faceGrid` | `bool` β boolean | has default |
| `faceTextureId` | `string` β string | has default |
| `fillEyes` | `bool` β boolean | has default |
| `fillMouth` | `bool` β boolean | has default |
| `flipTexture` | `bool` β boolean | has default |
| `showHints` | `bool` β boolean | has default |
**`type` = `surface`**
| Field | Type | Notes |
| --- | --- | --- |
| `isMain` | `bool` β boolean | has default |
| `bindingType` | enum(`floor` \| `wall` \| `ceiling` \| `table`) | has default |
| `recognitionAlgorithm` | enum(`plane_detection` \| `hit_test`) | has default |
| `providerIos` | enum(`auto` \| `webxr` \| `8thwall` \| `arclip-webar`) | has default |
| `providerAndroid` | enum(`auto` \| `webxr` \| `8thwall` \| `arclip-webar`) | has default |
| `vps` | `bool` β boolean | has default |
| `locationIds` | `array` β array | has default |
| `showHints` | `bool` β boolean | has default |
**`type` = `panorama`**
| Field | Type | Notes |
| --- | --- | --- |
| `isMain` | `bool` β boolean | has default |
| `showHints` | `bool` β boolean | has default |
**`type` = `3d`**
| Field | Type | Notes |
| --- | --- | --- |
| `isMain` | `bool` β boolean | has default |
| `cameraId` | `string` β string | has default |
### AudioComponent
| Field | Type | Notes |
| --- | --- | --- |
| `audio` | `string` β string | nullable, has default |
| `volume` | `f64` β number | has default |
| `play` | `bool` β boolean | has default |
| `loop` | `bool` β boolean | has default |
| `muted` | `bool` β boolean | has default |
| `positional` | `bool` β boolean | has default |
| `position` | `f64` β number | has default |
### BoneComponent
| Field | Type | Notes |
| --- | --- | --- |
| `index` | `f64` β number | has default |
| `inverse` | `mat4` β `number[16]` | has default |
### CameraComponent
Tagged union on `projection` β the fields depend on the tag.
**`projection` = `perspective`**
| Field | Type | Notes |
| --- | --- | --- |
| `fov` | `f64` β number | has default, min 1, max 179 |
| `near` | `f64` β number | has default, min 0.001 |
| `far` | `f64` β number | has default, min 0.01 |
| `controls` | enum(`none` \| `orbit` \| `fly` \| `firstPerson` \| `lock`) | has default |
| `target` | `vec3` β `{x, y, z}` | has default |
| `enableDamping` | `bool` β boolean | has default |
| `dampingFactor` | `f64` β number | has default, min 0, max 1 |
| `enableRotate` | `bool` β boolean | has default |
| `enablePan` | `bool` β boolean | has default |
| `enableZoom` | `bool` β boolean | has default |
| `minDistance` | `f64` β number | has default, min 0 |
| `maxDistance` | `f64` β number | has default, min 0 |
| `minPolarAngle` | `f64` β number | has default, min 0, max 3.141592653589793 |
| `maxPolarAngle` | `f64` β number | has default, min 0, max 3.141592653589793 |
| `autoRotate` | `bool` β boolean | has default |
| `autoRotateSpeed` | `f64` β number | has default |
| `moveSpeed` | `f64` β number | has default, min 0 |
| `boost` | `f64` β number | has default, min 1 |
| `lookSpeed` | `f64` β number | has default, min 0.05 |
| `moveForward` | `array` β array | has default |
| `moveBack` | `array` β array | has default |
| `moveLeft` | `array` β array | has default |
| `moveRight` | `array` β array | has default |
| `moveUp` | `array` β array | has default |
| `moveDown` | `array` β array | has default |
| `moveBoost` | `array` β array | has default |
### CameraViewComponent
| Field | Type | Notes |
| --- | --- | --- |
| `target` | enum(`screen` \| `texture`) | has default |
| `x` | `f64` β number | has default, min 0, max 1 |
| `y` | `f64` β number | has default, min 0, max 1 |
| `width` | `f64` β number | has default, min 0, max 1 |
| `height` | `f64` β number | has default, min 0, max 1 |
| `size` | `u32` β number | has default, min 16, max 4096 |
| `order` | `i32` β number | has default |
| `enabled` | `bool` β boolean | has default |
### ColliderComponent
Tagged union on `type` β the fields depend on the tag.
**`type` = `auto`**
| Field | Type | Notes |
| --- | --- | --- |
**`type` = `box`**
| Field | Type | Notes |
| --- | --- | --- |
| `halfExtents` | `vec3` β `{x, y, z}` | has default |
**`type` = `sphere`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
**`type` = `cylinder`**
| Field | Type | Notes |
| --- | --- | --- |
| `radiusTop` | `f64` β number | has default, min 0 |
| `radiusBottom` | `f64` β number | has default, min 0 |
| `height` | `f64` β number | has default, min 0 |
| `segments` | `u32` β number | has default, min 3, max 64 |
**`type` = `capsule`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
| `height` | `f64` β number | has default, min 0 |
**`type` = `plane`**
| Field | Type | Notes |
| --- | --- | --- |
### DivkitComponent
| Field | Type | Notes |
| --- | --- | --- |
| `referal` | `string` β string | nullable, has default |
| `render` | enum(`overlay` \| `anchored`) | has default |
| `anchorX` | enum(`left` \| `center` \| `right`) | has default |
| `anchorY` | enum(`top` \| `center` \| `bottom`) | has default |
| `enabled` | `bool` β boolean | has default |
| `persistVariables` | `bool` β boolean | has default |
### EventsComponent
| Field | Type | Notes |
| --- | --- | --- |
| `events` | `array` β array | has default |
### ExtensionComponent
| Field | Type | Notes |
| --- | --- | --- |
| `entries` | `record` β map | has default |
### GeometryComponent
Tagged union on `type` β the fields depend on the tag.
**`type` = `box`**
| Field | Type | Notes |
| --- | --- | --- |
| `width` | `f64` β number | has default, min 0 |
| `height` | `f64` β number | has default, min 0 |
| `depth` | `f64` β number | has default, min 0 |
| `widthSegments` | `u32` β number | has default, min 1, max 100 |
| `heightSegments` | `u32` β number | has default, min 1, max 100 |
| `depthSegments` | `u32` β number | has default, min 1, max 100 |
| `cornerRadius` | `f64` β number | has default, min 0 |
| `cornerSegments` | `u32` β number | has default, min 1, max 10 |
**`type` = `sphere`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
| `widthSegments` | `u32` β number | has default, min 3, max 256 |
| `heightSegments` | `u32` β number | has default, min 2, max 256 |
| `phiStart` | `f64` β number | has default |
| `phiLength` | `f64` β number | has default |
| `thetaStart` | `f64` β number | has default, min 0, max 6.283185307179586 |
| `thetaLength` | `f64` β number | has default, min 0, max 6.283185307179586 |
**`type` = `cylinder`**
| Field | Type | Notes |
| --- | --- | --- |
| `radiusTop` | `f64` β number | has default, min 0 |
| `radiusBottom` | `f64` β number | has default, min 0 |
| `height` | `f64` β number | has default, min 0 |
| `radialSegments` | `u32` β number | has default, min 3, max 256 |
| `heightSegments` | `u32` β number | has default, min 1, max 100 |
| `openEnded` | `bool` β boolean | has default |
| `thetaStart` | `f64` β number | has default, min 0, max 6.283185307179586 |
| `thetaLength` | `f64` β number | has default, min 0, max 6.283185307179586 |
**`type` = `capsule`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
| `height` | `f64` β number | has default, min 0 |
| `capSegments` | `u32` β number | has default, min 1, max 32 |
| `radialSegments` | `u32` β number | has default, min 3, max 128 |
| `heightSegments` | `u32` β number | has default, min 1, max 100 |
**`type` = `circle`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
| `segments` | `u32` β number | has default, min 3, max 256 |
| `thetaStart` | `f64` β number | has default, min 0, max 6.283185307179586 |
| `thetaLength` | `f64` β number | has default, min 0, max 6.283185307179586 |
**`type` = `cone`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
| `height` | `f64` β number | has default, min 0 |
| `radialSegments` | `u32` β number | has default, min 3, max 256 |
| `heightSegments` | `u32` β number | has default, min 1, max 100 |
| `openEnded` | `bool` β boolean | has default |
| `thetaStart` | `f64` β number | has default, min 0, max 6.283185307179586 |
| `thetaLength` | `f64` β number | has default, min 0, max 6.283185307179586 |
**`type` = `dodecahedron`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
| `detail` | `u32` β number | has default, max 5 |
**`type` = `icosahed`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
| `detail` | `u32` β number | has default, max 5 |
**`type` = `octahedron`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default, min 0 |
| `detail` | `u32` β number | has default, max 5 |
**`type` = `plane`**
| Field | Type | Notes |
| --- | --- | --- |
| `width` | `f64` β number | has default, min 0 |
| `height` | `f64` β number | has default, min 0 |
| `widthSegments` | `u32` β number | has default, min 1, max 100 |
| `heightSegments` | `u32` β number | has default, min 1, max 100 |
**`type` = `triangle`**
| Field | Type | Notes |
| --- | --- | --- |
| `width` | `f64` β number | has default, min 0 |
| `height` | `f64` β number | has default, min 0 |
| `widthSegments` | `u32` β number | has default, min 1, max 100 |
| `heightSegments` | `u32` β number | has default, min 1, max 100 |
**`type` = `ring`**
| Field | Type | Notes |
| --- | --- | --- |
| `innerRadius` | `f64` β number | has default, min 0 |
| `outerRadius` | `f64` β number | has default, min 0 |
| `thetaSegments` | `u32` β number | has default, min 3, max 256 |
| `phiSegments` | `u32` β number | has default, min 1, max 32 |
| `thetaStart` | `f64` β number | has default, min 0, max 6.283185307179586 |
| `thetaLength` | `f64` β number | has default, min 0, max 6.283185307179586 |
**`type` = `torus`**
| Field | Type | Notes |
| --- | --- | --- |
| `radius` | `f64` β number | has default |
| `tube` | `f64` β number | has default |
| `radialSegments` | `u32` β number | has default, min 1, max 128 |
| `tubularSegments` | `u32` β number | has default, min 3, max 256 |
| `arc` | `f64` β number | has default, min 0, max 6.283185307179586 |
**`type` = `extrude`**
| Field | Type | Notes |
| --- | --- | --- |
| `points` | `array` β array | has default |
| `depth` | `f64` β number | has default, min 0 |
| `bevelEnabled` | `bool` β boolean | has default |
| `bevelThickness` | `f64` β number | has default, min 0 |
| `bevelSize` | `f64` β number | has default, min 0 |
| `bevelSegments` | `u32` β number | has default, min 1, max 16 |
**`type` = `lathe`**
| Field | Type | Notes |
| --- | --- | --- |
| `points` | `array` β array | has default |
| `segments` | `u32` β number | has default, min 3, max 256 |
| `phiStart` | `f64` β number | has default, min 0, max 6.283185307179586 |
| `phiLength` | `f64` β number | has default, min 0, max 6.283185307179586 |
**`type` = `ref`**
| Field | Type | Notes |
| --- | --- | --- |
| `referal` | `string` β string | nullable, has default |
### InitialVelocityComponent
| Field | Type | Notes |
| --- | --- | --- |
| `velocity` | `vec3` β `{x, y, z}` | has default |
| `angularVelocity` | `vec3` β `{x, y, z}` | has default |
### InstancerComponent
Tagged union on `source` β the fields depend on the tag.
**`source` = `grid`**
| Field | Type | Notes |
| --- | --- | --- |
| `maxCount` | `u32` β number | has default, min 1, max 100000 |
| `seed` | `u32` β number | has default |
| `scaleMin` | `f64` β number | has default, min 0 |
| `scaleMax` | `f64` β number | has default, min 0 |
| `rotationJitter` | `vec3` β `{x, y, z}` | has default |
| `colorA` | `color` β hex colour string | has default |
| `colorB` | `color` β hex colour string | has default |
| `countX` | `u32` β number | has default, min 1, max 1000 |
| `countY` | `u32` β number | has default, min 1, max 1000 |
| `countZ` | `u32` β number | has default, min 1, max 1000 |
| `spacing` | `vec3` β `{x, y, z}` | has default |
| `jitter` | `f64` β number | has default, min 0, max 1 |
**`source` = `scatter`**
| Field | Type | Notes |
| --- | --- | --- |
| `maxCount` | `u32` β number | has default, min 1, max 100000 |
| `seed` | `u32` β number | has default |
| `scaleMin` | `f64` β number | has default, min 0 |
| `scaleMax` | `f64` β number | has default, min 0 |
| `rotationJitter` | `vec3` β `{x, y, z}` | has default |
| `colorA` | `color` β hex colour string | has default |
| `colorB` | `color` β hex colour string | has default |
| `count` | `u32` β number | has default, min 1, max 100000 |
| `shape` | enum(`box` \| `sphere`) | has default |
| `extents` | `vec3` β `{x, y, z}` | has default |
**`source` = `children`**
| Field | Type | Notes |
| --- | --- | --- |
| `maxCount` | `u32` β number | has default, min 1, max 100000 |
| `seed` | `u32` β number | has default |
| `scaleMin` | `f64` β number | has default, min 0 |
| `scaleMax` | `f64` β number | has default, min 0 |
| `rotationJitter` | `vec3` β `{x, y, z}` | has default |
| `colorA` | `color` β hex colour string | has default |
| `colorB` | `color` β hex colour string | has default |
### LightComponent
Tagged union on `type` β the fields depend on the tag.
**`type` = `ambient`**
| Field | Type | Notes |
| --- | --- | --- |
| `color` | `color` β hex colour string | has default |
| `intensity` | `f64` β number | has default, min 0 |
| `castShadow` | `bool` β boolean | has default |
| `visible` | `bool` β boolean | has default |
**`type` = `directional`**
| Field | Type | Notes |
| --- | --- | --- |
| `color` | `color` β hex colour string | has default |
| `intensity` | `f64` β number | has default, min 0 |
| `castShadow` | `bool` β boolean | has default |
| `visible` | `bool` β boolean | has default |
**`type` = `point`**
| Field | Type | Notes |
| --- | --- | --- |
| `color` | `color` β hex colour string | has default |
| `intensity` | `f64` β number | has default, min 0 |
| `castShadow` | `bool` β boolean | has default |
| `visible` | `bool` β boolean | has default |
| `distance` | `f64` β number | has default, min 0 |
| `decay` | `f64` β number | has default, min 0 |
**`type` = `spot`**
| Field | Type | Notes |
| --- | --- | --- |
| `color` | `color` β hex colour string | has default |
| `intensity` | `f64` β number | has default, min 0 |
| `castShadow` | `bool` β boolean | has default |
| `visible` | `bool` β boolean | has default |
| `distance` | `f64` β number | has default, min 0 |
| `decay` | `f64` β number | has default, min 0 |
| `angle` | `f64` β number | has default, min 0, max 1.5707963267948966 |
| `penumbra` | `f64` β number | has default, min 0, max 1 |
**`type` = `hemisphere`**
| Field | Type | Notes |
| --- | --- | --- |
| `color` | `color` β hex colour string | has default |
| `intensity` | `f64` β number | has default, min 0 |
| `castShadow` | `bool` β boolean | has default |
| `visible` | `bool` β boolean | has default |
| `groundColor` | `color` β hex colour string | has default |
### LookAtComponent
Tagged union on `mode` β the fields depend on the tag.
**`mode` = `world`**
| Field | Type | Notes |
| --- | --- | --- |
| `target` | `vec3` β `{x, y, z}` | has default |
**`mode` = `self`**
| Field | Type | Notes |
| --- | --- | --- |
| `offset` | `vec3` β `{x, y, z}` | has default |
### MaterialComponent
| Field | Type | Notes |
| --- | --- | --- |
| `materials` | `array` β array | has default, min 1 |
### MetaComponent
| Field | Type | Notes |
| --- | --- | --- |
| `name` | `string` β string | has default |
| `type` | enum(`object` \| `model` \| `plain` \| `box` \| `light`) | has default |
| `sort` | `f64` β number | has default |
| `enabled` | `bool` β boolean | has default |
| `visible` | `bool` β boolean | has default |
| `isMain` | `bool` β boolean | has default |
### ModelRefComponent
| Field | Type | Notes |
| --- | --- | --- |
| `referal` | `string` β string | nullable, has default |
| `clip` | `string` β string | nullable, has default |
| `speed` | `f64` β number | has default |
| `repeatMode` | enum(`none` \| `count` \| `permanent`) | has default |
| `repeatCount` | `f64` β number | has default, integer, min 1 |
| `startFrame` | `f64` β number | has default, integer, min 0 |
| `stopWhenPressed` | `bool` β boolean | has default |
| `playing` | `bool` β boolean | has default |
### MorphComponent
| Field | Type | Notes |
| --- | --- | --- |
| `weights` | `record` β map | has default |
### ParentComponent
| Field | Type | Notes |
| --- | --- | --- |
| `parent` | `string` β string | nullable, has default |
| `order` | `f64` β number | nullable, has default |
| `lock` | `bool` β boolean | has default |
### ParticleEmitterComponent
| Field | Type | Notes |
| --- | --- | --- |
| `emitting` | `bool` β boolean | has default |
| `count` | `u32` β number | has default, min 1, max 65536 |
| `lifetime` | `f64` β number | has default, min 0.05 |
| `shape` | enum(`point` \| `sphere` \| `box` \| `cone`) | has default |
| `radius` | `f64` β number | has default, min 0 |
| `extents` | `vec3` β `{x, y, z}` | has default |
| `angle` | `f64` β number | has default, min 0, max 180 |
| `speedMin` | `f64` β number | has default, min 0 |
| `speedMax` | `f64` β number | has default, min 0 |
| `gravity` | `vec3` β `{x, y, z}` | has default |
| `drag` | `f64` β number | has default, min 0 |
| `turbulence` | `f64` β number | has default, min 0 |
| `sizeStart` | `f64` β number | has default, min 0 |
| `sizeEnd` | `f64` β number | has default, min 0 |
| `sizeJitter` | `f64` β number | has default, min 0, max 1 |
| `spin` | `f64` β number | has default |
| `colorStart` | `color` β hex colour string | has default |
| `colorEnd` | `color` β hex colour string | has default |
| `opacityStart` | `f64` β number | has default, min 0, max 1 |
| `opacityEnd` | `f64` β number | has default, min 0, max 1 |
| `sprite` | `string` β string | nullable, has default |
| `blending` | enum(`additive` \| `normal`) | has default |
| `seed` | `u32` β number | has default |
### PatchGraphComponent
| Field | Type | Notes |
| --- | --- | --- |
| `signature` | `object` β nested object | has default |
| `nodes` | `array` β array | has default |
| `edges` | `array` β array | has default |
### PatchRefComponent
| Field | Type | Notes |
| --- | --- | --- |
| `referal` | `string` β string | nullable, has default |
| `inputs` | `record` β map | has default |
### PathComponent
| Field | Type | Notes |
| --- | --- | --- |
| `points` | `array` β array | has default |
| `closed` | `bool` β boolean | has default |
| `curve` | enum(`smooth` \| `linear`) | has default |
| `tension` | `f64` β number | has default, min 0, max 1 |
| `radius` | `f64` β number | has default, min 0, max 10 |
| `radialSegments` | `u32` β number | has default, min 3, max 64 |
| `tubularSegments` | `u32` β number | has default, min 4, max 1024 |
| `profile` | enum(`none` \| `circle` \| `rectangle` \| `star`) | has default |
| `profileSize` | `vec2` β `{x, y}` | has default |
| `ring` | `f64` β number | has default, min 0, max 1 |
| `arc` | `f64` β number | has default, min 0, max 360 |
| `sides` | `u32` β number | has default, min 3, max 64 |
| `depth` | `f64` β number | has default, min 0, max 1 |
| `offset` | `f64` β number | has default, min 0, max 1 |
| `rotation` | `f64` β number | has default, min -360, max 360 |
| `twist` | `f64` β number | has default, min -3600, max 3600 |
| `startScale` | `f64` β number | has default, min 0, max 10 |
| `endScale` | `f64` β number | has default, min 0, max 10 |
| `caps` | enum(`flat` \| `round`) | has default |
### PhysicsStateComponent
| Field | Type | Notes |
| --- | --- | --- |
| `velocity` | `vec3` β `{x, y, z}` | has default |
| `angularVelocity` | `vec3` β `{x, y, z}` | has default |
| `speed` | `f64` β number | has default |
| `sleeping` | `bool` β boolean | has default |
### PrefabComponent
| Field | Type | Notes |
| --- | --- | --- |
| `resourceId` | `string` β string | nullable, has default |
### PrefabContent
| Field | Type | Notes |
| --- | --- | --- |
| `rootId` | `string` β string | required |
### RigidBodyComponent
| Field | Type | Notes |
| --- | --- | --- |
| `type` | enum(`dynamic` \| `static` \| `kinematic` \| `none`) | has default |
| `mass` | `f64` β number | has default, min 0 |
| `linearDamping` | `f64` β number | has default, min 0, max 1 |
| `angularDamping` | `f64` β number | has default, min 0, max 1 |
| `fixedRotation` | `bool` β boolean | has default |
| `friction` | `f64` β number | nullable, has default, min 0 |
| `restitution` | `f64` β number | nullable, has default, min 0, max 1 |
| `network` | enum(`shared` \| `local`) | has default |
### ScriptComponent
| Field | Type | Notes |
| --- | --- | --- |
| `referal` | `string` β string | nullable, has default |
| `enabled` | `bool` β boolean | has default |
| `props` | `record` β map | has default |
### ShaderGraphComponent
| Field | Type | Notes |
| --- | --- | --- |
| `nodes` | `array` β array | has default |
| `edges` | `array` β array | has default |
| `output` | `string` β string | has default |
| `order` | `f64` β number | has default, integer |
| `source` | `string` β string | has default |
### ShadowComponent
| Field | Type | Notes |
| --- | --- | --- |
| `castShadow` | `bool` β boolean | has default |
| `receiveShadow` | `bool` β boolean | has default |
### SkeletonComponent
| Field | Type | Notes |
| --- | --- | --- |
### SplatRefComponent
| Field | Type | Notes |
| --- | --- | --- |
| `referal` | `string` β string | nullable, has default |
### StatesComponent
| Field | Type | Notes |
| --- | --- | --- |
| `states` | `array` β array | has default |
| `activeStateId` | `string` β string | nullable, has default |
### TagsComponent
| Field | Type | Notes |
| --- | --- | --- |
| `tags` | `record` β map | required |
### TemporalStateComponent
| Field | Type | Notes |
| --- | --- | --- |
| `presets` | `array` β array | has default |
| `activePresetId` | `string` β string | nullable, has default |
### Text3DComponent
| Field | Type | Notes |
| --- | --- | --- |
| `text` | `string` β string | has default |
| `font` | `string` β string | nullable, has default |
| `fontWeight` | enum(`normal` \| `bold`) | has default |
| `fontSize` | `f64` β number | has default |
| `textAlign` | enum(`left` \| `center` \| `right`) | has default |
| `verticalAlign` | enum(`top` \| `middle` \| `bottom`) | has default |
| `lineHeight` | `f64` β number | has default, min 0.1, max 5 |
| `letterSpacing` | `f64` β number | has default, min -0.5, max 2 |
| `curveSegments` | `u32` β number | has default, min 1, max 32 |
| `depth` | `f64` β number | has default, min 0, max 5 |
| `bevelEnabled` | `bool` β boolean | has default |
| `bevelThickness` | `f64` β number | has default, min 0, max 1 |
| `bevelSize` | `f64` β number | has default, min 0, max 1 |
| `bevelSegments` | `u32` β number | has default, min 1, max 16 |
| `color` | `union` β union | has default |
### TextComponent
| Field | Type | Notes |
| --- | --- | --- |
| `font` | `string` β string | nullable, has default |
| `fontStyle` | enum(`normal` \| `italic`) | has default |
| `text` | `string` β string | has default |
| `fontWeight` | enum(`normal` \| `bold`) | has default |
| `fontSize` | `f64` β number | has default |
| `width` | `f64` β number | nullable, has default |
| `height` | `f64` β number | nullable, has default |
| `textAlign` | enum(`left` \| `center` \| `right` \| `justify`) | has default |
| `verticalAlign` | enum(`top` \| `middle` \| `bottom`) | has default |
| `color` | `union` β union | has default |
### TransformComponent
| Field | Type | Notes |
| --- | --- | --- |
| `position` | `vec3` β `{x, y, z}` | has default |
| `rotation` | `vec3` β `{x, y, z}` | has default |
| `scale` | `vec3` β `{x, y, z}` | has default |
| `lock` | `bool` β boolean | has default |
### VideoComponent
| Field | Type | Notes |
| --- | --- | --- |
| `video` | `string` β string | nullable, has default |
| `volume` | `f64` β number | has default |
| `play` | `bool` β boolean | has default |
| `loop` | `bool` β boolean | has default |
| `muted` | `bool` β boolean | has default |
| `positional` | `bool` β boolean | has default |
| `position` | `f64` β number | has default |
---
# Material maps
Source: https://docs.arclip.design/creators-engine/reference/material-maps
{/* GENERATED by scripts/gen-engine-reference.ts β do not edit by hand. */}
# Material maps
Every map is a **pair**: the image, and its options (tiling, offset, rotation, filtering, colour space, UV channel). Setting just the image is usually enough.
Which maps a material accepts depends on its type, so switching type can leave a map you set earlier unused.
The names below are what the material panel calls them; the last column is what a script or a patch node uses.
| Map | What it is for | `basic` | `standard` | `physical` | `toon` | `normal` | `chromakey` | `occluder` | `ref` | In code |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| **Ambient occlusion** | baked contact shadow | β | β | β | β | | | | β | `aoMap` |
| **Anisotropy** | brushed-metal direction | | | β | | | | | | `anisotropyMap` |
| **Baked light** | baked lighting | β | β | β | β | | | | β | `lightMap` |
| **Bump** | height-based detail β prefer a normal map | | β | β | β | β | | | β | `bumpMap` |
| **Clearcoat** | where the lacquer sits | | | β | | | | | | `clearcoatMap` |
| **Clearcoat normal** | texture in the lacquer | | | β | | | | | | `clearcoatNormalMap` |
| **Clearcoat roughness** | how polished the lacquer is | | | β | | | | | | `clearcoatRoughnessMap` |
| **Colour** | the main colour image | β | β | β | β | | β | | β | `map` |
| **Displacement** | actually moves the surface | | β | β | β | β | | | | `displacementMap` |
| **Emissive** | which parts glow | | β | β | β | | | | | `emissiveMap` |
| **Environment** | reflections | β | β | β | | | | | | `envMap` |
| **Iridescence** | where the oil-slick effect appears | | | β | | | | | | `iridescenceMap` |
| **Iridescence thickness** | how strong it is | | | β | | | | | | `iridescenceThicknessMap` |
| **Metalness** | which parts are metal | | β | β | | | | | β | `metalnessMap` |
| **Normal** | surface detail without extra geometry | | β | β | β | β | | | β | `normalMap` |
| **Opacity** | transparency, taken from brightness | β | β | β | β | | | | β | `alphaMap` |
| **Roughness** | polished here, matte there | | β | β | | | | | β | `roughnessMap` |
| **Sheen colour** | the colour of fabric sheen | | | β | | | | | | `sheenColorMap` |
| **Sheen roughness** | how soft that sheen is | | | β | | | | | | `sheenRoughnessMap` |
| **Specular** | where it is shiny | β | | | | | | | | `specularMap` |
| **Specular colour** | the tint of reflections | | | β | | | | | | `specularColorMap` |
| **Specular intensity** | how reflective | | | β | | | | | | `specularIntensityMap` |
| **Thickness** | how thick the glass is | | | β | | | | | | `thicknessMap` |
| **Toon gradient** | the toon ramp | | | | β | | | | | `gradientMap` |
| **Transmission** | where light passes through | | | β | | | | | | `transmissionMap` |
:::warning Some of these are stored but not drawn
Ambient occlusion, light, environment, bump, displacement and gradient maps are kept if you set them, but the renderer ignores them. Use a **normal map** instead of bump, and set the environment on the **scene**. The material panel warns you when you use one.
:::
:::warning Mark data maps as data
A map carrying numbers rather than colour β roughness, metalness, a mask β must be marked as such in its colour-space option, or it is colour-corrected and the values come out wrong.
:::
How to use them: [Materials](/creators-engine/concepts/materials).
---
# Patch node reference
Source: https://docs.arclip.design/creators-engine/reference/patch-nodes
{/* GENERATED by scripts/gen-engine-reference.ts β do not edit by hand. */}
# Patch node reference
203 nodes across 24 categories. Nodes marked \* still work in existing graphs but are no longer offered in the palette.
A node is one of four kinds: an **event** node starts the graph, a **control** node shapes the flow (branch, loop, gate), an **effect** node does something and passes the pulse on, and a **data** node is just a value. See [Patches](/creators-engine/authoring/patches).
### animation
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `animate` | Runs a built-in animation step. | effect | `target`:object, `params`:any, `exec`:pulse | `then`:pulse |
### appearance
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `set_color` | Sets the colour on every material slot at once. | effect | `object`:object, `color`:color, `exec`:pulse | `then`:pulse |
### audio
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `play_sound` | Plays a one-shot sound. Each call is independent, so effects never cut each other off. | effect | `object`:object, `volume`:number, `exec`:pulse | `then`:pulse |
### code
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `action` | JavaScript statements, run in the flow. | effect | `a`:any, `b`:any, `c`:any, `d`:any, `exec`:pulse | `then`:pulse |
| `expression` | A JavaScript expression, for the one thing no node covers. | data | `a`:any, `b`:any, `c`:any, `d`:any | `result`:any |
### components
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `get_anchor` | Reads a sceneβs trigger settings. | data | `object`:object | `isMain`:boolean, `imageId`:string, `physicalWidth`:number, `physicalHeight`:number, `showHints`:boolean, `text`:string, `physicalSize`:number, `latitude`:number, `longitude`:number, `anchorPoint`:string, `faceGrid`:boolean, `faceTextureId`:string, `fillEyes`:boolean, `fillMouth`:boolean, `flipTexture`:boolean, `bindingType`:string, `recognitionAlgorithm`:string, `providerIos`:string, `providerAndroid`:string, `vps`:boolean, `locationIds`:any, `cameraId`:string |
| `get_audio` | Reads a soundβs settings. | data | `object`:object | `audio`:string, `volume`:number, `play`:boolean, `loop`:boolean, `muted`:boolean, `positional`:boolean, `position`:number |
| `get_collider` | Reads the collision shape. | data | `object`:object | `halfExtents`:vector, `radius`:number, `radiusTop`:number, `radiusBottom`:number, `height`:number, `segments`:number |
| `get_geometry` | Reads the shape and its dimensions. | data | `object`:object | `width`:number, `height`:number, `depth`:number, `widthSegments`:number, `heightSegments`:number, `depthSegments`:number, `cornerRadius`:number, `cornerSegments`:number, `radius`:number, `phiStart`:number, `phiLength`:number, `thetaStart`:number, `thetaLength`:number, `radiusTop`:number, `radiusBottom`:number, `radialSegments`:number, `openEnded`:boolean, `capSegments`:number, `segments`:number, `detail`:number, `innerRadius`:number, `outerRadius`:number, `thetaSegments`:number, `phiSegments`:number, `tube`:number, `tubularSegments`:number, `arc`:number, `points`:any, `bevelEnabled`:boolean, `bevelThickness`:number, `bevelSize`:number, `bevelSegments`:number, `referal`:string |
| `get_initial_velocity` | Reads the starting push. | data | `object`:object | `velocity`:vector, `angularVelocity`:vector |
| `get_instancer` | Reads how copies are arranged. | data | `object`:object | `maxCount`:number, `seed`:number, `scaleMin`:number, `scaleMax`:number, `rotationJitter`:vector, `colorA`:color, `colorB`:color, `countX`:number, `countY`:number, `countZ`:number, `spacing`:vector, `jitter`:number, `count`:number, `shape`:string, `extents`:vector |
| `get_light` | Reads a lightβs colour and intensity. | data | `object`:object | `color`:color, `intensity`:number, `castShadow`:boolean, `visible`:boolean, `distance`:number, `decay`:number, `angle`:number, `penumbra`:number, `groundColor`:color |
| `get_look_at` | Reads the look-at target. | data | `object`:object | `target`:vector, `offset`:vector |
| `get_material` | Reads the first material slot. | data | `object`:object | `materials`:any |
| `get_meta` | Reads the name, and whether the object is enabled and visible. | data | `object`:object | `name`:string, `type`:string, `sort`:number, `enabled`:boolean, `visible`:boolean, `isMain`:boolean |
| `get_model_ref` | Reads which model and clip are playing. | data | `object`:object | `referal`:string, `clip`:string, `speed`:number, `repeatMode`:string, `repeatCount`:number, `startFrame`:number, `stopWhenPressed`:boolean, `playing`:boolean |
| `get_parent` | Reads which object this one is inside. | data | `object`:object | `parent`:string, `order`:number, `lock`:boolean |
| `get_particle_emitter` | Reads emitter settings. | data | `object`:object | `emitting`:boolean, `count`:number, `lifetime`:number, `shape`:string, `radius`:number, `extents`:vector, `angle`:number, `speedMin`:number, `speedMax`:number, `gravity`:vector, `drag`:number, `turbulence`:number, `sizeStart`:number, `sizeEnd`:number, `sizeJitter`:number, `spin`:number, `colorStart`:color, `colorEnd`:color, `opacityStart`:number, `opacityEnd`:number, `sprite`:string, `blending`:string, `seed`:number |
| `get_physics_state` | Reads live speed and whether the body is asleep. Read-only. | data | `object`:object | `velocity`:vector, `angularVelocity`:vector, `speed`:number, `sleeping`:boolean |
| `get_rigid_body` | Reads the physics body settings. | data | `object`:object | `type`:string, `mass`:number, `linearDamping`:number, `angularDamping`:number, `fixedRotation`:boolean, `friction`:number, `restitution`:number, `network`:string |
| `get_script` | Reads a script componentβs settings. | data | `object`:object | `referal`:string, `enabled`:boolean, `props`:any |
| `get_shadow` | Reads the shadow flags. | data | `object`:object | `castShadow`:boolean, `receiveShadow`:boolean |
| `get_states` | Reads which state is active. | data | `object`:object | `states`:any, `activeStateId`:string |
| `get_temporal_state` | Reads the animation preset. | data | `object`:object | `presets`:any, `activePresetId`:string |
| `get_text` | Reads the text and its look. | data | `object`:object | `font`:string, `fontStyle`:string, `text`:string, `fontWeight`:string, `fontSize`:number, `width`:number, `height`:number, `textAlign`:string, `verticalAlign`:string, `color`:any |
| `get_transform` | Reads position, rotation and scale. Rotation is in degrees. | data | `object`:object | `position`:vector, `rotation`:vector, `scale`:vector, `lock`:boolean |
| `get_video` | Reads a videoβs settings. | data | `object`:object | `video`:string, `volume`:number, `play`:boolean, `loop`:boolean, `muted`:boolean, `positional`:boolean, `position`:number |
| `set_anchor` | Changes them. | effect | `object`:object, `isMain`:boolean, `imageId`:string, `physicalWidth`:number, `physicalHeight`:number, `showHints`:boolean, `text`:string, `physicalSize`:number, `latitude`:number, `longitude`:number, `anchorPoint`:string, `faceGrid`:boolean, `faceTextureId`:string, `fillEyes`:boolean, `fillMouth`:boolean, `flipTexture`:boolean, `bindingType`:string, `recognitionAlgorithm`:string, `providerIos`:string, `providerAndroid`:string, `vps`:boolean, `locationIds`:any, `cameraId`:string, `exec`:pulse | `then`:pulse |
| `set_audio` | Plays, pauses, or changes volume. | effect | `object`:object, `audio`:string, `volume`:number, `play`:boolean, `loop`:boolean, `muted`:boolean, `positional`:boolean, `position`:number, `exec`:pulse | `then`:pulse |
| `set_collider` | Changes it. | effect | `object`:object, `halfExtents`:vector, `radius`:number, `radiusTop`:number, `radiusBottom`:number, `height`:number, `segments`:number, `exec`:pulse | `then`:pulse |
| `set_geometry` | Changes them. | effect | `object`:object, `width`:number, `height`:number, `depth`:number, `widthSegments`:number, `heightSegments`:number, `depthSegments`:number, `cornerRadius`:number, `cornerSegments`:number, `radius`:number, `phiStart`:number, `phiLength`:number, `thetaStart`:number, `thetaLength`:number, `radiusTop`:number, `radiusBottom`:number, `radialSegments`:number, `openEnded`:boolean, `capSegments`:number, `segments`:number, `detail`:number, `innerRadius`:number, `outerRadius`:number, `thetaSegments`:number, `phiSegments`:number, `tube`:number, `tubularSegments`:number, `arc`:number, `points`:any, `bevelEnabled`:boolean, `bevelThickness`:number, `bevelSize`:number, `bevelSegments`:number, `referal`:string, `exec`:pulse | `then`:pulse |
| `set_initial_velocity` | Changes it. | effect | `object`:object, `velocity`:vector, `angularVelocity`:vector, `exec`:pulse | `then`:pulse |
| `set_instancer` | Changes the count, spacing or spread. | effect | `object`:object, `maxCount`:number, `seed`:number, `scaleMin`:number, `scaleMax`:number, `rotationJitter`:vector, `colorA`:color, `colorB`:color, `countX`:number, `countY`:number, `countZ`:number, `spacing`:vector, `jitter`:number, `count`:number, `shape`:string, `extents`:vector, `exec`:pulse | `then`:pulse |
| `set_light` | Changes them β dimming and colour shifts. | effect | `object`:object, `color`:color, `intensity`:number, `castShadow`:boolean, `visible`:boolean, `distance`:number, `decay`:number, `angle`:number, `penumbra`:number, `groundColor`:color, `exec`:pulse | `then`:pulse |
| `set_look_at` | Points an object at something. | effect | `object`:object, `target`:vector, `offset`:vector, `exec`:pulse | `then`:pulse |
| `set_material` | Writes to it β colour, opacity, roughness and the rest. | effect | `object`:object, `materials`:any, `exec`:pulse | `then`:pulse |
| `set_meta` | Shows, hides, enables or disables an object. | effect | `object`:object, `name`:string, `type`:string, `sort`:number, `enabled`:boolean, `visible`:boolean, `isMain`:boolean, `exec`:pulse | `then`:pulse |
| `set_model_ref` | Switches model, clip, speed or looping. | effect | `object`:object, `referal`:string, `clip`:string, `speed`:number, `repeatMode`:string, `repeatCount`:number, `startFrame`:number, `stopWhenPressed`:boolean, `playing`:boolean, `exec`:pulse | `then`:pulse |
| `set_parent` | Moves it into another object. | effect | `object`:object, `parent`:string, `order`:number, `lock`:boolean, `exec`:pulse | `then`:pulse |
| `set_particle_emitter` | Changes them β start and stop an effect, or retune it live. | effect | `object`:object, `emitting`:boolean, `count`:number, `lifetime`:number, `shape`:string, `radius`:number, `extents`:vector, `angle`:number, `speedMin`:number, `speedMax`:number, `gravity`:vector, `drag`:number, `turbulence`:number, `sizeStart`:number, `sizeEnd`:number, `sizeJitter`:number, `spin`:number, `colorStart`:color, `colorEnd`:color, `opacityStart`:number, `opacityEnd`:number, `sprite`:string, `blending`:string, `seed`:number, `exec`:pulse | `then`:pulse |
| `set_rigid_body` | Changes mass, damping or body type. | effect | `object`:object, `type`:string, `mass`:number, `linearDamping`:number, `angularDamping`:number, `fixedRotation`:boolean, `friction`:number, `restitution`:number, `network`:string, `exec`:pulse | `then`:pulse |
| `set_script` | Enables, disables or reconfigures it. | effect | `object`:object, `referal`:string, `enabled`:boolean, `props`:any, `exec`:pulse | `then`:pulse |
| `set_shadow` | Turns casting and receiving on or off. | effect | `object`:object, `castShadow`:boolean, `receiveShadow`:boolean, `exec`:pulse | `then`:pulse |
| `set_states` | Switches state. | effect | `object`:object, `states`:any, `activeStateId`:string, `exec`:pulse | `then`:pulse |
| `set_temporal_state` | Switches preset. | effect | `object`:object, `presets`:any, `activePresetId`:string, `exec`:pulse | `then`:pulse |
| `set_text` | Changes what a text object says. | effect | `object`:object, `font`:string, `fontStyle`:string, `text`:string, `fontWeight`:string, `fontSize`:number, `width`:number, `height`:number, `textAlign`:string, `verticalAlign`:string, `color`:any, `exec`:pulse | `then`:pulse |
| `set_transform` | Writes them β only the ones you wire up. | effect | `object`:object, `position`:vector, `rotation`:vector, `scale`:vector, `lock`:boolean, `exec`:pulse | `then`:pulse |
| `set_video` | Plays, pauses, or seeks. | effect | `object`:object, `video`:string, `volume`:number, `play`:boolean, `loop`:boolean, `muted`:boolean, `positional`:boolean, `position`:number, `exec`:pulse | `then`:pulse |
### data
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `boolean` | True or false. | data | β | `value`:boolean |
| `color` | A colour. | data | β | `value`:color |
| `number` | A number you type in. | data | β | `value`:number |
| `pi` | Ο, for angle maths. | data | β | `value`:number |
| `text` | A piece of text. | data | β | `value`:string |
| `vector` | An x, y, z triple. | data | `x`:number, `y`:number, `z`:number | `value`:vector |
### entities
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `find_by_name` * | Finds an object by name. | data | `name`:string | `object`:object |
| `get_by_id` * | Finds an object by id. | data | `id`:string | `object`:object |
| `get_entity` | Refers to an object: this one, the scene, or one found by name or id. | data | `query`:string | `object`:object |
| `scene` * | The scene this graph belongs to. | data | β | `object`:object |
| `self` * | This object. | data | β | `object`:object |
### events
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `on_blur` | The cursor left it. | event (`on-blur`) | β | `pulse`:pulse |
| `on_click` | This object was tapped or clicked. | event (`on-click`) | β | `pulse`:pulse |
| `on_collide` | Something touched this object; `other` is what hit it. | event (`on-collide`) | β | `pulse`:pulse, `other`:object |
| `on_detect` | The marker or surface was found. | event (`on-detect`) | β | `pulse`:pulse |
| `on_divkit_action` | A button on an interface card was pressed. | event (`on-divkit-action`) | β | `pulse`:pulse, `id`:string |
| `on_drag` | Placed AR content is being dragged. | event (`on-drag`) | β | `pulse`:pulse, `dx`:number, `dy`:number |
| `on_frame` | Every frame, with the time since the last one β for anything continuous. | event (`frame`) | β | `pulse`:pulse, `dt`:number, `time`:number |
| `on_game_control` | The character controller changed state β idle, moving, running, jumping. | event (`on-game-control`) | β | `pulse`:pulse, `state`:string |
| `on_hover` | The cursor moved onto this object. | event (`on-hover`) | β | `pulse`:pulse |
| `on_key_down` | A key went down; `key` tells you which, so you can filter. | event (`on-keydown`) | β | `pulse`:pulse, `key`:string |
| `on_key_up` | A key was released. | event (`on-keyup`) | β | `pulse`:pulse, `key`:string |
| `on_launch` | The scene started. Your usual entry point. | event (`launch`) | β | `pulse`:pulse |
| `on_lost` | Tracking was lost. | event (`on-lost`) | β | `pulse`:pulse |
| `on_mouse_down` | A pointer button went down on this object. | event (`on-mousedown`) | β | `pulse`:pulse |
| `on_mouse_up` | It was released. | event (`on-mouseup`) | β | `pulse`:pulse |
| `on_pause` | The app or tab went to the background. | event (`on-pause`) | β | `pulse`:pulse |
| `on_pinch` | Placed AR content is being pinched. | event (`on-pinch`) | β | `pulse`:pulse, `scale`:number |
| `on_placed` | The visitor placed the content on a surface. | event (`on-placed`) | β | `pulse`:pulse |
| `on_pointer_lock` | The pointer was captured or released. | event (`on-pointer-lock`) | β | `pulse`:pulse, `locked`:boolean |
| `on_pointer_move` | The pointer moved, with how far β for look controls. | event (`on-pointer-move`) | β | `pulse`:pulse, `dx`:number, `dy`:number, `deltaYaw`:number, `deltaPitch`:number |
| `on_resize` | The viewport changed size. | event (`on-resize`) | β | `pulse`:pulse |
| `on_resume` | It came back. | event (`on-resume`) | β | `pulse`:pulse |
| `on_rotate` | Placed AR content is being twisted. | event (`on-rotate`) | β | `pulse`:pulse, `angle`:number |
| `on_scroll` | The wheel turned. | event (`on-scroll`) | β | `pulse`:pulse |
| `on_state_active` | This object entered a state. | event (`on-state-active`) | β | `pulse`:pulse |
| `on_state_inactive` | It left one. | event (`on-state-inactive`) | β | `pulse`:pulse |
| `on_vps_error` | Positioning could not run at all. | event (`on-vps-error`) | β | `pulse`:pulse |
| `on_vps_localized` | The visitorβs real position was found. | event (`on-vps-localized`) | β | `pulse`:pulse |
| `on_vps_not_localized` | It was not β this is routine while they look around, not an error. | event (`on-vps-not-localized`) | β | `pulse`:pulse |
| `on_vps_ready` | Positioning against a real-world map started up. | event (`on-vps-ready`) | β | `pulse`:pulse |
### flow
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `branch` | Take one path or the other depending on a condition. | control (`branch`) | `exec`:pulse, `condition`:boolean | `true`:pulse, `false`:pulse |
| `delay` | Wait, then continue. | control (`delay`) | `exec`:pulse, `dt`:number, `seconds`:number | `then`:pulse |
| `emit_event` | Raise a trigger from this object, which events and scripts can hear. | effect | `event`:string, `exec`:pulse | `then`:pulse |
| `for_each` | Run the body once per item in a list. | control (`for_each`) | `exec`:pulse, `list`:any | `loop`:pulse, `item`:any, `index`:number, `then`:pulse |
| `gate` | Let the pulse through only while a condition holds. | control (`gate`) | `exec`:pulse, `open`:boolean | `then`:pulse |
| `once` | Let the pulse through the first time only. | control (`once`) | `exec`:pulse | `then`:pulse |
| `open_scene` | Switch to another scene. | effect | `scene`:string, `exec`:pulse | `then`:pulse |
| `open_space` | Switch to another space. | effect | `space`:string, `exec`:pulse | `then`:pulse |
| `repeat` | Run the body a fixed number of times. | control (`repeat`) | `exec`:pulse, `count`:number | `loop`:pulse, `index`:number, `then`:pulse |
| `sequence` | Run several chains in a defined order. | effect | `exec`:pulse | `first`:pulse, `second`:pulse, `third`:pulse |
### functions
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `call_patch` | Runs another patch β how you reuse a graph. | data | β | β |
| `call_script` | Calls a function exported by a script. | data | `value`:number | `result`:number |
### globals
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `get_global` | Reads one. | data | β | `value`:number |
| `on_global_change` | Fires when a shared value changes. | event (`global`) | β | `pulse`:pulse, `value`:number |
| `set_global` | Stores a value shared with every graph, script and event. | effect | `value`:number, `exec`:pulse | `then`:pulse |
### io
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `patch_input` | An input socket on this patch when it is used by another. | data | β | `value`:number |
| `patch_output` | An output socket. | data | `value`:number | β |
### lifecycle
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `create_entity` | Makes a new, empty object. | effect | `name`:string, `parent`:object, `exec`:pulse | `then`:pulse, `object`:object |
| `destroy` | Removes an object. | effect | `object`:object, `exec`:pulse | `then`:pulse |
| `spawn` | Makes an object from a resource β a model, an image, a sound. | effect | `resource`:string, `parent`:object, `exec`:pulse | `then`:pulse, `object`:object |
### list
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `list` | Builds a list from its inputs; drag the handle for more slots. | data | `a`:any, `b`:any, `c`:any, `d`:any | `value`:any |
| `list_get` | The item at an index. | data | `list`:any, `index`:number | `item`:any |
| `list_length` | How many items. | data | `list`:any | `result`:number |
| `list_push` | Adds an item to the end. | data | `list`:any, `item`:any | `result`:any |
### logic
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `and` | True when both inputs are true. | data | `a`:boolean, `b`:boolean | `result`:boolean |
| `equals` | True when the two inputs match. | data | `a`:number, `b`:number | `result`:boolean |
| `greater_or_equal` | Larger, or the same. | data | `a`:number, `b`:number | `result`:boolean |
| `greater_than` | True when the first is larger. | data | `a`:number, `b`:number | `result`:boolean |
| `if_else` | Pick one of two values by a condition β the data twin of branch. | data | `condition`:boolean, `then`:number, `else`:number | `result`:number |
| `less_or_equal` | Smaller, or the same. | data | `a`:number, `b`:number | `result`:boolean |
| `less_than` | True when the first is smaller. | data | `a`:number, `b`:number | `result`:boolean |
| `not` | Flips true and false. | data | `value`:boolean | `result`:boolean |
| `not_equal` | True when they differ. | data | `a`:number, `b`:number | `result`:boolean |
| `or` | True when either is. | data | `a`:boolean, `b`:boolean | `result`:boolean |
| `same` | True when two objects are the same object. | data | `a`:any, `b`:any | `result`:boolean |
| `select` | Pick a value from several by index. | data | `condition`:boolean, `then`:any, `else`:any | `result`:any |
### math
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `abs` | Drops the sign. | data | `value`:number | `result`:number |
| `acos` | Inverse cosine. | data | `value`:number | `result`:number |
| `add` | Adds. | data | `a`:number, `b`:number | `result`:number |
| `asin` | Inverse sine. | data | `value`:number | `result`:number |
| `atan` | Inverse tangent. | data | `value`:number | `result`:number |
| `atan2` | The angle to a point, handling every quadrant β how you aim at something. | data | `a`:number, `b`:number | `result`:number |
| `ceil` | Rounds up. | data | `value`:number | `result`:number |
| `clamp` | Keeps a value inside a range. | data | `value`:number, `min`:number, `max`:number | `result`:number |
| `cos` | Cosine. | data | `value`:number | `result`:number |
| `deg_to_rad` | Degrees to radians. | data | `value`:number | `result`:number |
| `divide` | Divides. | data | `a`:number, `b`:number | `result`:number |
| `exp` | e to the power of the input. | data | `value`:number | `result`:number |
| `floor` | Rounds down. | data | `value`:number | `result`:number |
| `lerp` | Blends between two values β 0 gives the first, 1 the second. | data | `a`:number, `b`:number, `t`:number | `result`:number |
| `log` | Natural logarithm. | data | `value`:number | `result`:number |
| `max` | The larger of two. | data | `a`:number, `b`:number | `result`:number |
| `min` | The smaller of two. | data | `a`:number, `b`:number | `result`:number |
| `modulo` | The remainder β the usual way to wrap a value round. | data | `a`:number, `b`:number | `result`:number |
| `multiply` | Multiplies. | data | `a`:number, `b`:number | `result`:number |
| `power` | Raises to a power. | data | `a`:number, `b`:number | `result`:number |
| `rad_to_deg` | Radians to degrees. | data | `value`:number | `result`:number |
| `random` | A random number in a range. | data | `min`:number, `max`:number | `value`:number |
| `remap` | Rescales a value from one range to another. Extremely useful; reach for it often. | data | `value`:number, `inMin`:number, `inMax`:number, `outMin`:number, `outMax`:number | `result`:number |
| `round` | Rounds to nearest. | data | `value`:number | `result`:number |
| `sign` | Gives β1, 0 or 1. | data | `value`:number | `result`:number |
| `sin` | Sine β the building block of anything that oscillates. | data | `value`:number | `result`:number |
| `sqrt` | Square root. | data | `value`:number | `result`:number |
| `subtract` | Subtracts. | data | `a`:number, `b`:number | `result`:number |
| `tan` | Tangent. | data | `value`:number | `result`:number |
### messaging
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `on_message` | Receives one. | event (`message`) | β | `pulse`:pulse, `payload`:object |
| `post_message` | Sends a named message to anything listening. | effect | `payload`:object, `exec`:pulse | `then`:pulse |
### net
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `net_connected` | True when a room is actually open. | data | β | `value`:boolean |
| `net_despawn` | Removes one everywhere. | effect | `id`:string, `exec`:pulse | `then`:pulse |
| `net_id` | This participantβs id β the usual key for their own state. | data | β | `value`:string |
| `net_peer_count` | How many. | data | β | `value`:number |
| `net_peers` | Everyone currently in the room. | data | β | `value`:any |
| `net_send` | Sends a message to everyone else in the room. | effect | `data`:any, `exec`:pulse | `then`:pulse |
| `net_spawn` | Creates an object on every participantβs screen. | effect | `resource`:string, `position`:vector, `rotation`:vector, `exec`:pulse | `then`:pulse, `id`:string |
| `net_state_get` | Reads it. | data | β | `value`:any |
| `net_state_set` | Writes to the shared state everyone can read. | effect | `value`:any, `exec`:pulse | `then`:pulse |
| `on_net_join` | Someone joined. | event (`net-join`) | β | `pulse`:pulse, `peer`:string |
| `on_net_leave` | Someone left. | event (`net-leave`) | β | `pulse`:pulse, `peer`:string |
| `on_net_message` | Receives one. | event (`net`) | β | `pulse`:pulse, `data`:any, `from`:string |
| `on_net_state` | Fires when a shared value changes. | event (`net-state`) | β | `pulse`:pulse, `value`:any |
### physics
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `apply_force` | A continuous push while it runs β for thrust and wind. | effect | `object`:object, `force`:vector, `exec`:pulse | `then`:pulse |
| `apply_impulse` | A single push β for jumps, hits and launches. | effect | `object`:object, `force`:vector, `exec`:pulse | `then`:pulse |
| `apply_torque` | A twisting push. | effect | `object`:object, `torque`:vector, `exec`:pulse | `then`:pulse |
| `get_angular_velocity` | How fast it is spinning. | data | `object`:object | `value`:vector |
| `get_speed` | How fast, ignoring direction. | data | `object`:object | `value`:number |
| `get_velocity` | How fast and which way something is moving. | data | `object`:object | `value`:vector |
| `is_sleeping` | True when a body has settled and stopped simulating. | data | `object`:object | `value`:boolean |
| `raycast` | Fires a ray and reports what it hit. | control (`await`) | `from`:vector, `to`:vector, `ignore`:object, `exec`:pulse | `then`:pulse, `hit`:boolean, `object`:object, `point`:vector, `normal`:vector, `distance`:number |
| `set_angular_velocity` | Sets spin outright. | effect | `object`:object, `velocity`:vector, `exec`:pulse | `then`:pulse |
| `set_gravity` | Changes gravity for the whole scene. | effect | `gravity`:vector, `exec`:pulse | `then`:pulse |
| `set_velocity` | Sets speed and direction outright. | effect | `object`:object, `velocity`:vector, `exec`:pulse | `then`:pulse |
| `sleep_body` | Puts one to sleep. | effect | `object`:object, `exec`:pulse | `then`:pulse |
| `wake_body` | Wakes a settled body. | effect | `object`:object, `exec`:pulse | `then`:pulse |
### state
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `counter` | Counts pulses. Survives between frames. | effect | `inc`:pulse | `then`:pulse, `count`:number |
| `smooth` | Eases a value toward a target each frame β the cheap way to make anything feel less rigid. | effect | `tick`:pulse, `target`:number, `rate`:number | `then`:pulse, `value`:number |
| `timer` | Counts seconds while it runs. | effect | `tick`:pulse, `add`:number | `then`:pulse, `time`:number |
| `toggle` | Flips between true and false each pulse. | effect | `exec`:pulse | `then`:pulse, `value`:boolean |
### string
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `concat` | Joins text together. | data | `a`:string, `b`:string | `result`:string |
| `string_length` | How many characters. | data | `value`:string | `result`:number |
| `to_number` | Reads a number out of text. | data | `value`:string | `result`:number |
| `to_string` | Turns a value into text β for labels and score displays. | data | `value`:number | `result`:string |
### ui
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `get_divkit` | Reads a variable from an interface card. | data | `object`:object | `value`:any |
| `set_divkit` | Writes one β how a graph updates a score or a label. | effect | `object`:object, `value`:any, `exec`:pulse | `then`:pulse |
### variables
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `get_variable` | Reads it back. | data | β | `value`:any |
| `set_variable` | Stores a value for this graph. | effect | `value`:any, `exec`:pulse | `then`:pulse |
### vector
| Node | What it does | Kind | Inputs | Outputs |
| --- | --- | --- | --- | --- |
| `vector_add` | Adds two vectors. | data | `a`:vector, `b`:vector | `result`:vector |
| `vector_cross` | Cross product β a direction perpendicular to both. | data | `a`:vector, `b`:vector | `result`:vector |
| `vector_distance` | Distance between two points. | data | `a`:vector, `b`:vector | `result`:number |
| `vector_dot` | Dot product β how much two directions agree. Use it to tell "in front" from "behind". | data | `a`:vector, `b`:vector | `result`:number |
| `vector_length` | How long the vector is. | data | `v`:vector | `result`:number |
| `vector_lerp` | Blends between two positions. | data | `a`:vector, `b`:vector, `t`:number | `result`:vector |
| `vector_normalize` | Same direction, length 1. | data | `v`:vector | `result`:vector |
| `vector_scale` | Multiplies a vector by a number. | data | `v`:vector, `s`:number | `result`:vector |
| `vector_subtract` | Subtracts one from the other β the direction from B to A. | data | `a`:vector, `b`:vector | `result`:vector |
---
# Shader node reference
Source: https://docs.arclip.design/creators-engine/reference/shader-nodes
{/* GENERATED by scripts/gen-engine-reference.ts β do not edit by hand. */}
# Shader node reference
95 nodes across 7 categories, for building materials as graphs. How to use them is in [Material graphs](/creators-engine/authoring/shader-graphs).
Port types are `float`, `vec2`, `vec3` and `vec4`. A `vec3` carries a colour or a direction; connecting mismatched widths is handled for you where it makes sense.
### const
Fixed values you type in.
| Node | What it does | Inputs | Outputs |
| --- | --- | --- | --- |
| `constFloat` | A fixed number. | β | `out`:float |
| `constVec3` | A fixed triple. | β | `out`:vec3 |
### input
Where values come from: UVs, the surface normal, time, textures, the instance being drawn.
| Node | What it does | Inputs | Outputs |
| --- | --- | --- | --- |
| `cameraDistance` | How far this pixel is from the camera. Good for fading things out with distance. | β | `out`:float |
| `chromaKey` β Chroma key | Removes a colour, turning a green screen transparent. | `color`:vec3, `key`:vec3, `similarity`:float, `smoothness`:float, `spill`:float | `rgba`:vec4 |
| `instanceColor` β Instance Color | The tint that copy carries. | β | `out`:vec3 |
| `instanceId` β Instance ID | Which copy of an instanced object is being drawn β feed it into a hash to make every copy different. | β | `out`:float |
| `materialRef` β Material | Pulls in another material as a starting point. | β | `out`:vec3 |
| `normal` | Which way the surface faces. | β | `out`:vec3 |
| `normalMap` | Applies a normal map, adding surface detail without more geometry. | `uv`:vec2, `strength`:float | `out`:vec3 |
| `paramColor` β Param (color) | A colour exposed the same way. | β | `out`:vec3 |
| `paramFloat` β Param (float) | A number exposed as a material setting, adjustable without editing the graph. | β | `out`:float |
| `position` | The point on the surface, in the objectβs own space. | β | `out`:vec3 |
| `sampleLod` β Sample Texture LOD | Samples at a chosen blur level β cheap softness. | `uv`:vec2, `lod`:float | `rgba`:vec4 |
| `screenUv` | Where the pixel is on screen rather than on the surface. | β | `out`:vec2 |
| `texelFetch` β Texel Fetch | Reads one exact pixel, unfiltered. | `texel`:vec2 | `rgba`:vec4 |
| `texelSize` β Texture Size | The size of one pixel of a texture, for neighbour-sampling effects. | β | `size`:vec2, `texel`:vec2 |
| `texture` | Samples an image at some coordinates. | `uv`:vec2 | `rgba`:vec4 |
| `time` | Seconds since the scene started. Anything that moves starts here. | β | `out`:float |
| `triplanar` | Projects a texture from three directions at once, so untextured or stretched geometry still looks right. | `scale`:float, `blend`:float | `rgba`:vec4 |
| `uv` | The texture coordinates of the pixel being drawn β the basis of most patterns. | β | `out`:vec2 |
| `viewDirection` | The direction from the surface toward the camera. | β | `out`:vec3 |
| `worldPosition` | The same point in world space β use it for effects that stay put as an object moves. | β | `out`:vec3 |
### light
Lighting terms you can shape yourself.
| Node | What it does | Inputs | Outputs |
| --- | --- | --- | --- |
| `fresnel` | Stronger at glancing angles β rim light, force fields, the sheen on a car. | `power`:float, `normal`:vec3 | `out`:float |
| `lit` β Lit (scene lights) | Standard lighting, so a custom graph still sits correctly in the scene. | `color`:vec3, `roughness`:float, `specular`:float, `normal`:vec3 | `out`:vec3 |
| `toonLit` β Toon (scene lights) | Banded lighting for a stylised look. | `color`:vec3, `steps`:float, `normal`:vec3 | `out`:vec3 |
### math
Arithmetic and curves on single numbers.
| Node | What it does | Inputs | Outputs |
| --- | --- | --- | --- |
| `abs` | Drops the sign. | `x`:float | `out`:float |
| `add` | Adds. | `a`:float, `b`:float | `out`:float |
| `atan2` | The angle to a point β how you build radial patterns. | `y`:float, `x`:float | `out`:float |
| `ceil` | Rounds up. | `x`:float | `out`:float |
| `clamp` | Keeps a value inside a range. | `x`:float, `min`:float, `max`:float | `out`:float |
| `cos` | Cosine. | `x`:float | `out`:float |
| `divide` | Divides. | `a`:float, `b`:float | `out`:float |
| `exp` | e to the power of the input. | `x`:float | `out`:float |
| `fbm` | Layered noise β clouds, smoke, rust, terrain. | `uv`:vec2, `scale`:float, `octaves`:float | `out`:float |
| `floor` | Rounds down. | `x`:float | `out`:float |
| `fract` | Just the fractional part β the standard way to make anything repeat. | `x`:float | `out`:float |
| `log` | Natural logarithm. | `x`:float | `out`:float |
| `max` | The larger of two. | `a`:float, `b`:float | `out`:float |
| `min` | The smaller of two. | `a`:float, `b`:float | `out`:float |
| `mix` | Blends between two values. | `a`:float, `b`:float, `t`:float | `out`:float |
| `mod` | The remainder; the other way to wrap a value. | `x`:float, `y`:float | `out`:float |
| `multiply` | Multiplies. | `a`:float, `b`:float | `out`:float |
| `noise` | Smooth random values. The starting point for anything organic. | `uv`:vec2, `scale`:float | `out`:float |
| `oneMinus` | Flips a 0β1 value round. Deceptively useful. | `x`:float | `out`:float |
| `posterize` | Snaps values to a number of steps β banding, on purpose. | `x`:float, `steps`:float | `out`:float |
| `pow` | Raises to a power. | `x`:float, `e`:float | `out`:float |
| `remap` | Rescales from one range to another. | `x`:float, `inMin`:float, `inMax`:float, `outMin`:float, `outMax`:float | `out`:float |
| `round` | Rounds to nearest. | `x`:float | `out`:float |
| `sign` | Gives β1, 0 or 1. | `x`:float | `out`:float |
| `sin` | Sine β every wobble, pulse and ripple starts with this. | `x`:float | `out`:float |
| `smoothstep` | A soft edge, with a gradient between the two thresholds. The workhorse of shader graphs. | `from`:float, `to`:float, `x`:float | `out`:float |
| `sqrt` | Square root. | `x`:float | `out`:float |
| `step` | A hard edge: below the threshold 0, above it 1. | `edge`:float, `x`:float | `out`:float |
| `subtract` | Subtracts. | `a`:float, `b`:float | `out`:float |
| `tan` | Tangent. | `x`:float | `out`:float |
| `voronoi` | Cellular patterns: scales, cracked earth, stained glass. | `uv`:vec2, `scale`:float | `dist`:float, `cell`:float |
### output
Where the graph ends: what the surface actually looks like.
| Node | What it does | Inputs | Outputs |
| --- | --- | --- | --- |
| `mrtOutput` β Output (MRT) | Writes several outputs at once, for advanced pipelines. | `color`:vec3, `alpha`:float, `color1`:vec3, `alpha1`:float, `color2`:vec3, `alpha2`:float, `color3`:vec3, `alpha3`:float | β |
| `output` | Where the graph ends: the finished look of the surface. | `color`:vec3, `alpha`:float, `offset`:vec3 | β |
| `surfaceOutput` β Output (surface) | The physical description β base colour, metallic, roughness, normal, emissive, alpha. | `baseColor`:vec3, `metallic`:float, `roughness`:float, `emissive`:vec3, `normal`:vec3, `alpha`:float, `offset`:vec3, `clearcoat`:float, `clearcoatRoughness`:float, `ior`:float, `transmission`:float, `thickness`:float | β |
### post
Nodes that sample the rendered frame β these make the graph a full-screen effect rather than a surface.
| Node | What it does | Inputs | Outputs |
| --- | --- | --- | --- |
| `chromaticAberration` | Splits colour channels at the edges, like a real lens. | `uv`:vec2, `amount`:float | `out`:vec3 |
| `contrast` | Pushes light and dark apart. | `color`:vec3, `amount`:float | `out`:vec3 |
| `exposure` | Brightens or darkens the whole image. | `color`:vec3, `amount`:float | `out`:vec3 |
| `grain` | Adds film grain. | `color`:vec3, `intensity`:float | `out`:vec3 |
| `invert` | Inverts it. | `color`:vec3 | `out`:vec3 |
| `saturation` | More or less colourful. | `color`:vec3, `amount`:float | `out`:vec3 |
| `screenColor` | The scene as already rendered. Using this makes the graph a full-screen effect. | β | `out`:vec3 |
| `tint` | Pushes the image toward a colour. | `color`:vec3, `tint`:vec3 | `out`:vec3 |
| `vignette` | Darkens the edges of the frame. | `color`:vec3, `offset`:float, `darkness`:float | `out`:vec3 |
### vector
Building, splitting and transforming vectors and colours.
| Node | What it does | Inputs | Outputs |
| --- | --- | --- | --- |
| `addVec3` | Adds two triples. | `a`:vec3, `b`:vec3 | `out`:vec3 |
| `alpha` | Pulls the transparency channel out. | `v`:vec4 | `out`:float |
| `combineVec2` | Builds a pair. | `x`:float, `y`:float | `out`:vec2 |
| `combineVec3` | Builds a triple. | `r`:float, `g`:float, `b`:float | `out`:vec3 |
| `combineVec4` | Builds a quad. | `rgb`:vec3, `a`:float | `out`:vec4 |
| `cross` | A direction perpendicular to both. | `a`:vec3, `b`:vec3 | `out`:vec3 |
| `distance` | Distance between two points. | `a`:vec3, `b`:vec3 | `out`:float |
| `dot` | Dot product β how much two directions agree. | `a`:vec3, `b`:vec3 | `out`:float |
| `equirectUv` β Equirect UV | Maps a direction onto a panoramic image. | `dir`:vec3 | `uv`:vec2 |
| `flipbook` β Flipbook | Plays a grid of frames in one image as an animation. | `uv`:vec2, `frame`:float, `tilesX`:float, `tilesY`:float | `uv`:vec2 |
| `gamma` | Applies a gamma curve. | `rgb`:vec3, `gamma`:float | `out`:vec3 |
| `hsvToRgb` | And back again. | `hsv`:vec3 | `out`:vec3 |
| `hueToRgb` | A hue straight to a colour. | `h`:float | `out`:vec3 |
| `length` | How long a vector is. | `v`:vec3 | `out`:float |
| `luminance` | How bright a colour reads to the eye. | `rgb`:vec3 | `out`:float |
| `mixVec3` | Blends two triples. | `a`:vec3, `b`:vec3, `t`:float | `out`:vec3 |
| `multiplyVec3` | Multiplies two triples. | `a`:vec3, `b`:vec3 | `out`:vec3 |
| `normalize` | Same direction, length 1. | `v`:vec3 | `out`:vec3 |
| `panner` | Scrolls coordinates over time β flowing water, conveyor belts, energy beams. | `uv`:vec2, `speed`:vec2 | `out`:vec2 |
| `reflect` | Bounces a direction off a surface. | `v`:vec3, `normal`:vec3 | `out`:vec3 |
| `rgb` | Builds a colour from three numbers. | `v`:vec4 | `out`:vec3 |
| `rgbToHsv` | Colour to hue, saturation and value β the easy way to shift hue. | `rgb`:vec3 | `out`:vec3 |
| `scaleVec2` | Scales a pair by a number. | `v`:vec2, `s`:float | `out`:vec2 |
| `scaleVec3` | Scales a triple by a number. | `v`:vec3, `s`:float | `out`:vec3 |
| `splitVec2` | Takes a pair apart. | `v`:vec2 | `x`:float, `y`:float |
| `splitVec3` | Takes a triple apart. | `v`:vec3 | `x`:float, `y`:float, `z`:float |
| `uvTransform` | Scales, rotates and offsets coordinates before sampling. | `uv`:vec2, `tiling`:vec2, `offset`:vec2, `rotation`:float, `center`:vec2 | `out`:vec2 |
---
# Step reference
Source: https://docs.arclip.design/creators-engine/reference/steps
{/* GENERATED by scripts/gen-engine-reference.ts β do not edit by hand. */}
# Step reference
29 steps. A step is what an event actually *does* when its trigger fires. A script can run any of them with `ctx.step(name, params)` instead of reimplementing the behaviour.
| In the editor | Category | Deprecated | Id (scripts, patches) |
| --- | --- | --- | --- |
| **Contact download** | `action` | β | `contact_download_action` |
| **Compose email** | `action` | β | `email_action` |
| **Release pointer** | `action` | β | `exit_pointer_lock` |
| **File download** | `action` | β | `file_download_action` |
| **Follow object** | `action` | β | `follow` |
| **Look at object** | `action` | β | `look_at_target` |
| **Open messenger** | `action` | β | `messenger_action` |
| **Call phone number** | `action` | β | `phone_call_action` |
| **Lock pointer** | `action` | β | `request_pointer_lock` |
| **Scene transition** | `action` | β | `scene_transit_action` |
| **Show / hide object** | `action` | β | `set_visibility` |
| **Space transition** | `action` | β | `space_transit_action` |
| **Stop following** | `action` | β | `stop_follow` |
| **Open URL** | `action` | β | `url_transit_action` |
| **Fly along path** | `animation` | β | `follow_path` |
| **Built-in animation** | `animation` | yes | `launch_built_in_animation` |
| **Material** | `animation` | yes | `material_animation` |
| **Opacity** | `animation` | yes | `opacity_animation` |
| **Play animation** | `animation` | β | `play_animation` |
| **Bone transform** | `animation` | yes | `skeletal_transform` |
| **Stop animation** | `animation` | β | `stop_animation` |
| **Stop path flight** | `animation` | β | `stop_follow_path` |
| **Transformation** | `animation` | yes | `transformation_animation` |
| **Send signal** | `logic` | β | `emit_signal` |
| **If β otherwise** | `logic` | β | `if_action` |
| **Set variable** | `logic` | β | `set_variable` |
| **Wait, then** | `logic` | β | `wait_action` |
| **Override properties** | `state` | β | `override_state` |
| **Set state** | `state` | β | `set_state` |
## Parameters
### Contact download
Id `contact_download_action` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `photo` | `resource` | β | Photo |
| `fullName` | `string` | β | Full name |
| `mobilePhone` | `string` | β | Mobile phone |
| `workPhone` | `string` | β | Work phone |
| `homePhone` | `string` | β | Home phone |
| `company` | `string` | β | Company |
| `email` | `string` | β | Email |
| `position` | `string` | β | Position |
| `address` | `string` | β | Address |
| `website` | `string` | β | Website |
### Compose email
Id `email_action` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `email` | `string` | β | Email |
### Release pointer
Id `exit_pointer_lock` Β· category `action`
No parameters.
### File download
Id `file_download_action` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `resource` | `resource` | β | File |
### Follow object
Id `follow` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `target` | `entity` | `null` | Follow |
| `distance` | `number` | `0` | Keep distance |
| `offsetX` | `number` | `0` | Offset X |
| `offsetY` | `number` | `0` | Offset Y |
| `offsetZ` | `number` | `0` | Offset Z |
| `smoothing` | `number` | `0.2` | Smoothing |
| `maxSpeed` | `number` | `0` | Max speed |
| `lockY` | `boolean` | `false` | Keep own height |
| `faceTarget` | `boolean` | `true` | Face target |
| `turnSpeed` | `number` | `0` | Turn speed |
### Look at object
Id `look_at_target` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `target` | `entity` | `null` | Look at |
| `turnSpeed` | `number` | `0` | Turn speed |
| `lockY` | `boolean` | `true` | Keep upright |
### Open messenger
Id `messenger_action` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `platform` | `select` | β | `facebook`, `vk`, `instagram`, `whatsapp`, `telegram`, `viber`, `ok` |
| `value` | `string` | β | Value |
### Call phone number
Id `phone_call_action` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `phone` | `string` | β | Phone |
### Lock pointer
Id `request_pointer_lock` Β· category `action`
No parameters.
### Scene transition
Id `scene_transit_action` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `sceneId` | `scene` | β | Scene |
### Show / hide object
Id `set_visibility` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `visible` | `boolean` | `true` | Visible |
### Space transition
Id `space_transit_action` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `spaceId` | `space` | β | Space |
| `sceneId` | `scene` | `null` | Scene |
### Stop following
Id `stop_follow` Β· category `action`
No parameters.
### Open URL
Id `url_transit_action` Β· category `action`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `url` | `string` | β | URL |
| `transitionType` | `select` | `"new_tab"` | `new_tab`, `same_tab` |
### Fly along path
Id `follow_path` Β· category `animation`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `path` | `entity` | `null` | Path |
| `duration` | `number` | `3` | Duration |
| `loop` | `select` | `"once"` | `once`, `loop`, `ping_pong` |
| `orient` | `boolean` | `true` | Face along path |
| `reverse` | `boolean` | `false` | Reverse |
### Built-in animation
Id `launch_built_in_animation` Β· category `animation` Β· **deprecated** (still executes, no longer offered in palettes)
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `builtInAnimation` | `string` | β | Animation |
| `startFrame` | `number` | `0` | |
| `delay` | `number` | `0` | |
| `repeatCount` | `number` | `1` | |
### Material
Id `material_animation` Β· category `animation` Β· **deprecated** (still executes, no longer offered in palettes)
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `materialId` | `resource` | β | Material |
| `duration` | `number` | `1` | |
| `delay` | `number` | `0` | |
| `repeatCount` | `number` | `1` | |
### Opacity
Id `opacity_animation` Β· category `animation` Β· **deprecated** (still executes, no longer offered in palettes)
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `opacity` | `number` | `100` | |
| `duration` | `number` | `1` | |
| `delay` | `number` | `0` | |
| `repeatCount` | `number` | `1` | |
### Play animation
Id `play_animation` Β· category `animation`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `animator` | `entity` | `null` | |
| `presetId` | `string` | `""` | |
| `loopType` | `select` | `"once"` | `once`, `count`, `forever` |
| `repeatCount` | `number` | `1` | |
| `speed` | `number` | `1` | |
| `delay` | `number` | `0` | |
### Bone transform
Id `skeletal_transform` Β· category `animation` Β· **deprecated** (still executes, no longer offered in palettes)
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `positionX` | `number` | `0` | |
| `positionY` | `number` | `0` | |
| `positionZ` | `number` | `0` | |
| `quatX` | `number` | `0` | |
| `quatY` | `number` | `0` | |
| `quatZ` | `number` | `0` | |
| `quatW` | `number` | `1` | |
| `scaleX` | `number` | `1` | |
| `scaleY` | `number` | `1` | |
| `scaleZ` | `number` | `1` | |
| `duration` | `number` | `1` | |
| `delay` | `number` | `0` | |
| `loopType` | `select` | `"count"` | `once`, `count`, `forever` |
| `repeatCount` | `number` | `1` | |
### Stop animation
Id `stop_animation` Β· category `animation`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `animator` | `entity` | `null` | |
| `presetId` | `string` | `""` | |
### Stop path flight
Id `stop_follow_path` Β· category `animation`
No parameters.
### Transformation
Id `transformation_animation` Β· category `animation` Β· **deprecated** (still executes, no longer offered in palettes)
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `rotationEnabled` | `boolean` | `true` | |
| `rotationX` | `number` | `0` | |
| `rotationY` | `number` | `0` | |
| `rotationZ` | `number` | `0` | |
| `positionEnabled` | `boolean` | `true` | |
| `positionX` | `number` | `0` | |
| `positionY` | `number` | `0` | |
| `positionZ` | `number` | `0` | |
| `scaleEnabled` | `boolean` | `true` | |
| `scaleX` | `number` | `1` | |
| `scaleY` | `number` | `1` | |
| `scaleZ` | `number` | `1` | |
| `duration` | `number` | `1` | |
| `delay` | `number` | `0` | |
| `loopType` | `select` | `"count"` | `once`, `count`, `forever` |
| `repeatCount` | `number` | `1` | |
### Send signal
Id `emit_signal` Β· category `logic`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `signal` | `string` | `""` | Signal |
| `scope` | `select` | `"targets"` | `targets`, `broadcast` |
### If β otherwise
Id `if_action` Β· category `logic`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `source` | `select` | `"always"` | `always`, `variable`, `distance`, `state`, `visible`, `random` |
| `variable` | `string` | `""` | Variable |
| `target` | `entity` | `null` | Object |
| `op` | `select` | `"=="` | `==`, `!=`, `>`, `>=`, `<`, `<=` |
| `value` | `string` | `""` | Value |
| `threshold` | `number` | `1` | Threshold |
| `stateId` | `state` | `null` | State |
| `flag` | `boolean` | `true` | Expected |
### Set variable
Id `set_variable` Β· category `logic`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `variable` | `string` | `""` | Variable |
| `mode` | `select` | `"set"` | `set`, `add`, `toggle` |
| `value` | `string` | `""` | Value |
### Wait, then
Id `wait_action` Β· category `logic`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `seconds` | `number` | `1` | Delay |
### Override properties
Id `override_state` Β· category `state`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `patch` | `record` | `{}` | |
| `durationMs` | `number` | `0` | |
| `easing` | `select` | `"ease-in-out"` | `linear`, `ease-in`, `ease-out`, `ease-in-out` |
### Set state
Id `set_state` Β· category `state`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `stateId` | `state` | β | State |
| `durationMs` | `number` | `0` | |
| `easing` | `select` | `"ease-in-out"` | `linear`, `ease-in`, `ease-out`, `ease-in-out` |
---
# Trigger reference
Source: https://docs.arclip.design/creators-engine/reference/triggers
{/* GENERATED by scripts/gen-engine-reference.ts β do not edit by hand. */}
# Trigger reference
35 triggers. The same names work in three places: an event's trigger, a patch's event node, and `ctx.on(...)` in a script.
A trigger with **branches** keeps one list of steps per outcome, so one event answers all of them β see [Events](/creators-engine/authoring/events).
| In the editor | Category | Branches | Id (scripts, patches) |
| --- | --- | --- | --- |
| **Game controls** | `control` | `idle`, `move`, `jump`, `run` | `on-game-control` |
| **UI action** | `external` | β | `on-divkit-action` |
| **Signal** | `external` | β | `on-signal` |
| **Variable change** | `external` | β | `on-variable-change` |
| **Hover out** | `input` | β | `on-blur` |
| **Click** | `input` | β | `on-click` |
| **Double click** | `input` | β | `on-double-click` |
| **Drag** | `input` | β | `on-drag` |
| **Hover** | `input` | `enter`, `exit` | `on-hover` |
| **Key** | `input` | `down`, `up` | `on-key` |
| **Long press** | `input` | β | `on-long-press` |
| **Mouse button** | `input` | `click`, `down`, `up` | `on-mouse` |
| **Pinch** | `input` | β | `on-pinch` |
| **Pointer lock** | `input` | β | `on-pointer-lock` |
| **Cursor move** | `input` | β | `on-pointer-move` |
| **Rotate** | `input` | β | `on-rotate` |
| **Scroll** | `input` | β | `on-scroll` |
| **Launch** | `lifecycle` | β | `on-launch` |
| **Pause** | `lifecycle` | β | `on-pause` |
| **Resize** | `lifecycle` | β | `on-resize` |
| **Resume** | `lifecycle` | β | `on-resume` |
| **Visibility** | `lifecycle` | `show`, `hide` | `on-visibility` |
| **Collision** | `physics` | β | `on-collide` |
| **Distance** | `proximity` | `enter`, `exit` | `on-distance` |
| **State active** | `state` | β | `on-state-active` |
| **State inactive** | `state` | β | `on-state-inactive` |
| **Every N seconds** | `time` | β | `on-interval` |
| **Delay** | `time` | β | `on-timeout` |
| **Detect** | `tracking` | β | `on-detect` |
| **Tracking lost** | `tracking` | β | `on-lost` |
| **Placed** | `tracking` | β | `on-placed` |
| **VPS error** | `tracking` | β | `on-vps-error` |
| **VPS localized** | `tracking` | β | `on-vps-localized` |
| **VPS localization failed** | `tracking` | β | `on-vps-not-localized` |
| **VPS ready** | `tracking` | β | `on-vps-ready` |
## Parameters
### Game controls
Id: `on-game-control`
Category `control` Β· branches `idle`, `move`, `jump`, `run`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `mode` | `select` | `"walk"` | `walk`, `fly`, `top_down` |
| `turnToMove` | `boolean` | `true` | Auto-orient |
| `towards` | `select` | `"camera"` | `camera`, `axis` |
| `cameraRelative` | `boolean` | `true` | Camera relative |
| `speed` | `number` | `3` | Pos. Speed |
| `runMultiplier` | `number` | `2` | Run Factor |
| `turnSpeed` | `number` | `720` | Rot. Speed |
| `posDelayIn` | `number` | `0` | Pos. Delay |
| `posDelayOut` | `number` | `0` | |
| `rotDelayIn` | `number` | `0` | Rot. Delay |
| `rotDelayOut` | `number` | `0` | |
| `scheme` | `select` | `"both"` | `wasd`, `arrows`, `both` |
| `rotateBy` | `select` | `"mouse"` | `mouse`, `keys`, `both` |
| `touch` | `boolean` | `true` | Touch controls |
| `touchRotateBy` | `select` | `"drag"` | `drag`, `joystick` |
| `stickCorner` | `select` | `"bottom_left"` | `top_left`, `top_center`, `top_right`, `middle_left`, `middle_center`, `middle_right`, `bottom_left`, `bottom_center`, `bottom_right` |
| `jumpCorner` | `select` | `"bottom_right"` | `top_left`, `top_center`, `top_right`, `middle_left`, `middle_center`, `middle_right`, `bottom_left`, `bottom_center`, `bottom_right` |
| `touchOffsetX` | `number` | `30` | Offset |
| `touchOffsetY` | `number` | `30` | |
| `buttonSize` | `number` | `120` | Button Size |
| `touchVisibility` | `select` | `"show"` | `show`, `hide` |
| `colliderShape` | `select` | `"capsule"` | `capsule`, `sphere` |
| `colliderRadius` | `number` | `0.5` | Size |
| `colliderHeight` | `number` | `1.8` | |
| `colliderX` | `number` | `0` | Position |
| `colliderY` | `number` | `0.9` | |
| `colliderZ` | `number` | `0` | |
| `mass` | `number` | `1` | Mass |
| `colliderHelper` | `select` | `"hide"` | `show`, `hide` |
| `jumpHeight` | `number` | `0` | Jump |
| `airControl` | `number` | `0.3` | Air Control |
| `resetY` | `number` | `0` | Rst Y-offset |
| `clickToMove` | `boolean` | `false` | Click To Move |
| `followCamera` | `entity` | `null` | Camera |
| `orientTo` | `select` | `"normal"` | `normal`, `angle` |
| `camPosDelay` | `number` | `0.12` | Pos. Delay |
| `camRotDelay` | `number` | `0.08` | Rot. Delay |
| `upDown` | `select` | `"yes"` | `yes`, `no`, `limit` |
| `upDownMin` | `number` | `0` | Limit |
| `upDownMax` | `number` | `89` | |
| `leftRight` | `select` | `"yes"` | `yes`, `no`, `limit` |
| `leftRightMin` | `number` | `-90` | Limit |
| `leftRightMax` | `number` | `90` | |
### UI action
Id: `on-divkit-action`
Category `external`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `id` | `string` | β | Action id |
### Signal
Id: `on-signal`
Category `external`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `signal` | `string` | `""` | Signal |
### Variable change
Id: `on-variable-change`
Category `external`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `variable` | `string` | `""` | Variable |
### Hover out
Id: `on-blur`
Category `input`
No parameters.
### Click
Id: `on-click`
Category `input`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `button` | `select` | `"any"` | `any`, `left`, `right`, `middle` |
### Double click
Id: `on-double-click`
Category `input` Β· scope `entity`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `button` | `select` | `"any"` | `any`, `left`, `right`, `middle` |
### Drag
Id: `on-drag`
Category `input` Β· scope `entity` Β· only on anchors `surface`
No parameters.
### Hover
Id: `on-hover`
Category `input` Β· branches `enter`, `exit` Β· scope `entity`
No parameters.
### Key
Id: `on-key`
Category `input` Β· branches `down`, `up`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `code` | `string` | β | Code |
| `codes` | `array` | `[]` | Keys |
| `ctrl` | `boolean` | `false` | |
| `alt` | `boolean` | `false` | |
| `shift` | `boolean` | `false` | |
| `meta` | `boolean` | `false` | |
| `double` | `boolean` | `false` | Double press |
| `bind` | `string` | `""` | Project bind |
| `alts` | `array` | `[]` | Alternatives |
| `ignoreRepeat` | `boolean` | `false` | Ignore auto-repeat |
### Long press
Id: `on-long-press`
Category `input` Β· scope `entity`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `button` | `select` | `"any"` | `any`, `left`, `right`, `middle` |
### Mouse button
Id: `on-mouse`
Category `input` Β· branches `click`, `down`, `up` Β· scope `entity`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `button` | `select` | `"any"` | `any`, `left`, `right`, `middle` |
| `ctrl` | `boolean` | `false` | |
| `alt` | `boolean` | `false` | |
| `shift` | `boolean` | `false` | |
| `meta` | `boolean` | `false` | |
| `double` | `boolean` | `false` | Double press |
| `bind` | `string` | `""` | Project bind |
| `alts` | `array` | `[]` | Alternatives |
### Pinch
Id: `on-pinch`
Category `input` Β· scope `entity` Β· only on anchors `surface`
No parameters.
### Pointer lock
Id: `on-pointer-lock`
Category `input`
No parameters.
### Cursor move
Id: `on-pointer-move`
Category `input`
No parameters.
### Rotate
Id: `on-rotate`
Category `input` Β· scope `entity` Β· only on anchors `surface`
No parameters.
### Scroll
Id: `on-scroll`
Category `input`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `direction` | `select` | `"any"` | `any`, `up`, `down` |
### Launch
Id: `on-launch`
Category `lifecycle`
No parameters.
### Pause
Id: `on-pause`
Category `lifecycle`
No parameters.
### Resize
Id: `on-resize`
Category `lifecycle`
No parameters.
### Resume
Id: `on-resume`
Category `lifecycle`
No parameters.
### Visibility
Id: `on-visibility`
Category `lifecycle` Β· branches `show`, `hide`
No parameters.
### Collision
Id: `on-collide`
Category `physics`
No parameters.
### Distance
Id: `on-distance`
Category `proximity` Β· branches `enter`, `exit`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `target` | `entity` | `null` | Other object |
| `distance` | `number` | `1` | Distance |
| `fireOnStart` | `boolean` | `false` | Check at start |
### State active
Id: `on-state-active`
Category `state` Β· fires per state
No parameters.
### State inactive
Id: `on-state-inactive`
Category `state` Β· fires per state
No parameters.
### Every N seconds
Id: `on-interval`
Category `time`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `seconds` | `number` | `1` | Every |
| `repeat` | `number` | `0` | Repeat |
### Delay
Id: `on-timeout`
Category `time`
| Param | Type | Default | Options / label |
| --- | --- | --- | --- |
| `seconds` | `number` | `1` | After |
### Detect
Id: `on-detect`
Category `tracking` Β· only on anchors `image`, `qr`, `face`, `surface`
No parameters.
### Tracking lost
Id: `on-lost`
Category `tracking` Β· only on anchors `image`, `qr`, `face`, `surface`
No parameters.
### Placed
Id: `on-placed`
Category `tracking` Β· only on anchors `surface`
No parameters.
### VPS error
Id: `on-vps-error`
Category `tracking` Β· only on anchors `surface`
No parameters.
### VPS localized
Id: `on-vps-localized`
Category `tracking` Β· only on anchors `surface`
No parameters.
### VPS localization failed
Id: `on-vps-not-localized`
Category `tracking` Β· only on anchors `surface`
No parameters.
### VPS ready
Id: `on-vps-ready`
Category `tracking` Β· only on anchors `surface`
No parameters.
---
# The ctx API
Source: https://docs.arclip.design/creators-engine/scripting/ctx-reference
# The `ctx` API
`ctx` is your handle on the running scene β it is passed to `init` and everything below hangs
off it.
It is deliberately small. If you are looking for something and cannot find it here, there is a
good chance the answer is a [built-in step](#running-built-in-behaviour) rather than an API.
## Where you are
| | |
| --- | --- |
| `ctx.entity` | the object this script is attached to |
| `ctx.scene` | the scene it belongs to |
| `ctx.space` | the world |
## Lifecycle and events
```ts
ctx.tick((dt, t) => {}); // every frame; dt and t in seconds
ctx.effect(() => {}); // re-runs when what it reads changes; may return a cleanup
ctx.on(trigger, (payload) => {}); // subscribe to a trigger
ctx.emit(trigger, payload); // raise one from this object
```
Triggers use the same names as events and patches. The ones that carry useful information:
| Trigger | You get |
| -------------------------------------- | ---------------------------------------------- |
| `on-keydown` Β· `on-keyup` | `{ code, ctrl, shift, alt, meta }` |
| `on-state-active` Β· `on-state-inactive`| `{ stateId }` |
| `on-collide` | `{ other }` β what you hit |
| `on-divkit-action` | `{ id, β¦ }` β which button |
| `on-game-control` | `{ state }` β idle, move, run or jump |
| `on-drag` Β· `on-pinch` Β· `on-rotate` | `{ dx, dy }` Β· `{ scale }` Β· `{ angle }` |
| `on-vps-localized` | where the visitor turned out to be |
`on-launch` arrives the moment your instance is created, so you cannot miss it by starting late.
## Finding objects
```ts
ctx.get(id);
ctx.findByName('Door');
ctx.find(LightComponent); // the first object with these components
ctx.query(LightComponent, TagsComponent); // all of them
ctx.all();
```
## Creating and removing
```ts
ctx.create({ name: 'Bullet', parent, components: [] });
ctx.spawn(props.bulletModel, { parent });
ctx.destroy(entity);
```
`spawn` is the convenient one: hand it a resource and it assembles the right components for it.
A model becomes a model object, an image becomes a textured plane, a sound becomes an audio
source.
## Running built-in behaviour
```ts
ctx.step('play_animation', { presetId }, { targets: [enemy] });
ctx.startTransition({ durationMs: 400, easing: 'ease-out' }, () => {
// changes made in here ease instead of snapping
});
```
:::tip This is the shortcut most people miss
`ctx.step` runs any of the [built-in steps](/creators-engine/scripting/../reference/steps) β the same ones your events
use. Animation, state switching, scene transitions and transitions are one call away, so you
almost never need to reimplement them.
:::
## Navigation
```ts
ctx.openScene(sceneOrId);
await ctx.openSpace(spaceRefOrId);
ctx.scenes();
```
## Input
Two layers, for two different jobs.
**Named actions** read the project's key bindings, so a visitor who rebinds their keys is
respected:
```ts
ctx.input.pressed('jump');
ctx.input.justPressed('fire');
ctx.input.axis('moveX');
```
**Raw keys** read the keyboard directly. Presses last exactly one frame, so read them inside
`ctx.tick`:
```ts
ctx.keyboard.down('KeyW');
ctx.keyboard.press('Space');
ctx.keyboard.press('ArrowLeft', { every: 200 }); // auto-repeat, in ms
ctx.keyboard.axis('KeyA', 'KeyD'); // -1, 0 or 1
```
:::note The two treat modifiers differently, on purpose
A named binding ignores modifiers it does not mention β sprinting with Shift held must not
cancel "forward". A key *trigger* is the opposite: a modifier you did not tick means "must not
be held".
:::
Losing window focus clears held keys, so nothing gets stuck down.
## Camera
```ts
ctx.camera.entity(); // the active camera object
ctx.camera.setActive(target); // switch cameras; null restores the default
ctx.camera.pose(); // { position, rotation, forward } in world space
```
The camera's transform is the source of truth in every control mode: write to it to move the
camera, read it to see where the controls put it. In orbit and first-person modes the controls
own the orientation, so a rotation you write is overwritten β position is respected.
## Raycasting
```ts
await ctx.raycast(); // from the camera centre
await ctx.raycast({ screen: { x: 0.5, y: 0 }, all: true }); // a screen point, every hit
await ctx.raycast({ origin, direction }); // any ray you like
await ctx.raycast({ from: entity }); // from an object, along its forward
```
Each hit tells you the object, the distance, and the point and surface normal **in world
space** β sorted nearest first, one hit per object.
:::note It returns a promise, and that is not a mistake
The ray is cast against real geometry on the rendering side, so the answer arrives next frame.
Invisible objects and things marked to be ignored (reticles, gizmos) are skipped, so whatever
is behind them answers instead of the ray reporting a miss.
:::
## Physics {#physics}
```ts
ctx.physics.applyImpulse(target, { x: 0, y: 5, z: 0 });
ctx.physics.applyForce(target, vec, point);
ctx.physics.setVelocity(target, vec);
ctx.physics.teleport(target, position, { rotation, keepVelocity });
ctx.physics.setGravity(vec);
ctx.physics.getSpeed(target);
ctx.physics.isSleeping(target);
await ctx.physics.raycast(from, to, { skip: [ctx.entity] });
```
:::danger Setting a dynamic body's transform does nothing
Physics owns its position. Use `teleport` to place it and impulses or forces to move it.
And pass `skip` when firing a ray from inside your own body, or you will hit yourself every
single time.
:::
Readings come from the last synchronised state and lag about a frame β fine for "am I moving?",
wrong for exact instantaneous maths.
## Audio {#audio}
```ts
ctx.audio.play(props.hitSound, { at: enemy, volume: 0.6, positional: true });
```
Every call starts an independent sound, which is exactly what footsteps, impacts and gunfire
need. The audio *component* is a single voice and will cut itself off β do not use it for
effects.
## Keeping things
Three stores, differing in how far they reach:
```ts
// 1. this script's own values
const store = ctx.store('game', { score: { type: 'number', default: 0 } });
store.set('score', (v) => v + 1);
store.subscribe('score', (v) => {});
// 2. globals β shared with every script, patch and event
ctx.setGlobal('level', 3);
ctx.getGlobal('level');
ctx.subscribeGlobal('level', (v) => {});
// 3. messages between scripts
ctx.postMessage('enemy-died', { id });
ctx.handleMessage('enemy-died', ({ id }) => {});
```
| | Survives a scene change | Survives moving between spaces | Survives reload |
| -------------------- | ----------------------- | ------------------------------- | --------------- |
| `store` | yes | no | no |
| globals | yes | yes | no |
| space-scoped globals | yes | no β deliberately isolated | no |
:::warning Nothing here survives closing the tab
None of these three is saved between visits. If something must persist, send it somewhere
yourself while you still have a connection.
:::
## Interface
```ts
const ui = ctx.getDivKit(entity);
ui.get('score');
ui.set('score', (v) => v + 1);
ui.subscribe('lives', (v) => {});
ui.onAction('restart', () => {});
```
See [UI cards](/creators-engine/scripting/../authoring/ui-cards).
---
**Next:** [Things you will actually build](/creators-engine/scripting/recipes) β complete scripts to copy.
---
# Your first script
Source: https://docs.arclip.design/creators-engine/scripting/first-script
# Your first script
A script is a TypeScript file you attach to an object. It runs in the player, once for every
object that uses it.
Here is a complete one β tap the object and it starts spinning:
```ts title="Scripts/spinner.ts"
init((ctx) => {
const props = defineProps({
speed: { type: 'number', default: 1, min: 0, label: 'Speed' },
});
let spinning = false;
ctx.on('on-click', () => {
spinning = !spinning;
});
ctx.tick((dt) => {
if (!spinning) return;
const t = ctx.entity.getComponent(TransformComponent);
t?.update({
rotation: { ...t.$data.rotation, y: t.$data.rotation.y + props.speed * dt },
});
});
return () => {
// optional: tidy up when this object goes away
};
});
```
Four things are going on:
| | |
| --- | --- |
| **`init`** | receives `ctx`, your handle on the running scene. Everything starts here |
| **`defineProps`** | declares settings the editor shows in the inspector, filled in per object |
| **`ctx.on`** | subscribes to a trigger β the same triggers events and patches use |
| **`ctx.tick`** | runs every frame, with `dt` in seconds |
`init` and `defineProps` are always available β you never import them.
## Reading and writing objects
```ts
const transform = ctx.entity.getComponent(TransformComponent);
transform.position; // read one value
transform.$data; // read the whole thing as a plain object
transform.update({ β¦ }); // write
```
Two traps, and they are the two that catch everybody:
:::danger Writing a nested value replaces all of it
`update({ position: { y: 2 } })` also sets `x` and `z` to zero β you handed it a whole new
position with only `y` filled in.
Spread what you want to keep:
```ts
transform.update({ position: { ...transform.$data.position, y: 2 } });
```
:::
:::danger A material is a list of slots
`material.update({ color })` does nothing at all, because `color` lives inside a slot rather
than at the top. Write the slot:
```ts
material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});
```
:::
And never assign into `$data` directly. It looks like it worked, and the change is dropped.
## Settings in the inspector
`defineProps` is what makes a script worth reusing: the same script on ten objects, configured
differently on each, with no copy-paste.
```ts
const props = defineProps({
speed: { type: 'number', default: 1, min: 0, softMax: 10, suffix: 'm/s' },
target: { type: 'entity', label: 'Look at' },
sound: { type: 'resource', resource: 'audio' },
mode: { type: 'select', options: ['chase', 'patrol'], default: 'patrol' },
});
```
| Type | The editor shows | Your script gets |
| ---------- | --------------------- | --------------------------------- |
| `number` | a field or slider | a number |
| `string` | a text field | a string |
| `boolean` | a switch | a boolean |
| `color` | a colour picker | a colour string |
| `select` | a dropdown | one of your options |
| `entity` | an object picker | the object itself, ready to use |
| `scene` | a scene picker | the scene object |
| `resource` | a resource picker | a reference you can pass along |
| `array` | a list | an array |
| `group` | a titled block | a nested object |
Worth knowing: `label` and `help` for the panel, `min` and `max` for genuine limits, `softMax`
for where a slider ends without forbidding larger typed values, `showWhen` to reveal a field
only when another has a particular value, and `group` to keep a long list tidy.
:::note `defineProps` has to be written out literally
The editor reads your settings without running the script, so it needs to see them directly β
not built from variables or returned by a function.
:::
Values are read fresh every time, so `props.speed` always reflects what is in the inspector
right now.
## Importing things
```ts
import { TransformComponent } from '@was/engine';
import helpers from 'Scripts/helpers';
```
You can import the engine's component classes, and **other resources by path** β another script
gives you its exports, a patch gives you its compiled module, and anything else gives you a
reference you can hand to `ctx.spawn` or `ctx.audio.play`.
Rename or move a resource and these imports update themselves.
## What the sandbox gives you β and does not
Scripts run isolated from the page, which keeps a heavy script from stalling rendering. So
these are **not** available:
- `window`, `document`, the DOM;
- `fetch`, `localStorage`, network access of any kind;
- any rendering library β you change the scene through components, not by drawing;
- browser timers β use `ctx.tick` instead.
What you use instead: [UI cards](/creators-engine/scripting/../authoring/ui-cards) for interface, `ctx.audio` for
sound, `ctx.store` and globals for keeping things.
## When your script starts and stops
An instance is created when its object is live and on screen β nothing above it disabled, its
scene active β and destroyed when that stops being true.
`on-launch` fires as soon as it is created, so you never miss it. The function you return from
`init` is your cleanup: unsubscribe, stop sounds, clear state.
:::warning Scripts do not run in the editor, and there is no hot reload
The editor draws your scene but does not execute logic. Edit, then open **Preview**. A running
experience picks up an edited script when it next restarts.
:::
---
**Next:** [Where a script lives](/creators-engine/scripting/where-scripts-live)
---
# Multiplayer
Source: https://docs.arclip.design/creators-engine/scripting/multiplayer
# Multiplayer
Everything below is live only when your project has multiplayer switched on **and** the player
opened a room.
The rest of the time these calls quietly do nothing, which is deliberate: a script written for
multiplayer still runs perfectly well in a single-player preview. You do not need two versions.
```ts
ctx.net.id; // this participant's id
ctx.net.peers(); // everyone currently in the room
ctx.net.onJoin((peer) => {});
ctx.net.onLeave((peer) => {});
```
## What replicates by itself
Only the **positions of physics bodies marked shared**. Everything else you send yourself.
That sounds like a small amount, and it is the right small amount: a crate, a door or a ball
should be one object everybody agrees on, while a player character must not be β otherwise one
person walking moves everybody's avatar. β [ownership](/creators-engine/scripting/../concepts/components#physics-components)
## Messages
For events: someone scored, a round started, a door was opened.
```ts
ctx.net.send('score', { points: 10 });
ctx.net.on('score', ({ from, data }) => {
/* β¦ */
});
```
Fire-and-forget, to everyone except you.
## Shared state
For values everyone needs to agree on. It is a simple map where the last write wins, replicated
to everybody.
```ts
ctx.net.state.set(ctx.net.id, { name, colour, ready: true });
ctx.net.state.get(somePeerId);
ctx.net.state.all();
ctx.net.state.on(ctx.net.id, (value) => {});
```
Keying by `ctx.net.id` is the standard pattern for per-player state: everyone writes their own
entry and reads everyone else's.
## Spawning for everyone
```ts
const id = ctx.net.spawn(props.bullet, { position, rotation });
ctx.net.despawn(id);
```
The id comes back immediately, but the object appears once the room confirms it. That round
trip is what keeps the id identical on every client, which is what lets physics agree about it
afterwards.
Only whoever spawned something may despawn it, and leaving the room takes your spawns with you.
## Drawing other people smoothly
This is the part that most often goes wrong, so it has a dedicated call.
```ts
ctx.net.follow(otherPlayerObject, { position, rotation });
ctx.net.unfollow(otherPlayerObject);
```
Call it with each position you receive, ten to twenty times a second is normal, and the
runtime writes the transform every frame instead, playing the buffer back slightly behind so it
always has two known positions to move between.
:::warning Do not write incoming positions straight into a transform
Packets arrive far less often than frames, so the object stands still and then jumps: remote
players appear to step rather than walk. Easing toward the newest packet does not fix it either
β between packets there is nothing ahead of the current moment to ease toward, so it stalls and
then lurches.
:::
Re-feeding the same position costs nothing, so calling `follow` every frame with whatever you
currently hold is the intended use.
## A shape that works
If you are starting from scratch, this division of labour will get you a long way:
- **each player owns their own character** β a local body, driven by their input;
- **their position goes out** through `net.state` or a channel, ten to twenty times a second;
- **everyone else's characters are drawn with `follow`**;
- **the world's objects are shared bodies**, so physics keeps them consistent for free;
- **the score and round state live in `net.state`**, written by whoever is authoritative for
that event.
---
**Next:** [Troubleshooting](/creators-engine/scripting/../troubleshooting) β when something is not behaving.
---
# Things you will actually build
Source: https://docs.arclip.design/creators-engine/scripting/recipes
# Things you will actually build
Every script here is complete. Copy one, attach it to an object, change the numbers.
If you have not written one before, start with [Your first script](/creators-engine/scripting/first-script) β it
explains the shape these all share.
## Spin something forever
```ts
init((ctx) => {
const props = defineProps({
speed: { type: 'number', default: 45, suffix: 'Β°/s' },
});
ctx.tick((dt) => {
const t = ctx.entity.getComponent(TransformComponent);
if (!t) return;
const turn = (props.speed * Math.PI) / 180; // degrees β radians
t.update({
rotation: { ...t.$data.rotation, y: t.$data.rotation.y + turn * dt },
});
});
});
```
:::note Why multiply by `dt`
`dt` is how long the last frame took. Multiplying by it means the object turns the same amount
per *second* on a fast phone and a slow one. Leave it out and your animation runs at whatever
speed the device happens to manage.
:::
## Make something bob gently
```ts
init((ctx) => {
const t = ctx.entity.getComponent(TransformComponent);
const startY = t?.$data.position.y ?? 0;
ctx.tick((dt, time) => {
t?.update({
position: { ...t.$data.position, y: startY + Math.sin(time * 2) * 0.1 },
});
});
});
```
`Math.sin` swings smoothly between β1 and 1 forever. Multiply it to set how far it moves,
multiply the time to set how fast.
## Tap to collect
```ts
init((ctx) => {
const props = defineProps({
points: { type: 'number', default: 1 },
sound: { type: 'resource', resource: 'audio' },
});
ctx.on('on-click', () => {
ctx.audio.play(props.sound);
ctx.setGlobal('score', ((ctx.getGlobal('score') ?? 0) + props.points));
ctx.destroy(ctx.entity);
});
});
```
The score lives in a **global** so anything else (a counter on screen, the win condition) can
read it.
## Show that score on screen
Put this on the object holding your interface card:
```ts
init((ctx) => {
const ui = ctx.getDivKit(ctx.entity);
ctx.subscribeGlobal('score', (value) => {
ui.set('score', Number(value ?? 0));
});
ui.set('score', ctx.getGlobal('score') ?? 0); // show the starting value
});
```
:::tip This is the pattern to remember
**Your logic owns the numbers; the card just displays them.** Trying to keep the score inside
the card breaks down as soon as anything else needs it.
:::
## A countdown
```ts
init((ctx) => {
const props = defineProps({ seconds: { type: 'number', default: 60 } });
const ui = ctx.getDivKit(ctx.entity);
let left = props.seconds;
let finished = false;
ctx.tick((dt) => {
if (finished) return;
left -= dt;
if (left <= 0) {
finished = true;
left = 0;
ctx.emit('on-signal', { signal: 'time-up' });
}
ui.set('time', `${Math.ceil(left)}`);
});
});
```
## Do something every few seconds
```ts
init((ctx) => {
const props = defineProps({ every: { type: 'number', default: 3, suffix: 's' } });
let since = 0;
ctx.tick((dt) => {
since += dt;
if (since < props.every) return;
since = 0;
ctx.spawn(props.thing, { parent: ctx.scene });
});
});
```
There is no `setInterval` here. Counting seconds in `tick` is the equivalent, and it stops by
itself when the object goes away.
## Spawn something that has its own behaviour
This one catches people out. `ctx.spawn` builds an object **from a resource**, so spawning a
model gives you a model β and nothing else. It does not carry a script.
For an enemy that chases, a bullet that flies, a pickup that reacts, build the object yourself
and attach the behaviour:
```ts
init((ctx) => {
const props = defineProps({
model: { type: 'resource', resource: 'scene' },
brain: { type: 'resource', resource: 'script' },
});
const spawnEnemy = (x: number, z: number) => {
const enemy = ctx.create({
name: 'Enemy',
parent: ctx.scene,
components: [
new ModelRefComponent({ referal: props.model?.id ?? null }),
new ScriptComponent({ referal: props.brain?.id ?? null }),
new RigidBodyComponent({ type: 'dynamic' }),
new ColliderComponent({ type: 'capsule', radius: 0.3, height: 1.6 }),
],
});
enemy.getComponent(TransformComponent)?.update({ position: { x, y: 0, z } });
return enemy;
};
ctx.on('on-launch', () => spawnEnemy(2, 0));
});
```
Each spawned object gets its **own instance** of that script, with its own variables β so
health, state and timers per enemy need nothing special:
```ts
// the enemy's own script
init((ctx) => {
let health = 3;
ctx.on('on-collide', () => {
health -= 1;
if (health <= 0) ctx.destroy(ctx.entity);
});
});
```
:::tip Give the spawner a collider too, if physics should see it
An object built with `create` is exactly the components you listed. Forget the collider and it
will not collide; forget the rigid body and physics will not move it.
:::
## Find the nearest of something
The other half of a tower, an enemy, or anything that targets:
```ts
const nearest = (ctx, from: Entity, candidates: Entity[]) => {
const a = from.getComponent(TransformComponent)?.$data.position;
if (!a) return undefined;
let best: Entity | undefined;
let bestGap = Infinity;
for (const c of candidates) {
const b = c.getComponent(TransformComponent)?.$data.position;
if (!b) continue;
const gap = Math.hypot(b.x - a.x, b.y - a.y, b.z - a.z);
if (gap < bestGap) { bestGap = gap; best = c; }
}
return best;
};
```
Get the candidates with `ctx.query(...)` β ask for a component only enemies carry, or mark them
with [tags](/creators-engine/scripting/../concepts/components#tags) when you create them and filter on that.
## Follow the player, but not too closely
```ts
init((ctx) => {
const props = defineProps({
target: { type: 'entity' },
distance: { type: 'number', default: 2 },
speed: { type: 'number', default: 1.5 },
});
ctx.tick((dt) => {
const me = ctx.entity.getComponent(TransformComponent);
const them = props.target?.getComponent(TransformComponent);
if (!me || !them) return;
const a = me.$data.position;
const b = them.$data.position;
const dx = b.x - a.x;
const dz = b.z - a.z;
const gap = Math.hypot(dx, dz);
if (gap <= props.distance) return; // close enough
const step = Math.min(props.speed * dt, gap - props.distance);
me.update({
position: { ...a, x: a.x + (dx / gap) * step, z: a.z + (dz / gap) * step },
});
});
});
```
Dividing by `gap` turns the direction into a length of exactly 1, so multiplying by `step`
moves precisely that far. That trick comes up constantly.
## Shoot what you are looking at
```ts
init((ctx) => {
ctx.on('on-click', async () => {
const hits = await ctx.raycast();
const hit = hits[0];
if (!hit?.entity) return;
const meta = hit.entity.getComponent(MetaComponent);
if (meta?.$data.name.startsWith('Target')) ctx.destroy(hit.entity);
});
});
```
`await` is needed because the ray is traced against the real scene and the answer comes back on
the next frame.
## Throw a physics object
```ts
init((ctx) => {
const props = defineProps({ force: { type: 'number', default: 6 } });
ctx.on('on-click', () => {
const pose = ctx.camera.pose();
if (!pose) return;
const ball = ctx.spawn(props.ball, { parent: ctx.scene });
ctx.physics.teleport(ball, pose.position);
ctx.physics.applyImpulse(ball, {
x: pose.forward.x * props.force,
y: pose.forward.y * props.force,
z: pose.forward.z * props.force,
});
});
});
```
:::warning Place it with `teleport`, push it with `applyImpulse`
Setting the position of a physics object directly does nothing β physics owns where it is and
writes over you on the next step.
:::
## A door that opens once
```ts
init((ctx) => {
let open = false;
ctx.on('on-click', () => {
if (open) return;
open = true;
ctx.startTransition({ durationMs: 600, easing: 'ease-out' }, () => {
const t = ctx.entity.getComponent(TransformComponent);
t?.update({ rotation: { ...t.$data.rotation, y: Math.PI / 2 } });
});
});
});
```
`startTransition` is what makes the change glide instead of snapping. Everything you change
inside it eases together.
## React to a collision
```ts
init((ctx) => {
ctx.on('on-collide', ({ other }) => {
const hit = ctx.get(other);
const name = hit?.getComponent(MetaComponent)?.$data.name ?? '';
if (name !== 'Player') return;
ctx.audio.play(props.thud);
ctx.postMessage('player-hit', { by: ctx.entity.id });
});
});
```
`postMessage` tells other scripts without either of them needing a reference to the other.
## Wait for the scene to be tracked
```ts
init((ctx) => {
ctx.on('on-detect', () => {
ctx.step('play_animation', { presetId: 'intro' });
});
ctx.on('on-lost', () => {
ctx.step('stop_animation', { presetId: 'intro' });
});
});
```
:::tip `ctx.step` saves you rewriting the engine
Anything the editor can do as a step (animations, transitions, states, navigation) a script
can trigger with one call. Look through the [step reference](/creators-engine/scripting/../reference/steps) before
building something by hand.
:::
## A product configurator
The commonest commercial build on this platform, and it is mostly two moves: swap what is shown,
and remember what was chosen.
```ts
init((ctx) => {
const props = defineProps({
variants: {
type: 'array',
item: { type: 'resource', resource: 'material' },
label: 'Finishes',
},
prices: { type: 'array', item: { type: 'number' }, label: 'Price per finish' },
});
const ui = ctx.getDivKit(ctx.entity);
const material = ctx.entity.getComponent(MaterialComponent);
const choose = (index: number) => {
const finish = props.variants[index];
if (!finish || !material) return;
// A material slot can point at a saved material instead of carrying values.
material.update({
materials: [{ ...material.$data.materials[0], type: 'ref', referal: finish.id }],
});
ctx.setGlobal('finish', index);
ui.set('price', props.prices[index] ?? 0);
};
ui.onAction('finish-0', () => choose(0));
ui.onAction('finish-1', () => choose(1));
ui.onAction('finish-2', () => choose(2));
ui.onAction('buy', () => {
const index = ctx.getGlobal('finish') ?? 0;
ctx.step('url_transit_action', {
url: `https://shop.example.com/chair?finish=${index}`,
transitionType: 'new_tab',
});
});
choose(0); // start on the first finish
});
```
Swapping a whole **model** rather than a finish is the same shape, writing `referal` on the
model component instead:
```ts
ctx.entity.getComponent(ModelRefComponent)?.update({ referal: props.models[index]?.id ?? null });
```
:::tip Give it a camera the visitor can orbit, and no AR
A configurator is usually a plain **3D** scene: it opens instantly, works on every device, and
needs no marker. Set the camera to `orbit` and limit the polar angle so nobody ends up looking
at it from underneath. β [Cameras](/creators-engine/scripting/../concepts/cameras)
:::
:::tip Turn photo capture on
A configurator people can screenshot is a configurator people share.
β [Project settings](/studio/settings/project-settings)
:::
## Where to put your state
| The value is⦠| Put it in |
| ------------------------------------------ | ---------------------------------------- |
| used by this script only | a plain variable in `init` |
| needed after the scene changes | `ctx.store` |
| needed by other scripts, events or patches | `ctx.setGlobal` |
| an event others should react to | `ctx.postMessage` |
| shared with other people in a room | `ctx.net.state` |
:::warning None of it survives closing the tab
If something must be there next time, send it to your own server while the visitor is still on
the page.
:::
---
**Next:** [Multiplayer](/creators-engine/scripting/multiplayer)
---
# Where a script lives
Source: https://docs.arclip.design/creators-engine/scripting/where-scripts-live
# Where a script lives
The same script behaves differently depending on what you attach it to. Choosing the level is
usually more important than the code.
| Attached to | Runs while | Dies when |
| --------------------- | ------------------------------------------- | -------------------------------- |
| **An object** | that object is on screen | it is hidden, disabled or deleted |
| **A scene** | that scene is active | the scene is left |
| **A space** | the visitor is in that space | they move to another space |
| **The project** | always, in every space | they leave |
## On an object
The default, and the right choice for behaviour that belongs to a thing: a door that opens, an
enemy that chases, a button that reacts.
Its `ctx.entity` is that object, and it stops the moment the object does β which is a feature.
You never clean up a script for an object that no longer exists.
## On a scene
Added in the scene's **Scripts** section. Use it for logic about the scene as a whole:
sequencing an intro, counting how many collectables are left, deciding when the visitor may
move on.
It starts when the scene becomes active and stops when it does β so in an AR project, it starts
on detection.
## On a space
Added in the space's **Scripts** section. This one **survives scene changes**, which is the
whole point: a score that carries across three scenes, a timer for the whole visit, a
controller deciding which scene comes next.
:::tip This is what "space-level" means technically
An entity with behaviour and **no parent** belongs to no scene. It keeps running as scenes come
and go, and every addressed event eventually reaches it β the route is object, then scene, then
space.
:::
## On the project
Added in project settings. Injected into **every space**, so it is there no matter where the
visitor goes.
Reach for it when something must outlive a space transition: the visitor's choices, an overall
progress counter, analytics.
## What survives what
| Stored in | A scene change | A space change | A reload |
| ------------------------ | :------------: | :------------: | :------: |
| a variable in `init` | if the script lives | if the script lives | no |
| `ctx.store` | yes | no | no |
| a global | yes | yes | no |
| a `@space:` global | yes | no β deliberately isolated | no |
:::warning Nothing survives closing the tab
Not `store`, not globals, not anything. If a value has to be there next time, send it somewhere
of your own while the visitor is still on the page.
:::
## Choosing, in one question
**What is the smallest thing this logic is about?**
- about one object β put it on the object;
- about this scene β put it on the scene;
- about the whole visit β put it on the space, or the project if there are several spaces.
Putting everything at project level "to be safe" works and then stops working: a project script
cannot assume any particular scene is loaded, so it ends up full of existence checks that an
object-level script would never need.
## Patches follow the same rule
A patch can be attached at any of these levels too, with identical lifetimes. A project-level
patch is the usual way to build a global controller without writing code.
---
**Next:** [The ctx API](/creators-engine/scripting/ctx-reference)
---
# Troubleshooting
Source: https://docs.arclip.design/creators-engine/troubleshooting
# Troubleshooting
Most problems in this engine have the same handful of causes. If something is not behaving,
there is a good chance it is on this page β start at the top, since the first two account for
most of it.
## "It works in preview but not in the editor"
It is not supposed to. **The editor does not run your logic** β no scripts, no patches, no
physics, no timers, no proximity triggers. It draws and edits the scene.
That is deliberate: an animation playing while you author would fight you, and a timer would
write changes into your project. Use **Preview** to see behaviour.
## "My script does not run"
Work down this list:
1. the object is **enabled**, and so is everything above it in the hierarchy;
2. its **scene is active** β in an AR scene, that means the marker has been detected;
3. the script component is **enabled**;
4. you are in **preview or a publication**, not the editor.
A script also restarts when you change one of its inspector properties, so a stray edit can
look like a crash.
## "I change a component and it snaps back"
Two likely causes.
**A state is active.** While a state is selected, changes to that object are recorded *into the
state*. Switching states then restores the snapshot. If you meant to change the object itself,
deselect the state first.
**Something else owns that property every frame.** A timeline bar, a physics body, or a look-at
all write continuously and will overwrite you. See the next item.
## "My animation fights another animation"
Two mechanisms are writing to the same object. The timeline writes every frame and always wins
against a transition.
Pick one mechanism per object. If you need a transition on something the timeline drives, move
it to a child object, or animate it on the timeline instead.
See [Animation](/creators-engine/authoring/animation).
## "My material change from a script does nothing"
A material is a **list of slots**, so `material.update({ color })` writes a field that does not
exist at that level. Write the slot:
```ts
material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});
```
The same trap applies to any nested value: `update` replaces what you hand it, so spread what
you want to keep.
## "My object falls through the floor" / "the model is not solid"
**An imported 3D model has no collision shape unless you give it a collider.** Add one β a box
or capsule is usually right.
Also check that the floor itself has a rigid body and a collider. Bare decorative geometry is
scenery, and things pass through it. See [Physics](/creators-engine/concepts/physics).
## "I set the position of a physics object and it ignores me"
Physics owns the position of a dynamic body and rewrites it every step. Use `teleport` for
placing it, and impulses or forces for moving it.
## "My UI is missing from the screenshot"
Cards are drawn in front of the 3D view, and a capture of the 3D view does not include them.
Check UI in preview.
## "Rotation values do not match"
Everything you can see (the inspector, patch node ports) is in **degrees**. Values read or
written **from a script** are in **radians**. Convert at the boundary.
## "Nothing happens when I press the key"
Check the casing: triggers are written `on-click`, steps `play_animation`. A mistyped name does
not error, it simply never matches.
If the event refers to a **project binding**, make sure that binding still exists β a reference
to a removed binding does not fall back to "any key", it stops firing.
## "It fires twice"
The same trigger is handled in two places β an event and a script, or an event and a patch. Pick
one. See [Events](/creators-engine/authoring/events).
## "The object appears a moment late"
Resources load while the scene is already running, so models, textures and text arrive shortly
after the scene opens. For content that must be present at the first frame, avoid depending on
a large download, or keep the scene hidden until it is ready.
## "There is no event for 'animation finished'"
There is not one, in any of the animation mechanisms. Use a wait step of the same length, or
track elapsed time in a script.
## "My undo removed someone else's change"
Undo history is your own and strictly linear β it does not know a collaborator edited the same
object in between. On a shared scene, prefer fixing a mistake forward over undoing your way
back through it.
---
## Still stuck?
If none of these fit, the two places most likely to have your answer are the
[reference tables](/creators-engine/reference/components) (every setting, generated from the engine) and
[Objects and scenes](/creators-engine/concepts/objects-and-scenes), which explains the model the rest of
this section assumes.
---
# Why this engine
Source: https://docs.arclip.design/creators-engine/why-this-engine
# Why this engine
Not a feature list β the handful of decisions that change what you can build, and why they
matter in practice.
## One model, everywhere
The editor, the web player, the phone app and the server all run **the same scene**: the same
objects, the same components, the same behaviour.
There is no export step, no separate "runtime format", no second implementation to drift out of
sync with the first. What you arranged in the editor is what runs, and a bug you see in the
player is reproducible everywhere.
The practical consequence is boring and valuable: **things do not change meaning when you
publish.**
## Graphs become code, not an interpreter
A visual graph in most tools is data that something walks at runtime, node by node, every
frame. Here it is **compiled into an ordinary script**, once, and that script is what runs.
| | |
| --- | --- |
| **Speed** | a graph costs the same as hand-written code, because it *is* code |
| **Inspectable** | open the code panel and read exactly what your graph became |
| **Composable** | a graph can call a script and a script can call a graph, because both are the same thing underneath |
So there is no ceiling where you have to abandon the visual tool and rewrite in code. You add a
script next to the graph you already have.
## Materials are graphs all the way down
The built-in material types are not a fixed set with a node editor bolted on. They are points
inside the same system a custom graph uses β so a graph material is not slower, not a
second-class citizen, and not limited to the web: the same graph compiles for the browser and
for the native renderer on a phone.
And what you do not use costs nothing. Physical extras that sit at their defaults are folded
away at compile time, so a material with the *option* of glass is exactly as cheap as one
without it.
## It works like a spreadsheet
This is the decision everything else rests on, and it is easiest to explain by analogy.
In a spreadsheet you change one cell, and only the formulas that use that cell recalculate.
Nobody recomputes the whole sheet, and you never press a "refresh" button.
**The scene works exactly that way.** Change an object's colour and the only things that react
are the ones that were reading the colour. Not the object β the *colour*.
```
change material.color
β
ββ the renderer repaints that surface
ββ the inspector field showing it updates
ββ the change is queued for your collaborators
ββ β¦and nothing else runs at all
```
### What that buys you
**Nothing polls and nothing re-renders speculatively.** There is no per-frame pass comparing the
world against a copy of itself to find what moved. A frame does the work that changes require,
and a still scene costs almost nothing.
**Big scenes stay responsive while you edit them.** A timeline with twenty thousand keyframes
does not re-draw because one of them moved: a panel can depend on "the keyframes changed"
without subscribing to every keyframe in the list. That distinction is what keeps large
projects usable.
**Loading needs no loading code.** A system asks for a resource and gets nothing back if it has
not arrived; when it does arrive, the system simply runs again. Nobody writes callbacks, polls
a ready flag, or handles the "not loaded yet" case twice.
**One change stream feeds everything.** The renderer, the inspector, undo, your collaborators,
the script sandbox and the physics simulation all read the same updates β which is why they
cannot drift apart or disagree about what the scene contains. Undo is not a special path; it is
another change going through the same pipe.
**You cannot forget to tell the engine.** There is no "mark dirty", no "refresh", no manual
invalidation to remember. Writing the value *is* the notification.
:::note And the writes are done carefully
Updating a value merges into what is already there rather than replacing it, so everything that
was watching stays attached to the same thing it was watching. That sounds like an
implementation detail; it is the difference between smooth animation and a frame-rate that sags
as your scene grows.
:::
### It is not a framework feature β it is the substrate
The reactivity is its own package, and **the same one is available to you**. A plugin panel, a
component extension and the editor's own inspector are all written against it, so an extension
you write updates for exactly the same reason the built-in panels do.
```ts
import { signal, computed, effect, batch } from '@was/signals';
const score = signal(0);
const label = computed(() => `Score: ${score.value}`);
effect(() => render(label.value)); // re-runs only when the score moves
batch(() => { score.value += 1; score.value += 1; }); // one update, not two
```
Three properties are worth naming, because most reactive systems have one or two of them:
| | |
| --- | --- |
| **Fine-grained** | dependencies are tracked per value read, not per component. Nothing re-renders "just in case". |
| **Deep** | a whole object tree becomes reactive, so watching `position.y` does not mean watching the object |
| **Cheap to depend on** | you can depend on *that something changed* without reading it, which is what keeps a huge list from re-rendering when one item moves |
That last one sounds obscure and is the reason a twenty-thousand-keyframe timeline stays
editable.
β [The shared packages](/creators-engine/concepts/shared-packages)
## Validation that does not cost you a frame
Every component is described by a schema, and schemas normally mean a tax: something walks a
description at runtime, field by field, every time data arrives.
Here the schema is **compiled once into code for that exact shape**. There is no walking. The
difference is not academic β measured on this project's own benchmark:
| | Parse | Added to the bundle |
| --- | --- | --- |
| The usual library (zod) | 94.7 ns per operation | +267 KB minified, +61 KB gzipped |
| This one (svdt) | **5.0 ns per operation** | none β it is already there |
About nineteen times faster, and nothing extra for your visitors to download.
That matters because validation is not a rare event here: it runs on every change that crosses
between the editor, the script sandbox, the physics simulation and your collaborators. A
per-field cost that looks negligible becomes the animation budget once it happens thousands of
times a second.
:::note You get it too
It is the same package your own plugin can import for its own data.
β [The shared packages](/creators-engine/concepts/shared-packages)
:::
## Heavy work stays off the frame
Two things that usually stall a browser page are moved off it:
- **Your scripts** run isolated from the page, so a slow loop cannot freeze rendering.
- **Physics** runs beside the frame rather than inside it.
A scene stays responsive while it is busy, rather than dropping frames when your logic gets
interesting.
## The runtime refuses work until it needs it
This is the part that shows up as loading time for your visitors.
**Physics does not start, or download, unless a scene actually needs it.** Scenery with no
moving parts never pays for the simulator, which is megabytes your visitors do not wait for.
The check is live: a ball created mid-session brings physics up for itself.
**Ten thousand copies cost about one.** Grass, crowds, debris and particles draw in a single
call. Their arrangement is *generated from a seed* rather than stored, so it costs nothing in
the file, nothing on the network, and looks identical for every participant.
**Nothing per-particle is ever synchronised.** The same settings produce the same effect
everywhere, so an elaborate effect is free in multiplayer.
## One vocabulary for behaviour
Events, patch graphs and scripts all hear the **same triggers** β `on-click` means the same
thing in all three. You learn one set of names, then choose how much code you want to write.
That also means you can start an interaction as an event, upgrade it to a patch when it needs a
condition, and drop into a script when it needs real logic β without relearning anything.
## Built for more than one person
Collaboration is not a feature bolted on top: a space is **one live world shared by everyone in
it**, so edits appear as they happen and undo works per person.
The same machinery synchronises the editor, the script sandbox and the physics simulation β
which is why they cannot disagree about what the scene contains.
## It runs where your audience already is
A published project opens **from a link, in a browser, with nothing to install**. That is the
whole point of WebAR: no app store, no download, no "please install our app first" between your
visitor and the thing you made.
From there, the same project reaches further without being rebuilt:
| Where | How it gets there |
| ----------------------- | -------------------------------------------------------------------------- |
| **Any phone browser** | the published link, or a QR code β nothing to install |
| **iOS App Clip** | opens from a link, a QR code or NFC, without an App Store install |
| **The native app** | a native renderer β see below |
| **Headset browsers** | the same published link β the runtime speaks WebXR |
| **AR glasses and headsets** | a native XR layer for XREAL One, Quest 3 and PICO 4 β see below |
| **Headsets via Unity** | the [Unity SDK](/vps/Unity%20SDK/platforms/) β XREAL, PICO, Apple Vision Pro, HoloLens 2, Magic Leap 2, Rokid |
:::tip App Clips are the shortest path from a poster to your content
Someone points a camera at a code and your experience opens β no store, no account, no wait.
Apple caps how large a clip may be, so keep the first scene light and load the rest once it is
running.
:::
:::note Headset builds through the Unity SDK are an Enterprise add-on
Phones work out of the box. The device-specific packages β rendering pipelines, controller
profiles, deployment templates β are granted per account.
β [Platform support](/vps/Unity%20SDK/platforms/)
:::
### On a phone, native rendering speed
In a browser you get WebGL, which is fast and has a ceiling. The native app does not render
through a browser at all: it uses a **native renderer** on both iOS and Android, talking to
Metal and Vulkan directly.
So the same project gets the graphics budget of a native app β real-time shadows, heavier
materials, more on screen β without you maintaining a second version of it. Your materials
compile for that renderer as well as for the web, which is what makes "the same project" true
rather than aspirational.
Use the web build for reach, and the app for the projects that need the frame budget.
### Glasses, natively
A native XR layer puts the AR Clip runtime itself on glasses: the same project, the same scene,
rendered on the device rather than in a browser.
It is built as **one interface with swappable vendor backends**, so a device is a backend rather
than a fork of the runtime β and it does not go through Unity:
| Device | Through | What works there |
| ---------------- | -------------- | ------------------------------------------------------------------- |
| **XREAL One** | the vendor SDK | display, head tracking, camera, session recording |
| **Meta Quest 3** | OpenXR | display, tracking, controllers, hand tracking, session recording |
| **PICO 4** | OpenXR | the same, with the camera switched off |
:::note Capabilities differ by device, and the runtime tells you which
A backend declares what it actually supports rather than pretending. PICO has no camera pass-
through here, so a scene that needs the real world behind it belongs on XREAL or Quest; a scene
that does not runs on all three.
:::
Whichever of those a visitor arrives through, it is the same scene with the same components and
the same behaviour. You are not maintaining a web version and a headset version.
## And it is extensible in the places that matter
Your own steps and triggers can be registered from an application. The editor takes
[plugins](/studio/plugins) that add panels, generate scenes, or contribute entirely new kinds of
component. The node catalogue has an escape hatch for the one case it does not cover.
:::note Why this documentation does not go stale
Every table of components, triggers, steps and nodes on this site is generated from the engine
itself. When the engine gains a node, the reference gains a row β nobody has to remember to
update it.
:::
---
**Next:** [Objects and scenes](/creators-engine/concepts/objects-and-scenes) β the model in full.
---
# Studio
Source: https://docs.arclip.design/studio/
import Link from '@docusaurus/Link';
# π¬ Welcome to Studio
Studio is where you build. It runs in your browser, saves as you work, and lets your whole team
edit the same scene at once.
This section is a tour of the interface and what each part is for. If you are looking for how
the engine *behaves* (components, physics, scripting) that lives in
[Creators Engine](/creators-engine).
---
## π§± How a project is organised
```
Project
βββ Space background Β· lighting Β· grid Β· units Β· camera
βββ Scene an entity with an Anchor β the trigger that makes it appear
βββ Entity
```
A **project** holds spaces. A **space** holds scenes and owns everything they share. A **scene**
is an entity carrying an **Anchor** component, which is the only thing that makes it a scene.
Everything in it is an ordinary entity.
### Components are not levels of this tree
An entity is **made of components**. They are not children, and they do not appear in the
hierarchy β they are what the entity *is*:
```
Entity "Lamp"
β Transform where it is
β Geometry its shape
β Material how it looks
β Events what it reacts to
β UI an interface card attached to it
β Script behaviour
```
`UI`, `Events`, `Script`, `Patch`, `Animation`, `States` β all of these are **components**, sitting
alongside Transform and Geometry. A scene's interface is a UI component on the scene entity, not
a separate layer beside it.
:::tip The one distinction to hold on to
**Containment** β an entity inside another entity. Moving the parent moves the child.
**Composition** β a component on an entity. It is an aspect of that entity, one of each kind,
added with **+ New component**.
Both look like "inside" in the editor, and telling them apart is most of understanding this
engine.
:::
So the three things you will meet are all the same kind of object:
| It is | Because it has | And it lacks |
| --------------------- | ------------------------------------------------- | ------------------ |
| a **scene** | an Anchor component | a Transform |
| an ordinary **entity**| a Transform, plus whatever else you added | an Anchor |
| **space-level logic** | a Script or Patch component, and **no parent** | both |
β [Objects and scenes](/creators-engine/concepts/objects-and-scenes) explains the model in full.
---
## π Start here
πΊοΈ First project
Build and publish something small, end to end, in about ten minutes.
Get started
π§ Interface tour
What every panel, rail and button in the editor does.
Take the tour
β¨οΈ Shortcuts
The keys that make editing fast.
See shortcuts
---
## π§° The specialised editors
Studio has four editors inside it, each opened from the left rail:
| Editor | For |
| ----------------------- | -------------------------------------------------------------------- |
| **Animation Controller**| choreographing movement on a timeline |
| **Patch Editor** | building logic as a node graph |
| **UI Editor** | designing the 2D interface over your scene |
| **Material Editor** | the look of a surface |
And Studio itself can be extended: **[plugins](/studio/plugins)** add panels, generators and even
your own kinds of component.
---
## π₯ Working together
Studio is collaborative. Everyone editing a project sees each other's avatars in the top bar
and each other's changes as they happen. Undo is your own β it will not roll back a colleague's
work, but note that it also does not know they changed the same object in between.
**Comments** let you leave a note pinned to a spot in the scene, which is usually a better way
to hand over feedback than describing where something is.
---
# Your own components
Source: https://docs.arclip.design/studio/component-extensions
# Your own components
The second kind of plugin does not open a panel at all. It **attaches to an object as a
component** and draws its interface inside the editor's own inspector, alongside Transform and
Material.
This is the right shape when your tool is *about a particular object* β notes on an object, a
custom control, a generator that belongs to the thing it generates.
## Declaring one
Declare it in `components`; `panels` can be empty:
```json
{
"id": "entity-notes",
"entry": "main.tsx",
"panels": [],
"components": [{ "id": "notes", "title": "Notes", "entry": "main.tsx" }]
}
```
## Writing one
```tsx
import {
Button,
Column,
Text,
defineProps,
useEntity,
useSignals,
useSyncedState,
} from '@was/extension';
import { TransformComponent } from '@was/engine';
const useProps = defineProps({ step: { type: 'number', default: 0.5 } });
export default function Notes() {
useSignals(); // track scene data read during render
const props = useProps(); // values from the inspector form
const entity = useEntity(); // the object this is attached to
const [note, setNote] = useSyncedState('note', '');
return (
{note}
);
}
```
Four hooks do the work:
| Hook | Gives you |
| ------------------ | ---------------------------------------------------------------- |
| `useEntity()` | the object your component is attached to |
| `useProps()` | the values from the form you declared with `defineProps` |
| `useSignals()` | re-renders when the scene data you read during render changes |
| `useSyncedState()` | state stored on the object and synced like any other component |
`defineProps` uses the same field types as script properties β number, string, boolean, colour,
select, entity, resource β so your component gets a proper inspector form for free.
## How it runs
:::info This is real React, not a simplified imitation
Every component extension from every plugin runs in one shared worker, with React 19 inside it.
Hooks, `useEffect`, context and Suspense all work exactly as you expect.
What the worker produces is not DOM but a description of it, which the editor draws with its own
components. That is what makes an extension look native rather than like an embedded page.
:::
Two consequences of that boundary:
- **The element set is a fixed list.** You build from the provided components, not arbitrary
HTML.
- **Handlers do not cross the boundary.** The editor is told only that a handler exists; a click
comes back as a message and calls your function in the worker. You write `onClick` normally β
this only matters if you expected to pass a DOM event around.
## How people add it
Your extension appears in **+ New component**, in an **Extensions** group β it is not treated as
anything more special than a built-in component.
It also appears in the toolbar under **+**, grouped under a **Plugins** heading so it is obvious
where an unfamiliar button came from. The two entry points behave differently, deliberately:
| Added from | Result |
| ----------------------- | --------------------------------------------------------- |
| **+ New component** | attaches to the object you already selected |
| **The toolbar** | creates a **new** object with your component and selects it |
A locally connected folder shows up in that list too if it has a panel β otherwise the only way
to reach it would be to open the Plugins panel every time. A folder with nothing but an overlay
does not appear: there would be nothing to press.
## Trust
:::warning A component extension runs without being opened
It mounts whenever anyone opens a project containing it, with no click involved. That is what
puts it behind the stronger consent described in
[trust and verification](/studio/plugins#trust).
:::
---
**Next:** [Keyboard shortcuts](/studio/hotkeys)
---
# Your first project
Source: https://docs.arclip.design/studio/getting-started
# Your first project
We will make a marker-based AR scene: point a phone at a picture, and a 3D object appears with
a sound when you tap it. Everything here generalises to bigger projects.
## 1. Create the project
Sign in at [arclip.design](https://arclip.design) and create a project. You land in Studio with
an empty space and one scene.
## 2. Choose what makes the scene appear
Open **Scenes** in the left rail, select your scene, and set its **trigger**:
| Trigger | Appears when |
| ------------- | -------------------------------------------------- |
| **3D** | straight away β no AR, good for testing |
| **Image** | the camera sees a picture you upload |
| **QR** | a QR code is scanned |
| **Surface** | the visitor places it on a floor or table |
| **Face** | a face is detected |
| **360** | a panoramic environment opens |
Pick **Image** and upload the picture you will print or show on another screen. Set its real
physical width β this is how the engine knows what scale your content should be.
:::tip Use a picture with lots of detail
A photograph tracks well. A flat logo on white does not: there is nothing for the camera to
lock onto. High contrast and irregular detail are what matter, not resolution.
:::
## 3. Add something to see
Open **Layers and assets** and drag a model onto the canvas β or use the **+** button in the
top bar to add a primitive.
Move, rotate and scale it with the toolbar that appears next to the selection. The grid is in
your project's units, so a one-metre cube really is a metre against the marker.
## 4. Give it a material
With the object selected, find **Material** in the right-hand panel and press **Edit**.
`Standard` is the right default: set a colour, then roughness. Roughness is the setting that
does the most work β high for cloth and stone, low for polished metal.
## 5. Make it react
Still with the object selected, add an **Events** component, then add an event:
1. **Trigger:** on click
2. **Step:** play sound β pick an audio file
3. **Step:** transformation animation β move it up 0.2 m over 400 ms
That is a complete interaction, with no code.
:::note Nothing runs while you edit
The editor draws your scene but deliberately does not execute logic β otherwise animations
would fight you while you work. Press **Preview** to try it.
:::
## 6. Preview
Preview opens your experience as a visitor sees it. Test the tap, the sound, the animation.
For a marker scene, preview on a phone and point it at the printed picture β desktop preview
cannot show you whether tracking holds at an angle, which is what usually needs adjusting.
## 7. Publish
**Publish** in the top bar turns your project into a link anyone can open. You get a preset
address, and you can connect a custom domain in project settings.
Re-publish after any change β visitors see the last published version, not your working copy.
---
## Where to go from here
| You want to⦠| Read |
| ----------------------------------- | ----------------------------------------------------------------------- |
| Know what every panel does | [Interface tour](/studio/interface) |
| Organise a bigger project | [Scenes and spaces](/studio/panels/scenes) |
| Choreograph movement | [Animation Controller](/studio/panels/animation-controller) |
| Build logic with values and conditions | [Patch Editor](/studio/panels/patch-editor) |
| Add buttons and a score display | [UI Editor](/studio/panels/ui-editor) |
| Understand how the engine behaves | [Creators Engine](/creators-engine) |
---
**Next:** [Interface tour](/studio/interface)
---
# Keyboard shortcuts
Source: https://docs.arclip.design/studio/hotkeys
# Keyboard shortcuts
Open the in-app list any time from the keyboard icon at the bottom of the left rail.
:::note Three things to know first
On macOS, `Ctrl` is `β` and `Delete` is `β«`.
Shortcuts are ignored while you are typing in a field, and while preview is open.
They act on **everything you have selected** β several objects at once is fine.
:::
## General
| Keys | Does |
| ------------------ | ------------------ |
| `Ctrl + Z` | undo |
| `Ctrl + Y` | redo |
## Objects
| Keys | Does |
| ---------------------- | ----------------------------------- |
| `Ctrl + D` | duplicate |
| `Ctrl + C` | copy |
| `Ctrl + X` | cut |
| `Ctrl + V` | paste |
| `Delete` / `Backspace` | delete |
| `β β β β` | nudge by one grid step |
## Scenes
| Keys | Does |
| ---------- | ------------------- |
| `Ctrl + J` | duplicate the scene |
## Layers
| Keys | Does |
| ------------------ | ----------------------------- |
| `Ctrl + ]` | move up one layer |
| `Ctrl + Shift + ]` | move to the top |
| `Ctrl + [` | move down one layer |
| `Ctrl + Shift + [` | move to the bottom |
| `Ctrl + Shift + H` | show / hide |
| `Ctrl + Shift + L` | lock / unlock |
## Transform
| Keys | Does |
| ----------- | ----------------------- |
| `Shift + H` | flip horizontally |
| `Shift + V` | flip vertically |
## In the Animation Controller
With bars selected, `Ctrl + D`, `Ctrl + C`, `Ctrl + V` and `Delete` act on **those bars**
rather than on objects in your scene.
## In the Patch Editor
| Keys | Does |
| ---------------------- | ------------------------- |
| `Ctrl + A` | select every node |
| `Ctrl + C` / `X` / `V` | copy / cut / paste |
| `Ctrl + D` | duplicate |
| `Delete` / `Backspace` | delete selected nodes |
| `Escape` | cancel a wire you are dragging |
---
# Figma and Sketchfab
Source: https://docs.arclip.design/studio/integrations
# Figma and Sketchfab
You can connect two outside accounts to AR Clip. Both work the same way: you give AR Clip a
personal key, and it then works with those services **as you**.
| Service | Lets you |
| ------------- | ----------------------------------------------------------------------------- |
| **Figma** | bring a frame into a scene or an interface card β sizes, colours and text as they are, without redrawing |
| **Sketchfab** | search and download models under your own account, including ones only you can reach |
## Connecting an account
Integrations live in your **account settings**, not inside a project β a key is yours, and it
works across every project you open.
:::danger AR Clip never asks for a key in a chat
If the assistant ever asks you to paste a token into a conversation, **do not**. Enter it on the
integrations page and nowhere else.
:::
Your key is stored **encrypted**, and it is never shown again β the page displays only its last
few characters so you can tell which key is connected.
### Getting a Figma token
```
Figma β Settings β Security β Personal access tokens β Generate new token
```
**File content: Read-only is enough.** There is no reason to give write access; AR Clip only
ever reads your designs.
### Getting a Sketchfab token
```
Sketchfab β Settings β Password & API β API token
```
:::note Why your own key, and not ours
Figma files are private to whoever can open them. A shared key would see a completely different
set of files from the ones you see β usually none at all. The same goes for Sketchfab models
that are not public.
:::
If the page says keys cannot be stored securely yet, the server is missing its encryption
secret β an administrator has to set it before any key can be saved.
## Bringing in a Figma design
Paste **the link to a frame**, copy it straight from Figma, or the file key. AR Clip reads the
frame and builds either of two things:
| Import as | You get |
| --------------- | ---------------------------------------------------------------------- |
| **A scene** | the layout rebuilt as objects in 3D: sizes, colours, text and images |
| **An interface card** | the layout rebuilt as a UI card, which is usually what a designed screen should be |
:::tip Pick the frame, not the file
Copy the link with the frame selected. Handing over a whole marketing page produces hundreds of
layers and a scene nobody can work with β you almost always want one section.
:::
Practical limits: around **900 layers** and **200 images** per import. Past that the import is
minutes of uploading for something that was not worth importing whole.
What travels well: frames, groups, rectangles and their corner radii, strokes, text with its
size and colour, and images. What does not: anything that only exists as a Figma effect. Treat
the import as a very good starting point rather than a pixel-exact copy.
## Bringing in a Sketchfab model
Sketchfab appears as a source in the **library** panel, under the 3D model tabs. Search it like
any other source, and importing copies the model into your project.
With your account connected you also reach **your own** models β including ones that are not
public.
:::note Check what you are allowed to use
Sketchfab models carry their own licences, and "downloadable" is not the same as "free to use
in a commercial project". The licence is on the model's page; it is worth reading before it
ends up in something you publish.
:::
---
**Next:** [Plugins](/studio/plugins)
---
# Interface tour
Source: https://docs.arclip.design/studio/interface
# Interface tour
## The top bar
Left to right β on a narrow window some of this collapses into the logo menu:
| Element | What it does |
| -------------------- | --------------------------------------------------------------------- |
| Logo | back to your projects |
| Project name | what this project is called |
| Menu (β°) | project, edit, add and view menus |
| Comments | drop a pin, show pins in the scene, show resolved ones |
| Undo / Redo | your own edit history |
| 3D / 2D | switch the canvas mode |
| Toolbar | **+** to add an object, plus your pinned tools |
| Avatars | who else is editing right now |
| **Publish** | turn the project into a link |
The **menu** groups the things you would expect: navigation back to the project, the edit
commands (copy, cut, paste, duplicate, delete), adding objects and lights, and view options β
reset the camera, switch to orthographic, toggle settings and comments, go full screen.
## The left rail
Each icon opens a panel:
| Icon | Opens |
| --------------------- | ----------------------------------------------------------------------- |
| **AI assistant** | [build a scene from a description](/studio/panels/assistant) |
| **Scenes** | [your spaces and scenes](/studio/panels/scenes) |
| **Layers and assets** | [the object tree](/studio/panels/layers) and [your files](/studio/panels/assets) |
| **Library** | [content and uploads](/studio/panels/library) *(signed-in only)* |
| **Patch Editor** | [logic as a node graph](/studio/panels/patch-editor) |
| **Animations** | [the timeline editor](/studio/panels/animation-controller) |
| **Project settings** | [analytics, languages, domain](/studio/settings/project-settings) |
| **Scene UI** | [the interface layer](/studio/panels/ui-editor) |
At the bottom: **keyboard shortcuts**, and a link to the community for help.
## The canvas
| Element | What it does |
| -------------------- | ----------------------------------------------------------------------- |
| Orientation gizmo | bottom-right axes; click a face to snap the camera to it |
| Selection toolbar | move, rotate, scale, select, hand β appears next to what you selected |
| Hover outline | shows what you are about to click |
**3D** is the normal spatial view, and you can switch the camera between perspective and
orthographic. **2D** flattens everything β useful for laying out interface-style content and
for precise alignment.
## The layers panel
The object tree for the current scene, with search and an add button.
Each row has:
- a **checkbox** β enabled or not. Disabled objects and everything under them appear dimmed;
- an **eye** β visible or not;
- **double-click** to rename;
- **drag** to re-parent and reorder.
Right-click gives you rename, duplicate and delete.
:::tip Enabled and visible are not the same thing
The **eye** only hides: scripts keep running and physics keeps colliding. The **checkbox**
takes the object out of the experience entirely. An invisible wall uses the eye; a prize that
appears later uses the checkbox.
:::
## The right-hand panel
Select an object and this is where its components live β the
**[inspector](/studio/panels/inspector)**.
Which components an object has *is* what it is: add a light component and it is a light; add a
rigid body and physics starts moving it. β [Components](/creators-engine/concepts/components)
## Panels you can move
The [Patch Editor](/studio/panels/patch-editor), the
[Animation Controller](/studio/panels/animation-controller) and the
[UI Editor](/studio/panels/ui-editor) open as panels you can resize and reposition β docked at the
bottom, floating in a window, or expanded over the whole viewport. Drag their edges to resize.
## Every panel, in detail
| Panel | For |
| --- | --- |
| [Scenes](/studio/panels/scenes) | spaces, scenes and their triggers |
| [Layers](/studio/panels/layers) | the object tree of the current scene |
| [Project assets](/studio/panels/assets) | this project's own files |
| [Library](/studio/panels/library) | uploads and ready-made content |
| [Inspector](/studio/panels/inspector) | an object's components |
| [Material editor](/studio/panels/material-editor) | how surfaces look |
| [Events editor](/studio/panels/events-editor) | interactivity without code |
| [Animation Controller](/studio/panels/animation-controller) | the timeline |
| [Patch Editor](/studio/panels/patch-editor) | logic as a graph |
| [UI Editor](/studio/panels/ui-editor) | the 2D interface |
| [AI assistant](/studio/panels/assistant) | building from a description |
| [Comments](/studio/panels/comments) | feedback pinned to the scene |
---
**Next:** [Scenes panel](/studio/panels/scenes)
---
# Animation Controller
Source: https://docs.arclip.design/studio/panels/animation-controller
# Animation Controller
The timeline editor, for choreography β several things moving together on one clock.
For a single smooth change, you do not need it: put a duration on an event step. The timeline
earns its place when timing between several objects matters.
## The model
| Level | Is |
| ------------ | ------------------------------------------------------------------------ |
| **Preset** | one named animation you can play |
| **Bar** | one object's part in it, on its own track |
| **Keyframe** | a time, a value, and the easing used to arrive at it |
A bar animates **one component of one object** β and that object does not have to be the one
owning the preset, which is what lets a single preset choreograph a whole group. When a bar
targets another object, that object's component shows a back-reference saying it is being
animated.
There is a second kind of bar: a **play bar**, which starts *another object's preset* when the
playhead reaches it. Nest presets this way and a complex animation stays in manageable pieces.
A preset can have an explicit duration, which extends the timeline past the last keyframe.
Without one, it lasts as long as its longest bar.
## The panel
| Area | Holds |
| ------ | ----------------------------------------------------------------------------- |
| Left | the object hierarchy β pick what to animate |
| Centre | the timeline of the active preset |
| Right | that object's components β click or drag one to add a bar for it |
The header has the edit commands, the playback transport, the preview toggles and a
**Scene / Entity** mode switch.
:::note The left panel does not follow your scene selection
You choose what to animate here, deliberately. Otherwise clicking around the scene would keep
moving the timeline out from under you.
:::
### Two preview toggles worth understanding
**Auto-frames** captures a keyframe at the playhead whenever you move the object in the scene.
With it on, animating is: move the playhead, pose the object, repeat. This is the fastest way
to work, and it is also how you accidentally create keyframes you did not want.
**Demo** makes scrubbing and playback actually drive the scene. On by default. Turn it off when
you want to move the playhead without disturbing objects.
## Working on the timeline
| Action | Result |
| ----------------------------- | ------------------------------------------------------------ |
| Drag a bar | moves all of its keyframes together |
| Drag a keyframe | moves it in time, bounded by its neighbours |
| Ctrl-drag an end keyframe | scales the whole bar from the opposite end |
| Scroll | zoom the time axis |
Bars can be copied, cut and pasted. Deleting the last keyframe deletes the bar.
:::note In the animation panel the edit shortcuts act on bars
With bars selected, duplicate, copy, paste and delete apply to them β not to the objects in
your scene.
:::
## Easing
Pick a curve on a keyframe and it describes how that keyframe is **arrived at**, not left.
The families (Quad, Quart, Circular, Exponent, Back) each come as In, Out and In-Out, plus
Elastic with its own amount and period, plus linear and a custom curve you shape by hand.
If you are unsure, **Out** is almost always right: things that arrive and settle look natural;
things that speed up into their destination do not.
What each curve feels like: [Animation](/creators-engine/authoring/animation).
## Playing an animation
A preset plays from an event step, with a loop mode (once, a count, or forever), a repeat
count, a speed and a delay.
**Autoplay** is not a separate flag: it is an event on scene launch that plays the preset. The
autoplay toggle in the panel creates and removes exactly that event, so you can see and edit it
like anything else.
---
**Next:** [Patch Editor](/studio/panels/patch-editor)
---
# Project assets
Source: https://docs.arclip.design/studio/panels/assets
# Project assets
The bottom half of the **Layers and assets** panel: the files belonging to this project, in
folders.
Drag anything from here onto the canvas or into the layer tree to add it to your scene.
## The buttons
| Button | Creates |
| -------------- | ------------------------------------------------------------ |
| **Add folder** | a folder, to organise |
| **Add script** | a new file β see the list below |
| You can create | For |
| ------------------------ | -------------------------------------------- |
| JavaScript or TypeScript | behaviour written in code |
| JSON or XML | data your project reads |
| **Patch graph** | behaviour built from nodes |
| **UI** | an interface card |
Clicking a script, patch or UI file opens its editor.
## Folders are real paths
A folder is not a label β it is part of the file's address:
```
Models/Trees/oak.glb
```
Scripts import each other by that path, so **renaming or moving a file rewrites every import
that pointed at it**. You never have to go and fix them by hand.
:::tip Organise as you import, not later
Getting the shape right early costs nothing. Reorganising two hundred loose files afterwards is
an afternoon.
:::
:::note Imported models bring their dependencies with them
A model that arrives with its own textures tucks them underneath itself, and they travel with
it when you move or delete it.
:::
---
**Next:** [Library](/studio/panels/library)
---
# AI assistant
Source: https://docs.arclip.design/studio/panels/assistant
# AI assistant
The top icon in the left rail. Describe a scene in ordinary words and it builds it, using the
same tools you would have used yourself.
```
Add a red cube, then spin it on click.
```
Everything it makes is an ordinary part of your project β objects, components, events. You can
open any of it afterwards and change it by hand; nothing is locked or generated into a form you
cannot edit.
## Working with it
- Be specific about **what** and **where**: "a cube on the table, half a metre across" beats
"add something".
- Ask for one change at a time. A long list is harder to correct than three short requests.
- If a result is close but wrong, say what to change rather than starting again β it can see
the scene as it now is.
- **Stop** interrupts a request that is going the wrong way.
## Usage
The panel shows what you have used over the last few minutes and the last day, and when the
allowance resets. If you hit the limit, it tells you how long to wait.
:::danger It will never ask you for a key
If anything in a conversation asks you to paste an API token or a password, that is not us.
Keys go in your account settings and nowhere else. β [Figma & Sketchfab](/studio/panels/../integrations)
:::
---
**Next:** [Comments](/studio/panels/comments)
---
# Comments
Source: https://docs.arclip.design/studio/panels/comments
# Comments
The speech-bubble menu in the top bar. Comments are pinned to a **place in the scene**, which
is usually a better way to hand over feedback than describing where something is.
| Menu item | Does |
| ------------------------- | --------------------------------------------- |
| **Place new comment** | drop a pin, then type |
| **Show on canvas** | show or hide the pins in the viewport |
| **Show resolved** | include threads already dealt with |
## A thread
Click a pin to open it. From there you can reply, **Resolve** it when it is handled, **Reopen**
one that was closed too early, and delete the thread.
:::tip Resolve rather than delete
A resolved thread disappears from view but stays readable with **Show resolved** on. Deleting
loses the reasoning, which is usually the part worth keeping.
:::
Comments are not part of your published experience β visitors never see them.
---
**Next:** [Plugins](/studio/panels/../plugins)
---
# Making things interactive
Source: https://docs.arclip.design/studio/panels/events-editor
# Making things interactive
Add an **Events** component to an object and you can give it behaviour without writing anything.
Each event is one sentence: **when this happens, do these things.**
## Building one
| Field | What it is |
| ----------- | ---------------------------------------------------------------------- |
| Name | for you β name events after what they do, not what they are on |
| Trigger | when it fires |
| State | for state triggers β which state to watch |
| Key | for key triggers β record the combination, with modifiers |
| Action id | for interface triggers β which button; empty means any |
| Steps | what happens, in order |
Steps are grouped into tabs (**Actions**, **Animations**, **States**) which is roughly "affect
the outside world", "make something move", "change how something is".
## Which objects a step affects
Every step has a **target object**. Leave it empty and the step acts on the object holding the
event; fill it in and it acts on that object instead.
This is the part worth internalising: **the event lives on one object, but its steps can act on
any object.** One event on a button can open a door, dim a light and start a sound somewhere
else entirely.
## What you can react to
Input (tap, hover, key, drag, pinch), the scene starting, the app pausing and resuming, AR
tracking being found or lost, timers, proximity, state changes, collisions, interface buttons,
and the character controller changing state.
The editor only offers triggers that make sense where you are β "marker detected" will not
appear on a plain 3D scene.
:::note One behaviour worth knowing while you author
A tap on an object also reaches its **parents**, so an event on a group catches taps on
anything inside it. That saves putting the same event on forty objects.
The rest of how triggers travel, and what every one of them does, is in
[Events](/creators-engine/authoring/events).
:::
## What you can do
The step list, grouped the way the tabs group it:
| Actions |
| ------------------------------------------------------------------------- |
| Scene transition Β· Space transition Β· Open URL Β· Open messenger |
| Call phone number Β· Compose email Β· File download Β· Contact download |
| Show / hide object Β· Follow object Β· Look at object Β· Stop following |
| Fly along path Β· Stop path flight Β· Lock pointer Β· Release pointer |
| Animations |
| ------------------------------------------------------------------------- |
| Play animation Β· Stop animation Β· Transformation Β· Opacity |
| Material Β· Bone transform Β· Built-in animation |
| States and logic |
| ------------------------------------------------------------------------- |
| Set state Β· Override properties Β· If β otherwise Β· Set variable |
| Send signal Β· Wait, then |
Full detail, with every parameter: [the step reference](/creators-engine/reference/steps).
:::tip Branching is a step, not a setting
The **if** step holds a condition and two nested lists of steps. Because it is a step, it nests
β an *if* inside an *if* gives you as many outcomes as you need.
:::
## Re-tracking
For AR scenes, each event's re-tracking mode decides what happens when the marker is lost and
found again: start over immediately, never restart, or restart once the current steps finish.
:::danger If something fires twice, this is nearly always why
Events, patches and scripts all hear the same triggers. Wiring the same trigger on the same
object in two of them runs it twice. Pick one place for each.
:::
---
**Next:** [Animation Controller](/studio/panels/animation-controller)
---
# What an object is made of
Source: https://docs.arclip.design/studio/panels/inspector
# What an object is made of
Select an object and the right-hand panel shows what it is made of. **This panel is where you
decide what an object does** β there is no object type to pick, only components to add.
## The header
| Icon | Does |
| ----------- | ------------------------------------------------------- |
| Duplicate | clone the object and everything inside it |
| Lock | stop it being edited on the canvas |
| Delete | remove it from the scene |
| Show / hide | toggle visibility |
:::tip Lock the floor early
Large background objects (a floor, a backdrop) are the things you click by accident all day.
Locking them means your marquee selections and drags stop catching them.
:::
## What you can add
**+ New component** lists everything available:
| Component | Gives the object |
| --------------- | ---------------------------------------------------------------- |
| **Transform** | position, rotation, scale β always present, cannot be removed |
| **Geometry** | a primitive shape, or an imported model |
| β 3D animation | skeletal animation from an imported model |
| β 3D scan | a Gaussian splat capture |
| **Material** | how surfaces look β [Material editor](/studio/panels/material-editor) |
| **Text** | a text label |
| **Video** | video, as a surface or texture |
| **Audio** | sound |
| **Light** | a light source |
| **Look at** | turn to face a point or an object |
| **Shadow** | whether it casts and receives shadows |
| **Script** | behaviour written in code |
| **Patch** | behaviour built as a node graph |
| **UI** | an interface card β [UI Editor](/studio/panels/ui-editor) |
| **Events** | triggers and actions β [Events editor](/studio/panels/events-editor) |
| **States** | named snapshots of this object's settings |
| **Animation** | timeline presets β [Animation Controller](/studio/panels/animation-controller) |
Some components are created for you and never appear in the menu: the object's name and
visibility, its place in the hierarchy, the skeleton and blend shapes of an imported model, a
scene's trigger, and a scene's patch graph.
## Transform
| Field | Notes |
| ---------- | -------------------------------------------------------------- |
| Position | in your project's units |
| Rotation | in degrees |
| Scale | a multiplier; axes can be linked so they scale together |
| Lock | stops canvas editing, same as the header icon |
:::note Look-at takes over rotation
Add a **Look at** component and the rotation fields go inactive β that component owns the
rotation now. Remove it to get manual control back.
:::
## Geometry
A primitive (box, sphere, cylinder, capsule, cone, plane and the rest) or a reference to a
model you imported. Each shape has its own dimensions and segment counts.
Segment counts are worth understanding: they control how smooth a curved shape looks and how
much it costs to draw. A sphere at 32 segments looks round; at 8 it is a faceted ball; at 128
it is round *and* expensive for no visible gain.
## Text, video, audio
**Text** has its own font, size, alignment and colour. Size is in pixels, where 1000 px is one
metre in the world.
**Video** plays onto the object. It starts muted, because browsers refuse to autoplay video
with sound.
**Audio** plays a sound, positioned in space by default so it gets louder as you approach.
:::warning Audio is a single voice
Playing it again cuts off the first one, so it is wrong for footsteps or gunfire. Those belong
in a script, where each call starts its own sound.
:::
## Light and shadow
Five light types, each with a colour and intensity. **You aim a light by rotating the object** β
there is no target field to fill in.
**Shadow** decides whether an object casts and receives them. Shadows are the expensive part of
lighting, so switch casting on for the one or two lights that matter rather than all of them.
Which type suits what: [Lights](/creators-engine/concepts/lights).
## States
A **state** is a named snapshot of this object's settings β position, colour, visibility,
anything. Add states here, then switch between them from an event.
This is how you build a switch, a highlight, or a door that is open or closed, without
animating anything by hand.
:::warning While a state is selected, your edits go into it
Change the object with a state active and you are editing **that state's snapshot**, not the
object. This is exactly right when building states and thoroughly confusing when you did not
realise one was selected.
:::
## Script and patch
Both point at a file in your project assets. A script is code; a patch is a node graph. Either
can expose its own settings in this panel, which is what makes one script reusable across many
objects with different values.
---
**Next:** [Material editor](/studio/panels/material-editor)
---
# Layers panel
Source: https://docs.arclip.design/studio/panels/layers
# Layers panel
The top half of the **Layers and assets** panel: everything in the current scene, as a tree.
## A row
| Control | Does |
| ---------------- | --------------------------------------------------------------------- |
| **Checkbox** | switch the object on or off |
| **Eye** | show or hide it |
| **Double-click** | rename |
| **Drag** | move it into another object, or reorder |
| **Right-click** | rename, duplicate, delete |
Switched-off objects and everything inside them appear dimmed, so you can see at a glance what
is not running.
:::tip The checkbox and the eye are not the same thing
The **eye** only stops it being drawn: its scripts keep running and it still collides. The
**checkbox** takes it out of the experience entirely.
An invisible wall the player still bumps into uses the eye. A prize that appears later uses the
checkbox.
:::
## Parenting by dragging
Drop one object onto another and it goes inside it. From then on it **moves, rotates and scales
with its parent** β which is how anything with parts is built. A car body with four wheels
dropped into it moves as one car.
:::warning Deleting an object deletes everything inside it
And their children too. Drag a child out first if you want to keep it.
:::
The editor refuses to put an object inside its own child, rather than tangling the tree.
## Finding things
The search box filters the tree. Objects are found by their **name**, which is also what a
script uses to look one up β so naming things properly pays off twice.
:::tip Lock the things you keep clicking by accident
Large background objects (a floor, a backdrop) are what marquee selections and stray drags
catch all day. Lock them in the [inspector](/studio/panels/inspector) header and they stop getting in
the way.
:::
---
**Next:** [Project assets](/studio/panels/assets)
---
# Library
Source: https://docs.arclip.design/studio/panels/library
# Library
Content you can bring into any project. You need to be signed in.
## Getting files in
Press **Add assets**, or drag files straight onto the panel or the canvas.
| Category | Accepted |
| ------------------ | ------------------------------------------------- |
| Images | `.png` `.jpg` `.jpeg` `.webp` `.gif` `.svg` |
| Video | `.mp4` `.webm` `.mov` |
| Audio | `.mp3` `.wav` `.ogg` `.aac` |
| Fonts | `.ttf` `.otf` `.woff` `.woff2` |
| 3D models | `.glb` `.gltf` `.fbx` `.obj` |
| 3D scans | `.ply` `.sog` `.spz` `.splat` `.ksplat` `.rad` |
| HDRI / environment | `.hdr` `.exr` |
Anything else is refused at upload.
:::tip Prefer `.glb`
It packs the model, its materials and its textures into one file, so nothing arrives missing.
If your tool exports `.fbx` or `.obj`, converting to `.glb` first avoids most import surprises.
:::
## Where content comes from
Tabs narrow by type (images, video, audio, 3D models, 3D scans) and within a tab you pick a
source:
| Source | What it is |
| ----------------- | --------------------------------------------------------------- |
| **My assets** | what you have uploaded |
| **Team assets** | your team's shared library |
| **Creators hub** | the platform catalogue β importing copies it into your team |
| **Sketchfab** | external 3D model search β [Figma & Sketchfab](/studio/panels/../integrations) |
Mark things with a **heart** to find them again. Some items are marked **Premium** and need a
paid plan. You can publish your own work to the catalogue.
:::note Check the licence on anything from outside
Sketchfab models carry their own terms, and "downloadable" is not the same as "free to use
commercially". The licence is on the model's page.
:::
---
**Next:** [Inspector](/studio/panels/inspector)
---
# Making things look right
Source: https://docs.arclip.design/studio/panels/material-editor
# Making things look right
## Slots
A material is a **list of slots**, not one material. Slot order follows the submeshes of an
imported model β slot 1 paints the first group, slot 2 the second. That is how one chair can be
wood and fabric.
| Action | Does |
| ----------------------- | --------------------------------------------------------- |
| **+ Add material** | adds a standard slot |
| **Edit** | open this slot in the editor |
| **Replace from library**| pick a saved material |
| **Duplicate** | copy the slot |
| **Delete** | remove it β unavailable when only one is left |
Drag to reorder. There is always at least one slot.
## Choosing a type
| Type | Use it for |
| ----------- | ------------------------------------------------------------------ |
| **Standard**| the default β realistic surfaces via metalness and roughness |
| **Physical**| standard plus clearcoat, transmission, thickness and refraction |
| **Basic** | flat colour that ignores lighting |
| **Toon** | stylised banded shading |
| **Normal** | surface-normal colouring, for debugging |
| **Chromakey**| video with a colour keyed out β a green screen |
| **Occluder**| invisible, but hides whatever is behind it |
The panel only shows fields the chosen type actually has, so switching type changes what you
see.
## The fields
| Field | Notes |
| ----------- | -------------------------------------------------------------------- |
| Name | so you can tell slots apart |
| Side | front, back or double β which faces are drawn |
| Colour | the base colour, with a swatch for its texture |
| Alpha | switches transparency on |
| Opacity | 0β100%, with a swatch for an opacity texture |
| Roughness | 0β100% β 0 is mirror-smooth, 100 is completely matte |
| Metalness | 0β100% β realistically this is 0 or 100, rarely between |
| Emissive | colour and intensity of light the surface gives off by itself |
:::tip Roughness does most of the work
If a surface looks wrong, adjust roughness before anything else. Metalness is close to binary
in reality: a thing either is bare metal or it is not, and values in between mostly look like
mistakes.
:::
**Chroma key** has its own block: the source video, the colour to remove, and how aggressively β
similarity, smoothness and spill, all as percentages.
**Preview** shows the material on a test object, which is much easier to judge than a small
swatch.
## Textures
The swatch next to a field opens the texture picker: choose an image, a texture or a video, then
tune how it sits on the surface.
| Setting | What it controls |
| -------------------------- | --------------------------------------------------------- |
| Repeat, offset, centre | how the image tiles and where it sits |
| Rotation | β180Β° to 180Β° |
| Wrap | repeat, clamp or mirror at the edges |
| Magnify / minify filter | how it is sampled up close and far away |
| Anisotropy | 1β16 β sharpness at grazing angles |
| Mipmaps | generate smaller versions for distance |
| Colour space | how the image's values are interpreted |
| UV channel | which set of coordinates to use, 0β3 |
| Flip Y, premultiplied alpha| orientation and transparency handling |
:::warning Colour space is the setting that quietly ruins materials
A map carrying numbers rather than colour (roughness, a mask, a height map) has to be marked as
data, or it is colour-corrected and the values come out wrong.
β [Materials](/creators-engine/concepts/materials)
:::
Two behaviours to expect: a ready-made texture resource has read-only settings, and if the
object has a **Video** component, that video takes over the colour map and the picker is locked.
## The material library
**Replace from library** opens the saved materials:
| Tab | Holds |
| ------------- | ---------------------------------------------------- |
| Project | materials saved in this project |
| Team | your team's shared materials |
| Creators hub | platform templates |
:::note Applying copies, it does not link
Picking a library material copies its values into your slot. Editing it afterwards changes only
this object β the library material is untouched, and so are other objects using it.
If you want one material shared across many objects, use a **material reference** instead, so
editing it once updates everything.
:::
---
**Next:** [Events editor](/studio/panels/events-editor)
---
# Patch Editor
Source: https://docs.arclip.design/studio/panels/patch-editor
# Patch Editor
Build logic by connecting nodes. The graph becomes a real script and runs exactly as fast as
one β you can read the generated code at any time.
What the nodes actually do is in
[Patches](/creators-engine/authoring/patches); this page is about using the editor.
## What you are editing
| You opened | You are editing |
| ----------------------------- | -------------------------------------------------------- |
| the rail button | the **scene's** graph |
| a `.patch` file in your assets | a **reusable** patch, saved back with **Save** |
Either way your edits go through the normal editing path, so undo and live collaboration work
without anything extra.
## The toolbar
| Button | Does |
| ------------------- | ----------------------------------------------------------------- |
| **+ Add node** | the node menu, by category |
| **Load example** | ready-made graphs to start from or learn from |
| **Node from object** | a reference node for whatever is selected in the scene |
| **Code** | the generated script, read-only, with a copy button |
| **Save patch** | save the graph as a reusable file |
:::tip The Code button is the fastest way to check a graph
It shows the exact script your graph became. If something is not behaving, reading that is
usually quicker than tracing wires β and it is how you confirm the graph does what you think.
:::
## Working with the graph
| Action | How |
| ------------------- | ------------------------------------------------------------------ |
| Add a node | right-click empty canvas (it appears under the cursor), or **+ Add node** |
| Node menu | right-click a node β copy, cut, duplicate, paste, delete |
| Select | click; drag a box; `Shift` to add to the selection |
| Select all | `Ctrl + A` |
| Delete | `Delete` or `Backspace` |
| Copy / cut / paste | `Ctrl + C` / `X` / `V` |
| Duplicate | `Ctrl + D` |
| Zoom | scroll |
| Pan | middle mouse, or drag the canvas |
| Collapse a node | the chevron in its header hides unused ports |
## Wiring
Drag from a port and compatible ports light up while incompatible ones dim.
:::tip Drop a wire on empty canvas
You get a menu of nodes that can accept it, and picking one creates the node **already
connected**. It is much faster than adding a node and then finding its port.
:::
Grab an existing wire near its end to re-route or remove it; grab an occupied input to detach
it. `Escape` or right-click cancels a drag in progress.
## Port types
Each type has its own colour:
| Type | Colour | Carries |
| --------- | ---------- | ---------------------------------- |
| `pulse` | light blue | the flow of execution |
| `number` | blue | a number |
| `boolean` | orange | true or false |
| `string` | green | text |
| `vector` | purple | an x, y, z triple |
| `color` | pink | a colour |
| `object` | dark blue | a reference to an object |
`pulse`, `object` and `vector` only connect to their own type. The scalars β number, boolean,
string, colour β convert between each other freely.
**Vector ports unfold** with a chevron into separate `x`, `y` and `z` sockets: on an input that
assembles a vector from parts, on an output it takes one apart.
**An `object` port with no wire** gives you a dropdown of the scene's objects. Leave it empty
and it means the object the graph is attached to.
**Constant nodes** and the id fields on variable, message and global nodes are typed directly
into the node header.
## Panel size
The editor opens docked at the bottom. The resize menu in its header switches it to **expanded**
over the whole viewport, back to **docked**, or to a floating **window**. Drag its edges to
resize.
---
**Next:** [UI Editor](/studio/panels/ui-editor)
---
# Scenes panel
Source: https://docs.arclip.design/studio/panels/scenes
# Scenes panel
Opened from the left rail. This is the shape of your project.
```
Project
βββ Space β everything inside shares its background, lighting and units
βββ Scene β appears when its trigger fires
```
## The list
| You see | It means |
| -------------------------- | --------------------------------------------------- |
| A stripe down the left | this is the main space, or the main scene |
| A line under a scene name | which trigger it uses |
| A badge | this scene has problems worth looking at |
**+** at the top adds a space; **+** in a space's header adds a scene to it.
Right-click either for rename, make main, duplicate, reorder and delete.
:::tip When to add a space rather than a scene
Scenes in one space share a background, lighting and units, and switching between them is
instant. Use separate **scenes** for the steps of one experience, and a separate **space** when
you need genuinely different surroundings β a different world, not a different moment.
:::
## Setting a scene's trigger
Select a scene and the trigger dropdown decides when it appears.
| Trigger | The scene appears when⦠|
| ------------ | ------------------------------------------------------ |
| **3D** | straight away β no AR. Good for building and testing |
| **Image** | the camera recognises a picture you upload |
| **QR** | a QR code is scanned |
| **Surface** | the visitor places it on a floor, wall, table |
| **Face** | a face is detected |
| **360** | a panoramic environment opens |
| **VPS** | the visitor is located against a scanned real place |
### Image
Upload the picture, then set its **real printed size**.
:::warning Physical size decides the scale of everything
If you say the marker is 20 cm wide and it is really 40, your content arrives at half the size
it should be. Measure the printed thing.
:::
:::tip What tracks well
Photographs and busy, irregular artwork. What does not: a logo on white, large flat areas,
repeating patterns, anything glossy. Detail and contrast matter far more than resolution.
:::
### QR
The text the code carries, and its printed size.
### Surface
| Setting | Choices |
| --------------------- | -------------------------------------------------------- |
| Binding type | floor Β· wall Β· ceiling Β· table |
| Recognition algorithm | find a surface automatically, or place where they tap |
### Face
Pick the **anchor point** β nose, forehead, chin, eyes, eyebrows, ears, nose base, lower lip β
and the physical size used for scale.
Switch on **face mesh** and you can apply a texture that follows the face, with options for
filling the eyes and mouth and mirroring the texture.
### VPS
Choose which of your team's scanned maps the scene should locate against.
Getting a map, and handling the wait while a visitor is located:
β [Placing content in a real place](/studio/panels/../vps-scenes)
### Hints
A toggle available for image, QR, face, surface and 360 scenes: the on-screen prompts telling a
visitor what to point at or where to tap.
## Scene scripts
Each scene has a **Scripts** section for behaviour not attached to any single object. A scene
script starts and stops with its scene.
For behaviour that outlives scene changes, use the space's scripts instead β
[Space settings](/studio/panels/../settings/space-settings).
## Several scenes on one trigger
Scenes sharing a trigger (the same image, the same QR code) form a group, and **only one
shows at a time**. That is how one printed poster opens chapter one today and chapter two after
the visitor has progressed.
How that behaves at runtime: [Objects and scenes](/creators-engine/concepts/objects-and-scenes).
---
**Next:** [Layers panel](/studio/panels/layers)
---
# UI Editor
Source: https://docs.arclip.design/studio/panels/ui-editor
# UI Editor
All 2D interface in AR Clip (HUDs, menus, buttons, labels) is a **card**, laid out like a
normal interface in front of the 3D view. That is what keeps text crisp at any distance and on
any screen.
How cards behave at runtime is in
[UI cards](/creators-engine/authoring/ui-cards); this page is about building one.
## Opening it
Four ways in, all leading to the same full-screen editor:
- the **Scene UI** button in the left rail β opens the active scene's interface, creating it if
there is none;
- **scene settings**, in the UI section;
- an object's **UI component**, via *Edit layout*;
- a UI file in your **project assets**.
The editor is collaborative, and has **Apply** and **Close**.
:::note Every new scene comes with an empty UI already attached
You do not have to create one; just open it and start.
:::
## Attaching a card to something
A card is a file, and a **UI component** attaches it to an object:
| Setting | Options |
| ---------------------- | ---------------------------------------------------------------- |
| UI resource | which card |
| Mode | **Overlay** (full screen) or **Anchored** (pinned to an object) |
| Anchor X / Y | which corner or edge sits on the object β anchored only |
| Enabled | show it or not |
| Keep variables | values survive moving between scenes |
A card on a **scene** is always an overlay. A card on an **object** can be either.
:::warning An anchored card does not shrink with distance
It stays the size you designed and hides when its object goes behind the camera. If you want
something that scales with distance, use 3D text instead.
:::
## Connecting UI to your scene
Three ways, in increasing order of power.
**Built-in actions** β the card changes itself: switch screens, set a variable, run a timer.
Anything purely about the interface should stay here.
**Scene steps** β the engine's steps are offered directly in the editor as actions with proper
parameters: go to a scene, open a URL, play an animation. This is how a button drives your
scene without any code.
**Named actions plus logic** β give an action an id, then react to it. An object's **Events**
component has a UI-action trigger (leave the id empty to catch any action), and a script can
listen directly:
```ts
const ui = ctx.getDivKit(entity);
ui.set('score', (v) => v + 1);
ui.subscribe('lives', (v) => {});
ui.onAction('restart', () => {});
```
:::tip Keep game state in your logic, and let the card display it
The reverse, treating card variables as where the truth lives, falls apart the moment you
need that value somewhere else, because card variables are not shared between participants and
reset on reload.
:::
---
**Next:** [Space settings](/studio/panels/../settings/space-settings)
---
# Writing a plugin
Source: https://docs.arclip.design/studio/plugin-development
# Writing a plugin
A plugin is **a folder on your disk**. You edit it in your own IDE, it runs in the editor with
no build step, and you publish it when it is ready.
## Getting started
In the **Plugins** panel, press **Create**. Studio asks for a folder and writes a template into
it:
```
my-plugin/
meta.json the manifest
main.html your panel's markup
main.js your code β .ts, .tsx and .jsx work too
plugin.d.ts generated types for the whole API
tsconfig.json so your IDE resolves those types
.wasignore what not to upload when publishing
```
Edit the files, press **Reload from disk**, and your changes are live. No bundler, no install
step.
:::note `.ts`, `.tsx` and `.jsx` are compiled as they are read
You can write TypeScript and JSX directly. Types come from the generated `plugin.d.ts`, so your
editor autocompletes the whole API.
:::
## The manifest
```json
{
"id": "acme.shape-spawner",
"name": "Shape Spawner",
"version": "1.0.0",
"entry": "main.tsx",
"permissions": ["scene:read", "scene:write"],
"panels": [{ "id": "main", "title": "Shapes", "entry": "main.tsx" }],
"icon": "data:image/webp;base64,β¦"
}
```
The icon is a data URL rather than a file path, so a local folder and a published plugin look
identical everywhere the plugin appears β the toolbar, the panel header, the library card. The
publish form will generate it for you from any image.
## Your panel
The simplest panel is an HTML file and a script:
```html
## Sign in
1. Sign in with the credentials you received on **arclip.design**.
2. Pick your **organization** and **team**.
## Create a new map
The home screen lists every map along with its status, title, ID, and creation time.
Tap the **plus** action at the bottom to start a new map. Grant camera access β a dialog displays the current **GPS** accuracy.
> GPS precision is **optional**. If the accuracy is worse than 50 m, tap βContinue without GPS.β Waiting briefly or walking around can improve the reading.
Choose one of two capture modes:
- **Real-time mode** β frames are captured automatically every second.
- **Manual mode** β you trigger each capture manually.
Move the device slowly while capturing. You will see **feature points**βtheir density gives a fast indication of scene texture.
When you are done, tap **Finish** to upload the capture to the server.
## Test localization and reconstruction quality
Open the context menu on any map.
Review reconstruction density by choosing **View Map** β **PLY**.
To validate localization, tap **Localization Test** and aim the camera at the captured area.
Adjust point cloud opacity and inspect logs as needed.
---
# Support
Source: https://docs.arclip.design/vps/support
# Need help with AR Clip VPS?
Our team is ready to assist you with anything from onboarding to advanced integrations.
## Reach us directly
- **Discord community** β [Join the chat](https://discord.gg/sabCsp6tvR) to get fast answers from the AR Clip crew and other creators.
- **Email** β write to [support@arclip.design](mailto:support@arclip.design) for in-depth requests or to open a support ticket.
> Tip: share your project name, SDK version, and a short description of the issue so we can help you faster.
---
# Unity SDK
Source: https://docs.arclip.design/vps/Unity SDK/introduction
## Overview
The AR Clip VPS Unity SDK lets you localize users within configured VPS maps and place content with centimeter-level accuracy on ARKit (iOS) and ARCore (Android) devices. This page mirrors the structure of the Immersal docsβscan the prerequisites, install the package, wire the scene, and start localization.
## Prerequisites
- Unity 2022.3 LTS or newer
- Test device with ARKit or ARCore support
- [Git LFS](https://git-lfs.com) if you plan to clone the sample project (large assets)
## Install Options
### Clone the sample project
```bash
git clone https://github.com/WebAR-Studio/was-vps-unity.git
```
Open the project in Unity 2022.3+. Scenes under `Assets/Scenes` contain reference setups.
### Add via Package Manager
1. Go to **Window β Package Manager**.
2. Select the **+** button β **Add package from Git URLβ¦**.
3. Paste `https://github.com/WebAR-Studio/was-vps-unity.git?path=/Assets`.
Unity downloads the SDK into `Packages/was-vps-unity`.
## Project Setup
1. Create or open an AR Foundationβready scene (contains **AR Session** and **AR Session Origin**).
2. Add `VPSLocalisationService` to an empty GameObject; this component drives the localization loop.
3. Assign required references in the inspector:
- `AR Session`
- `AR Session Origin`
- Optional AR camera and UI hooks (follow the sample scenes).
4. Fill in your VPS **API Key** and at least one **Location ID**.
> Need an API key? Grab one at [arclip.design](https://arclip.design/) or email [support@arclip.design](mailto:support@arclip.design) / [support@arclip.design](mailto:support@arclip.design).
5. (Optional) Enable **Save Images Locally** or **Save Logs In File** while you debug.
## Start Localization
The SDK exposes a concise API so you can control localization from your scripts. Attach the component below and assign the `VPSLocalisationService` reference in the inspector.
```csharp
using UnityEngine;
using WASVPS;
public class VPSBootstrap : MonoBehaviour
{
[SerializeField] private VPSLocalisationService vpsService;
private void Start()
{
vpsService.OnPositionUpdated += HandlePositionUpdated;
vpsService.OnErrorHappend += HandleError;
var settings = new SettingsWASVPS(
new[] { "your-location-id" },
failsCountToReset: 5
);
settings.ApiKey = "your-api-key";
settings.LocalizationTimeout = 2.0f;
vpsService.StartVPS(settings);
}
private void HandlePositionUpdated(LocationState state)
{
Debug.Log($"Localized at {state.Localisation.VpsPosition}");
}
private void HandleError(ErrorInfo error)
{
Debug.LogError(error.LogDescription());
}
}
```
## Inspector Reference
| Property | Purpose | Default |
| --- | --- | --- |
| **Start On Awake** | Automatically start localization when the scene loads | `false` |
| **Force Mock In Editor** | Always use the mock provider while running in the Editor | `true` |
| **Send GPS** | Attach device GPS data to each VPS request | `false` |
| **Fails Count To Reset** | Number of consecutive failures before the session resets | `5` |
| **Save Images Locally** | Persist captured frames for debugging | `false` |
## Debugging & Testing
- Enable `Force Mock In Editor` and use the supplied mock textures to iterate without a connected device.
- Call `VPSLogger.SetLogLevel(LogLevel.VERBOSE)` to review HTTP requests, responses, and error payloads in the console.
- If localization never succeeds, confirm that your API key is valid and that each location ID matches an available VPS map.
## Next Steps
- Explore `Assets/Scenes/TestScene.unity` and `Assets/Scripts/ExampleVPS.cs` in the sample project for reference implementations.
- Combine VPS poses with your own logic for spawning anchors, aligning 3D content, or driving UI updates.
---
# Platform Support
Source: https://docs.arclip.design/vps/Unity SDK/platforms
## At a Glance
The Unity SDK ships with baseline support for ARKit- and ARCore-enabled phones out of the box. Enterprise customers can unlock additional headset builds that bundle device-specific rendering pipelines, localization hooks, and deployment templates.
| Platform | Runtime | Device family | Availability |
| --- | --- | --- | --- |
| Android & iOS | ARCore / ARKit | Smartphones & tablets | Included with standard SDK |
| Magic Leap 2 | Lumin OS | Spatial computing headset | Enterprise add-on |
| HoloLens 2 | Windows Holographic | Mixed reality headset | Enterprise add-on |
| XREAL | Nebula / Android | Optical AR glasses | Enterprise add-on |
| Rokid | Android | Optical AR glasses | Enterprise add-on |
| Pico | Wave OS | VR/AR headset (Enterprise) | Enterprise add-on |
| Apple Vision Pro | visionOS | Spatial computing headset | Enterprise add-on |
| WeChat Mini App | iOS / Android bridge | AR experience launcher | Enterprise add-on |
> **Enterprise only:** Dedicated headset distributions, sample scenes, and deployment pipelines are provided exclusively on the Enterprise plan. Contact your AR Clip account manager to activate access.
## Requesting Enterprise Headset Builds
1. Collect your organization details (company name, contact person, target launch date, expected monthly active users).
2. Email `support@arclip.design` or reach out via the customer portal.
3. Our solutions team will provision a private package feed and instructions for the headsets you require.
4. Once the entitlement is enabled, the headset-specific pages in this section walk you through installing plugins, configuring build targets, and validating localization.
## How the Specialized Builds Differ
- **Device profiles:** Preconfigured camera matrices, depth overrides, and localization heuristics tailored to each headset.
- **Deployment templates:** Gradle/Xcode/Lumin/visionOS projects and CI examples ready for side-loading or store submission.
- **Sample experiences:** Scene templates that demonstrate controller input, gesture interactions, and optimized UI for each device.
- **Support SLAs:** Fast-track ticket routing with direct access to the AR Clip solutions engineering team.
Use the navigation on the left to jump to the headset you plan to support and follow the step-by-step guidance.
---
# Android & iOS
Source: https://docs.arclip.design/vps/Unity SDK/platforms/android-ios
Mobile builds are part of the core Unity SDK and run on any ARCore- or ARKit-capable handset. Enterprise customers can layer on additional servicesβmanaged app delivery, premium telemetry, and advanced localization diagnostics.
## Standard Support
- Android (API 24+) with ARCore.
- iOS 12+ with ARKit.
- Unity 2022.3 LTS with URP (optional but recommended for performance tuning).
Follow the [Unity SDK introduction](../introduction.md) to configure your scene, assign map IDs, and build for mobile targets.
## Enterprise Enhancements
> **Enterprise only:** The items below are unlocked when your workspace is upgraded to Enterprise.
- **Managed distribution:** Private Play Store / App Store Connect workflows and MDM profiles for corporate fleets.
- **Advanced telemetry:** Real-time localization metrics streamed to the AR Clip observability stack.
- **Seat licensing:** SSO integration (Azure AD, Okta) and device attestation for secure field deployments.
- **Regional edge endpoints:** Dedicated VPS ingress points per geography to minimize request latency.
To enable these add-ons, contact `support@arclip.design` with your bundle identifiers and release plan.
## Testing Checklist
- Validate localization indoors and outdoors on representative hardware (flagship + mid-range devices).
- Confirm fallbacks for camera or motion sensor denial.
- Monitor the optional Enterprise telemetry dashboard to ensure event volume matches expectations.
Enterprise accounts can escalate mobile build issues via the same priority support channel used for headset distributions.
---
# Apple Vision Pro
Source: https://docs.arclip.design/vps/Unity SDK/platforms/apple-vision-pro
The Apple Vision Pro build adapts the Unity SDK for visionOS, including hands-free interaction and passthrough rendering. It is distributed only to Enterprise customers.
## Enterprise Access
- Contact `support@arclip.design` with your Apple Developer Team ID, enterprise bundle identifiers, and TestFlight rollout plan.
- Provisioning delivers:
- `com.webarvps.vps.visionos` Unity package.
- A sample visionOS Unity project configured with PolySpatial rendering.
- CI workflows for creating signed `visionOS` `.ipa` bundles.
> **Enterprise only:** The PolySpatial shaders, spatial audio cues, and localization gesture bindings are not part of the publicly available SDK.
## Prerequisites
- Apple Vision Pro running visionOS 1.2 or later.
- Unity 2022.3 LTS with the `visionOS` build support module.
- Xcode 15.2+ with visionOS SDK installed.
- Access to PolySpatial (Unity Pro or Industry license).
## Setup Steps
1. Install the Enterprise registry and add the `com.webarvps.vps.visionos` package.
2. Import the `VisionPro_Localization.unity` scene and ensure the PolySpatial settings use the **Hybrid Rendering** mode.
3. Configure `VPSLocalisationService` to use the `VisionProPoseAdapter` (provided with the package).
4. Update `Project Settings β Player β visionOS` with your team ID and `Info.plist` capabilities (camera, head tracking, spatial audio).
5. Build the visionOS `.ipa` via Xcode and deploy through TestFlight or the Devices window.
## Testing Checklist
- Validate hand gestures for toggling localization HUD elements.
- Test passthrough opacity adjustments so overlaid content remains readable in varying lighting.
- Ensure localization continues to work when switching between Shared Space and Full Space experiences.
- Use the `VisionProDiagnostics` panel to export localization logs for enterprise support.
## Support
Enterprise support includes joint debugging labs with AR Clip engineers, best practices for PolySpatial performance, and guidance on App Store vs. direct distribution.
---
# HoloLens 2
Source: https://docs.arclip.design/vps/Unity SDK/platforms/hololens-2
The HoloLens 2 integration provides spatial mapping, hand-tracking UI, and Azure Spatial Anchor fallbacks tuned for Windows Holographic. Distribution is restricted to Enterprise plans.
## Enterprise Access
- Email `support@arclip.design` with your tenant domain, device count, and deployment scenario (sideloading vs. Microsoft Store for Business).
- You will receive:
- Access to the `com.webarvps.vps.hololens2` UPM module.
- A preconfigured Unity project using the Mixed Reality Feature Tool and OpenXR backend.
- PowerShell scripts for automated device provisioning and certificate installation.
> **Enterprise only:** The HoloLens renderer, MRTK profiles, and depth sharing bridge components are not part of the publicly available Unity SDK.
## Prerequisites
- Microsoft HoloLens 2 with Windows Holographic 23H1+.
- Unity 2022.3 LTS with Universal Render Pipeline and OpenXR Plugin.
- Mixed Reality Feature Tool 1.7+ for importing MRTK3.
- Visual Studio 2022 with UWP workload for packaging.
## Setup Steps
1. Add the AR Clip Enterprise scoped registry and install `com.webarvps.vps.hololens2`.
2. Import the sample `MRTK3_HoloLens.unity` scene and confirm the profile `WAS/MRTK/Profiles/HoloLens3` is active.
3. In `Project Settings β XR Plug-in Management`, enable `OpenXR` for UWP and select the **Microsoft HoloLens** feature group.
4. Update the `VPSLocalisationService` inspector:
- Enable `Send GPS` if you combine indoors/outdoors navigation.
- Assign `HandTrackingInput` references for toggling localization overlays.
5. Build the UWP package using **Build Settings β Universal Windows Platform**, then deploy via the Device Portal or Visual Studio.
## Testing Checklist
- Run the `Diagnostics` slate to view localization confidence, drift, and reprojected anchors.
- Validate hand ray shortcuts: pinch to reset localization, open palm to show debug menu.
- Confirm spatial mesh occlusion works with your URP pipeline (depth buffer sharing must be enabled).
- Test multi-map switching if you preload indoor/outdoor experiences.
## Support
Enterprise support includes compatibility testing on Insider Preview builds, assistance with Azure AD device management, and guidance on distributing line-of-business packages securely.
---
# Magic Leap 2
Source: https://docs.arclip.design/vps/Unity SDK/platforms/magic-leap-2
The Magic Leap 2 build delivers an optimized rendering pipeline, controller bindings, and localization heuristics for Lumin OS. It is available exclusively to Enterprise customers.
## Enterprise Access
- Request activation via `support@arclip.design` with your Magic Leap organization ID and intended deployment timeline.
- After approval, you receive:
- A private UPM registry URL that exposes the `com.webarvps.vps.ml2` package.
- Side-loadable `.zip` archives that contain preconfigured Lumin projects and sample scenes.
- A deployment checklist covering certificate management, spatial permission prompts, and controller gestures.
> **Enterprise only:** The Magic Leap 2 adapters, shaders, and map streaming plugins are not bundled with the public SDK.
## Prerequisites
- Magic Leap 2 (Lumin OS 1.4.0 or newer).
- Magic Leap Hub with the `ml2` Unity extensions.
- Unity 2022.3 LTS + Universal Render Pipeline (URP) profile.
- AR Clip VPS API key with maps published for indoor use. Acquire keys via [arclip.design](https://arclip.design/) or email [support@arclip.design](mailto:support@arclip.design) / [support@arclip.design](mailto:support@arclip.design).
## Setup Steps
1. **Add the Enterprise package feed** to your Unity project's `Packages/manifest.json`:
```json
{
"scopedRegistries": [
{
"name": "AR Clip Enterprise",
"url": "https://packages.web-ar.studio/enterprise-registry",
"scopes": [
"com.webarvps.vps",
"com.webarvps.vps.ml2"
]
}
]
}
```
2. **Install the Magic Leap 2 support package** via Package Manager.
3. **Switch platform** to Lumin (`File β Build Settings β Lumin`) and import the example scene `Assets/WebARStudio/MagicLeap2/Scenes/SpatialLocalization.unity`.
4. **Update localization settings** on the `VPSLocalisationService` component:
- Enable `Fails Count To Reset` = `3`.
- Set `Localization Timeout` = `1.5`.
- Assign your Magic Leap specific map IDs.
5. **Build & deploy** using Magic Leap Hub. During the first launch, grant camera, depth, and meshing permissions.
## Testing Checklist
- Validate VPS acquisition indoors (recommended minimum 120 lux).
- Confirm controller trigger toggles the localization HUD overlay.
- Use the included `PoseDiagnostics` prefab to monitor drift and re-localization events.
- Run the sample `Spatial Anchors` scene if you rely on meshing data.
## Support
Enterprise accounts gain a dedicated Slack channel with AR Clip solutions engineers for troubleshooting Lumin OS patches, firmware regressions, or custom viewer requirements.
---
# Pico
Source: https://docs.arclip.design/vps/Unity SDK/platforms/pico
The Pico integration focuses on business devices such as Pico 4 Enterprise and Pico Neo 3 Pro. It combines OpenXR controllers, kiosk mode helpers, and VPS rendering shaders. Access is restricted to Enterprise subscribers.
## Enterprise Access
- Submit your Pico device SKU, OS version, and intended deployment channel when contacting `support@arclip.design`.
- Approved customers receive:
- `com.webarvps.vps.pico` package with Pico OpenXR feature toggles.
- Sample launcher for kiosk deployments and managed distribution.
- Batch scripts for installing APKs via Pico Business Suite.
> **Enterprise only:** Pico-specific render paths, input bindings, and localization diagnostics are not provided in the public SDK.
## Prerequisites
- Pico 4 Enterprise or Pico Neo 3 Pro with latest firmware.
- Pico Business Suite or Developer Mode enabled.
- Unity 2022.3 LTS with Android build support and OpenXR Plugin.
## Setup Steps
1. Add the Enterprise registry and install the Pico support package.
2. Switch to Android build target, enable `OpenXR`, and activate **Pico XR Support** plus **AR Clip Pico Feature Group** in Project Settings.
3. Import the `Pico_Localization.unity` scene and register your map IDs on `VPSLocalisationService`.
4. Configure controller bindings using `Assets/WebARStudio/Pico/Input/PicoControllerBindings.asset`.
5. Build the APK and deploy via Pico Business Suite or `adb`.
## Testing Checklist
- Ensure Guardian boundaries are configured so users remain inside the mapped area.
- Test localization both with controllers and hand tracking (gesture reset).
- Validate kiosk mode by launching through the sample `PicoLauncher` script.
- Review localization metrics in the `PicoDiagnostics` overlay before shipping.
## Support
Enterprise support covers firmware validation, managed app distribution advice, and optimization reviews for sustained 72 Hz rendering.
---
# Rokid
Source: https://docs.arclip.design/vps/Unity SDK/platforms/rokid
The Rokid package targets Rokid Max / Rokid Max Pro headsets paired with Rokid Station or compatible Android hosts. Enterprise licensing is required to download the build.
## Enterprise Access
- Contact `support@arclip.design` with your Rokid model, host device lineup, and distribution plan.
- Enterprise customers receive:
- `com.webarvps.vps.rokid` Unity package with pose smoothing and station detection.
- Sample projects for optical see-through UX and voice command toggles.
- Documentation for sideloading via Rokid Developer tools.
> **Enterprise only:** Rokid transport adapters, driver checks, and diagnostic overlays are not shipped in the public SDK.
## Prerequisites
- Rokid Max / Max Pro with the latest firmware.
- Rokid Station or Android 12+ device with USB-C DP output.
- Unity 2022.3 LTS with Android build support.
## Setup Steps
1. Install the Enterprise package and import `Rokid_Localization.unity`.
2. Switch to the Android build target (IL2CPP + ARM64) and enable **Split APKs by target architecture**.
3. In `Project Settings β Player β XR Plug-in Management`, enable `OpenXR` and activate the **AR Clip Rokid Feature Group**.
4. Add the `RokidPoseAdapter` component to the same GameObject as `VPSLocalisationService`.
5. Build and install with `adb install-multiple` or the Rokid Developer assistant.
## Testing Checklist
- Calibrate interpupillary distance (IPD) using Rokid settings before localization tests.
- Validate voice-triggered localization reset (say βReset positioningβ) and confirm the headset haptic feedback fires.
- Test map switching when the glasses reconnect after a cable unplug event.
- Use the included `RokidDiagnostics` prefab to export logs for enterprise support.
## Support
Enterprise customers can schedule firmware validation and request custom UI audits for kiosk or guided-tour scenarios.
---
# WeChat Mini App
Source: https://docs.arclip.design/vps/Unity SDK/platforms/wechat
The WeChat integration ships a bridge between Unity and the Tencent Mini Program runtime for launching VPS-enabled AR content inside the WeChat ecosystem. Access is limited to Enterprise contracts.
## Enterprise Access
- Provide your Tencent developer account ID, target provinces, and mini program review schedule when contacting `support@arclip.design`.
- After approval you will receive:
- `com.webarvps.vps.wechat` Unity package.
- A WeChat Mini Program scaffold with secure API proxying.
- CI/CD templates for packaging Unity WebGL builds into the WeChat asset bundle format.
> **Enterprise only:** The WeChat transport layer, analytics hooks, and mini program templates are not distributed publicly.
## Prerequisites
- Approved WeChat Mini Program account with AR permissions.
- Unity 2022.3 LTS with WebGL build support.
- Access to the AR Clip WeChat proxy endpoint (Enterprise entitlement).
## Setup Steps
1. Add the Enterprise registry and install `com.webarvps.vps.wechat`.
2. Build your scene for WebGL using the AR Clip template; enable **Offscreen Rendering** in Player Settings.
3. Run the provided `wechat-packager` CLI to wrap the build output into a Mini Program asset.
4. Configure the API proxy by setting `WECHAT_VPS_PROXY_URL` in `Assets/WebARStudio/Wechat/Resources/Config.asset`.
5. Deploy to the WeChat developer console and test via the Mini Program dev tools.
## Testing Checklist
- Verify login and signature validation using your Tencent sandbox credentials.
- Run localization in both Wi-Fi and cellular scenarios to ensure low-latency frame uploads.
- Confirm analytic events reach the WeChat data dashboard via the bundled telemetry plugin.
- Test graceful fallback to 2D content if camera permission is denied.
## Support
Enterprise support includes help with Tencent security reviews, traffic quota sizing, and joint debugging sessions with AR Clip engineers.
---
# XREAL
Source: https://docs.arclip.design/vps/Unity SDK/platforms/xreal
The XREAL package targets Nebula-compatible devices (XREAL Air, Air 2, Air 2 Pro) and bundles controller profiles plus color-space optimizations. Only Enterprise accounts get access to the distribution.
## Enterprise Access
- Share your XREAL device SKU, planned regions, and distribution method (Nebula for Windows vs. Android).
- AR Clip will grant access to:
- `com.webarvps.vps.xreal` (Unity package).
- Native companion APKs for Nebula integration and USB host detection.
- Sample UX for heads-up overlays that respect the limited field of view.
> **Enterprise only:** XREAL-specific rendering pipelines and Nebula bridge services are not part of the public SDK.
## Prerequisites
- XREAL Air series glasses with the latest firmware and Nebula Companion 3.0+.
- Host device (Android 12+ or Windows 11) capable of running Unity builds.
- Unity 2022.3 LTS with URP and Android/Windows build support installed.
## Setup Steps
1. Install the Enterprise package and import the `XREAL_HUD.unity` sample scene.
2. Switch build target:
- **Android deployments:** Use `IL2CPP`, target API 33, and enable the `Nebula Bridge` checkbox in Player Settings.
- **Windows deployments:** Enable `PC, Mac & Linux Standalone` with `Windows` target and check **Use Primary Display Only** to prevent duplicate rendering.
3. Open `Project Settings β XR Plug-in Management` and enable `OpenXR`, then add the **AR Clip XREAL Feature Group**.
4. On `VPSLocalisationService`, assign the provided `XRealPoseAdapter` component to smooth head pose data.
5. Deploy to the host device, launch Nebula, and confirm the Unity view streams to the glasses.
## Testing Checklist
- Verify localization stability while walking slowly; the XREAL IMU introduces drift above 1.5 m/s.
- Test brightness-controlled overlays so UI remains readable in bright environments.
- Confirm the fallback 2D UI renders on the host phone/tablet when glasses disconnect.
- Run through the `Enterprise Diagnostics` scene to collect pose, frame rate, and localization logs for support.
## Support
Enterprise customers can request tailored UX audits for gesture-only interaction, plus firmware validation ahead of large deployments.
---
# Web SDK
Source: https://docs.arclip.design/vps/Web SDK/introduction
Documentation for the Web SDK is coming soon. Check back shortly for guides and API references.
---
# Welcome
Source: https://docs.arclip.design/vps/welcome
---
# Quick Start
Source: https://docs.arclip.design/docs/
# π Welcome to AR Clip
AR Clip is a platform for building augmented-reality and 3D experiences that open **from a link,
in a browser, with nothing to install**.
You do not need to know 3D, and you do not need to write code. Most of what people build here is
made by arranging objects and saying "when this is tapped, do that".
---
## π Your first ten minutes
### 1. Make an account
Sign up at **[arclip.design](https://arclip.design)**. The free plan is enough for everything on
this page.
### 2. Open a new project
You land in **Studio**, the editor, with an empty scene ready to go.
### 3. Put something in it
Press **+** in the top bar and add a cube. Drag it around. That is your scene β it already
works.
### 4. Make it do something
Select the cube, add an **Events** component, and build one event:
```
WHEN someone taps it
THEN move it up 0.2 m over 400 ms
```
No code. That pattern β a trigger and a list of steps β covers most of what a project needs.
### 5. See it
Press **Preview**. Tap the cube.
:::tip If it does nothing in the editor viewport, that is correct
The editor deliberately does not run your logic while you build β an animation playing as you
work would fight you for control. Always check behaviour in **Preview**.
:::
### 6. Share it
Press **Publish**. You get a link. Send it to someone β it opens on their phone, no app
required.
**That is the whole loop.** Everything else is variations on it.
---
## π§± Not sure this is the right tool?
AR is what the platform is known for, and plenty of what people build here never opens a camera
at all β configurators, product viewers, games, training, dashboards.
β [What you can build here](/docs/what-you-can-build)
## π― Start from something, not from nothing
Pick whichever is closest to what you have in mind.
### A poster that comes alive
An **Image** scene: upload the printed artwork, set its real width, and put your content on top.
Point a phone at the poster and the content appears in place.
*Good first project.* β [Scene triggers](/studio/panels/scenes)
### A product you can walk around
A **3D** scene with a model in it and orbit controls. No AR, works everywhere, opens instantly.
*Good for a shop or a portfolio.* β [Cameras](/creators-engine/concepts/cameras)
### Something on your table
A **Surface** scene: the visitor taps to place your content on a real floor or table, then walks
around it.
*Good for furniture, machines, anything room-sized.* β [Scene triggers](/studio/panels/scenes)
### A face filter
A **Face** scene with a mask or an object anchored to a facial point.
*Good for campaigns and toys.* β [Scene triggers](/studio/panels/scenes)
### A small game
Objects with physics, a character to drive, a score on screen.
*Good when you want to learn the whole toolkit.* β [Game controls](/creators-engine/authoring/game-controls)
---
## π§ Where to go next
| You want to⦠| Go to |
| ------------------------------------- | ------------------------------------------------------ |
| Learn the editor panel by panel | [Studio](/studio/) |
| Understand how a scene behaves | [Creators Engine](/creators-engine/) |
| Make things interactive | [Events](/creators-engine/authoring/events) |
| Write your first script | [Your first script](/creators-engine/scripting/first-script) |
| Have an AI assistant help | [Build with AI](/creators-engine/ai/) |
| Work with real-world positioning | [VPS](/vps/) |
| Work out why something is not working | [Troubleshooting](/creators-engine/troubleshooting) |
---
## π API access (only if you need it)
Most people never need this. You do if you want to drive AR Clip from your own tools, or let an
AI assistant build in your project.
**Profile β API Access** generates a key, and you can limit what it may do β read-only is enough
for analysis and cannot break anything.
> π **Treat a key like a password.** Never paste it into a chat, a screenshot or a public
> repository. Nobody legitimate will ask you for it in a conversation.
β [Connecting over MCP](/creators-engine/ai/mcp)
---
## π Stuck?
Most first-time problems are on the [troubleshooting page](/creators-engine/troubleshooting),
and the first two entries cover the majority of them.
Still stuck? The [community on Discord](https://discord.gg/sabCsp6tvR) is the fastest way to get
an answer from someone who has hit the same thing.
---
# What you can build here
Source: https://docs.arclip.design/docs/what-you-can-build
# What you can build here
AR is what AR Clip is known for, and it is not the boundary. The underlying thing is simpler and
broader: **interactive 3D that opens from a link, on any device, with nothing to install.**
Whether a camera is involved is your choice, not a requirement.
---
## Without any AR at all
These are ordinary applications. They run in a browser on a phone, a tablet, a laptop or a
headset, and there is no app store anywhere in the story.
### Product configurators
Pick a finish, a size, a layout; watch it change; get a link to buy. The commonest commercial
build here.
β [A worked configurator](/creators-engine/scripting/recipes#a-product-configurator)
### Product and portfolio viewers
One model, orbit controls, good lighting. Opens instantly, embeds in an existing page, and
replaces a carousel of photographs.
### Games
Physics, characters, multiplayer, an interface. Small enough to open from a link, which is
exactly why people finish them.
β [Game controls](/creators-engine/authoring/game-controls) Β·
[Multiplayer](/creators-engine/scripting/multiplayer)
### Training and simulation
A machine you can take apart, a procedure you step through, a space you learn before entering
it. Interactivity and a UI layer are the whole requirement, and both are here.
### Data and dashboards in 3D
A building with live sensors on it, a floor plan with occupancy, a model with hotspots. Your
data arrives through a script; the scene is just how it is shown.
### Presentations and installations
Something that runs on a screen at an event, on a kiosk, or in a meeting, driven by a timer or
by whoever is presenting.
---
## With AR, when the room matters
Reach for the camera when **where the visitor is standing** is part of the point.
| You want | Use |
| ---------------------------------------------- | ------------------------------------------------------------- |
| A printed thing to come alive | an image or QR scene |
| Furniture in someone's actual room | a surface scene |
| A filter or a try-on | a face scene |
| Directions and labels across a whole venue | [VPS](/studio/vps-scenes) |
| To be somewhere else entirely | a 360Β° scene |
---
## Everywhere it runs
One project, and you do not maintain a version per platform:
| Where | How |
| -------------------- | -------------------------------------------------------------- |
| Phone browsers | the published link, or a QR code |
| Desktop browsers | the same link |
| iOS App Clip | opens from a link, a code or NFC, with no install |
| The native app | native rendering, for projects that need the frame budget |
| Headset browsers | the same link again, through WebXR |
| Embedded in your site| a snippet, inside your own page |
β [Why this engine](/creators-engine/why-this-engine)
---
## When this is the wrong tool
Worth saying plainly, so you do not find out late:
- **A conventional 2D app** β a form, a list, a settings screen β is faster to build with
ordinary web tools. The interface layer here exists to sit over a 3D scene.
- **Offline-first** work does not suit something that opens from a link.
- **Very heavy simulation** β thousands of interacting bodies β will fight a browser, though the
native app raises that ceiling considerably.
Everything else that is visual, spatial or interactive is fair game.
---
**Next:** [Quick Start](/docs/)