# AR Clip Documentation β€” full text Generated from the documentation site. Canonical: https://docs.arclip.design --- # Creators Engine Source: https://docs.arclip.design/creators-engine/ import Link from '@docusaurus/Link'; # πŸ‘‹ Welcome to the Creators Engine This is where your experience comes to life: what happens when someone taps an object, how a door opens, why the ball falls, when the next scene appears. You can build all of it **without writing code**, and drop into TypeScript whenever you want more control. Most projects use both. --- ## 🧩 One idea to start with Everything in your scene is an **object**, and an object is simply a **set of components**. Components are capabilities you switch on. Give an object a light component and it lights the room. Give it a geometry and a material and it becomes a visible shape. Add a rigid body and it starts falling. You never write the code that reacts to those components β€” the engine does that part. Your job is to decide **which components an object has, and what their values are**, either in the editor or from a script while the experience is running. That is the whole model. Everything else in this section builds on it. Change a value and only the things that were using that value react β€” the way a spreadsheet recalculates one column rather than the whole sheet. You never refresh anything. β†’ [Why this engine](/creators-engine/why-this-engine) --- ## πŸš€ Pick your path

🎨 Build without code

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
Loading…
``` Or skip the HTML entirely and point `entry` at a `.tsx` file β€” the runtime mounts its default export and you can build the panel from the editor's own component library: ```tsx // main.tsx import { useState } from 'react'; import { Button } from '@was/ui'; export default function Panel() { const [clicks, setClicks] = useState(0); return ; } ``` :::note `init()` waits until the world is really there It runs once the connection to the editor is open **and** the synced copy of the scene is ready, so you never have to poll for either. ::: Four globals are always available: **`editor`** (the editor API), **`world`** (a live, synced copy of the scene as entities and components), **`meta`** (your manifest) and **`init`**. You can import `@was/ecs`, `@was/signals`, `@was/engine`, `@was/editor-api`, `@was/ui`, `@was/icons`, `react` and `react-dom/client`. Anything else fails loudly rather than silently resolving to nothing. :::warning The styling set is fixed Panels built from `@was/ui` use a prebuilt stylesheet, so only the classes it ships exist. Arbitrary values like `text-[13px]` are not among them β€” use an inline `style` for sizes outside the scale. ::: ## Working with the scene You could assemble entities by hand through `world`, but for scenes and spaces there is a better way β€” ask the editor, and you get its own defaults, numbering and safeguards: ```ts await editor.scenes.list(); await editor.scenes.create({ name: 'Chapter 2', trigger: { type: 'image', imageId } }); await editor.scenes.setTrigger(sceneId, { type: 'surface', bindingType: 'wall' }); await editor.scenes.delete(sceneId); await editor.spaces.create({ name: 'Lobby' }); ``` The `trigger` is just an anchor's data, validated by the same schema the editor uses β€” so new trigger types work without the plugin protocol changing. An image trigger works out its physical size from the image itself if you do not give one. ### Finding your own work again A plugin that generates scenes needs to find them later. That is what `tags` is for β€” it is namespaced to your plugin automatically, so two plugins never overwrite each other's marks, and the editor neither shows nor touches them: ```ts tags.set(entity, { kind: 'scene', card: '2' }); tags.find({ kind: 'scene' }); tags.remove(entity, 'card'); ``` ## Shared session state Some state belongs to the meeting, not the document: a running timer, an open vote, a raised hand. Undoing it, publishing it or storing it in the project would all be wrong. The **project room** is a small shared key-value store that everyone currently in the project sees: ```ts const { now, entries } = await editor.room.get(); await editor.room.set('timer', { running: true, endsAt: now + 60_000 }); editor.on('room.changed', (state) => render(state.entries.timer)); await editor.room.delete('timer'); ``` It survives a page reload, expires after twelve hours, and holds up to 64 keys of 8 KB. :::tip `now` is the server's clock, and that is the point Two people's computers can disagree by minutes. Store **absolute end times** from the server clock and let each client count down itself β€” never store "seconds remaining", or a ticking timer would mean writing to the room every second for everyone in the project. ::: The server also records **who wrote each key**, which is what makes honest voting possible: a vote counts only when the key `vote/` was actually written by that user. ## The viewport overlay A panel is private and can be closed, which is no good for something everyone should see. A panel declared with `"surface": "hud"` is drawn as a small overlay **on top of the scene** instead: ```ts editor.hud.set({ visible: true, width: 240, height: 96 }); editor.panels.open('main'); // an overlay can summon its own panel ``` It starts hidden and shows itself when it has something to show β€” an invisible transparent frame over the scene would swallow clicks. Size is capped at 640Γ—400. :::note The overlay gets no copy of the scene It is up for everyone, all the time, so it is deliberately cheap: whatever it needs to display, the panel puts into the room. ::: ## Publishing The publish form takes an icon, a name, up to 12 tags, a description up to 500 characters, up to 4 screenshots, and a subscription-only flag. Limits: **512 KB per file, 64 files, 50 plugins per account.** Publishing again updates the same entry. You can also **download a published plugin for editing**, which writes its files back into a folder you pick. :::note Assets only work once published A published plugin's files are served properly, so `./icon.png` resolves. A local development folder only inlines its `.ts`, `.js` and `.json` β€” images referenced by path will not appear until you publish. ::: ## Permissions The manifest lists what your plugin needs β€” `scene:read`, `scene:write`, `resources:write`, `spaces:write`, `collaboration:read`, `collaboration:write`, `editor:panels` and others β€” and the server rejects unknown ones. :::warning Declare only what you need Permissions are what reviewers read, and what people judge your plugin by. Ask for the narrowest set that does the job. And when **installing** someone else's plugin, assume it can reach the whole project β€” only install what you have reason to trust. β†’ [Trust and verification](/studio/plugins#trust) ::: --- **Next:** [Component extensions](/studio/component-extensions) --- # Plugins Source: https://docs.arclip.design/studio/plugins # Plugins Plugins extend Studio itself. A plugin is a folder of files that runs inside the editor without anything being rebuilt β€” it can add a panel, generate scenes, process resources, or add a new kind of component to objects. Want to build one? β†’ [Writing a plugin](/studio/plugin-development). ## The plugins panel Two tabs. **My uploads** β€” your local development folders, your published plugins and the ones you installed, in one list. From here you can create a plugin, open an existing folder, reload it from disk, publish or unpublish, and delete. **Library** β€” the shared registry: search, tags, screenshots, author, rating, install count and favourites. Your own and installed plugins rank higher in results. Search spans both: type a query and the tabs collapse into a single list, yours first. ## Installing Installing adds a plugin to **your** editor. Its icon appears in your toolbar. **Adding it to a project** is different: the plugin then appears for **everyone** who opens that project, not just you. That is the right choice for a plugin your whole team needs β€” a house style checker, a scene generator you all use. Removing a plugin from the registry removes it from every project that used it. ## Trust :::warning A plugin runs with access to your project Opening someone else's plugin asks for confirmation once, because it can read and change your scene and resources. "Do not ask again" remembers your answer for that plugin. Treat the library the way you would treat any third-party extension: install what you have reason to trust. ::: ### What verification means A verified plugin is not "a good plugin". It means **someone read this exact code**. Verification is tied to a fingerprint of the plugin's contents. Publishing new code changes the fingerprint, so a verified plugin drops back to pending until it is looked at again. Editing the description, tags or screenshots does not β€” those do not run. A verification can waive up to three separate things: | Waives | Means | | ---------- | -------------------------------------------------------------------------- | | `open` | no confirmation dialog when you open its panel | | `autorun` | its self-starting parts may run without asking β€” see below | | `warnings` | the "we do not review plugin code" notices are hidden | An empty set is possible: the badge alone, with every prompt still in place. "We read the code" and "we will stop asking you" are deliberately different promises. :::danger Self-starting code is a bigger promise A panel runs when you open it. A **component extension** or a **viewport overlay** runs every time anyone opens the project. That is why `autorun` is waived separately, and why an untrusted plugin's self-starting parts do not mount at all. ::: Statuses are `none`, `pending`, `verified`, `rejected` and `revoked`. A revoked verification also clears your local "do not ask again" β€” that consent was about different code. ## Ratings and premium One rating per person, one to five stars. Some plugins are marked as requiring a subscription; your own are never gated. ## What you need The development side needs a Chromium-based browser, because reading a local folder uses the File System Access API. **The library works everywhere** β€” only building plugins from a local folder needs Chromium. --- **Next:** [Writing a plugin](/studio/plugin-development) --- # Project settings Source: https://docs.arclip.design/studio/settings/project-settings # Project settings Settings that apply to the whole project, whichever space a visitor is in. ## Display **Preload the AR project** makes the experience start more reliably by fetching content up front. It trades a longer wait at the start for fewer stalls once running β€” worth it for anything with large models. ## Analytics Connect several counters at once. Each takes the id from that service: | Service | Looks like | | ---------------- | ----------------------- | | Yandex Metrica | `87946806` | | Facebook Pixel | `1234567890123456` | | Google Analytics | `UA-56589716-1` | A counter added without an id is flagged as a warning rather than silently doing nothing. ## Loading screen The first thing anybody sees, so it is worth a few minutes. | Setting | Options | | -------------- | ---------------------------------------------------------------- | | Background | an image (cover, contain or fill) or a colour | | Logo | your logo β€” JPG around 525Γ—525 works well | | Start button | on or off, with its own colour, text colour and label | | Onboarding | the prompts explaining what to point at | | Loader | on or off, which style, and a custom animation | ### Replacing it entirely Above those fields is a **Preset** selector. Leave it on the default and you get the built-in screen described above. Choose **+ Add new** and a **script** takes over both screens outright β€” the one the visitor taps and the one that fills up. Because that script declares its own settings, whoever uses it afterwards gets a form to fill in rather than code to edit; if it declares none, the panel says so. That is the route for a branded opening that the built-in fields cannot express. β†’ [Loading and the launch screen](/creators-engine/concepts/loading) :::tip Keep the start button on for anything with sound Browsers refuse to play audio until the visitor interacts with the page. Without a button to press, your first sound will not play and it will look like a bug. ::: ## Languages Set the default language, and optionally ask visitors which they want on launch. ## Controls **Scene rotation access** lets visitors rotate the scene themselves, and you choose which axes they may rotate around. Restricting axes is usually right: an object that can be tumbled freely ends up upside down, and visitors rarely realise that is what they did. ## Capture Let visitors take a **photo** or record **video** of what they are seeing. This is the feature that gets an AR experience shared, so it is worth enabling whenever the content is worth showing off. ## Project scripts Behaviour that runs everywhere in the project, in every space. Each slot points at a script or patch file and can be switched off without removing it. This is the right home for anything that has to survive moving between spaces β€” a score, a visitor's choices, a session timer. ## Domain Every published project already has an address of the form `example.arclip.design`. That address, and the snippet for embedding the project in a page, live in the **publish dialog** rather than here. This section is for putting the project on **your own** domain instead. 1. Type the domain β€” `ar.example.com`, or the bare `example.com` β€” and press **Connect**. 2. Point that name at AR Clip in your DNS. 3. The status under the field tracks the connection; it turns to connected once the platform has verified the domain and issued its certificate. **Disconnect** releases it again. :::note Get the DNS target from your account or support The exact record to create belongs to the platform rather than to the editor, and it can differ between plans and regions. Ask before you edit DNS β€” a guessed record simply never verifies. ::: :::tip A subdomain is easier than a root domain `ar.example.com` needs one record and leaves your main site alone. Putting a project on a bare `example.com` means moving the whole domain's traffic, which is rarely what anyone wants for a campaign. ::: --- **Next:** [VPS scenes](/studio/settings/../vps-scenes) --- # Space settings Source: https://docs.arclip.design/studio/settings/space-settings # Space settings These belong to the **space**, so every scene inside it shares them. ## Background | Skybox | What you get | | ---------- | ---------------------------------------------------------- | | `color` | a flat colour | | `gradient` | a top and a bottom colour | | `texture` | an image or environment map | | `none` | no background β€” the camera feed shows through in AR | :::note In AR you almost always want `none` A background fills the space behind your content, which in an AR scene means covering the real world. `none` is what lets the camera through. ::: ## Grid The grid is an editing aid β€” it is not part of your published experience. | Setting | What it does | | ---------------- | ------------------------------------------------ | | Visibility | show or hide it | | Plane | XZ (floor), XY, or YZ | | Snap to grid | objects snap while you drag them | | Show scale | label the divisions | | Fade | how far out the grid fades away | | Origin marker | colour, size and opacity of the world origin | **Snap to grid is worth turning on early.** Objects that are almost aligned look like a mistake, and fixing it later means re-placing things one at a time. ## Units Metres, centimetres, millimetres, feet, inches or pixels. Every measurement in the editor is shown in this unit, including physical trigger sizes. :::warning Units are how your content ends up the wrong size in AR The engine works in metres. If you model in centimetres and set a trigger's physical size as though it were metres, your content arrives a hundred times too large. Pick the unit that matches how you think about the real object and stay with it. ::: ## Environment lighting An HDRI or image used as a light source. This is the single biggest improvement you can make to how a scene looks: it lights objects from every direction with a real environment, which is what makes metals and glossy surfaces read correctly. | Setting | What it does | | ----------- | ------------------------------------- | | Map | the HDRI or image | | Intensity | how strong it is | | Visibility | on or off | ## Ambient light A flat colour added everywhere, with a colour and an intensity. Cruder than an environment map, but cheap and useful for lifting shadows. ## Fog | Setting | What it does | | ----------------- | ------------------------------------------------ | | Visibility | on or off | | Colour | the colour distance fades toward | | Near / far | where fog begins and where it is complete | | Apply to skybox | whether the background fades too | Set the fog colour to match your background and distant objects dissolve into the horizon rather than ending abruptly. ## Camera The camera mode decides which one a visitor's device opens with: automatic, front-facing, or the main rear camera. Front-facing is what a face-filter scene wants. --- **Next:** [Project settings](/studio/settings/project-settings) --- # Placing content in a real place (VPS) Source: https://docs.arclip.design/studio/vps-scenes # Placing content in a real place Most AR scenes attach to something the camera recognises: a picture, a code, a flat surface. VPS does something different β€” it works out **where the visitor actually is** inside a place you scanned beforehand, and puts your content at the spot you chose. That is what lets a museum label sit on the right exhibit, or an arrow point down the correct corridor, without a marker on the wall. :::note The trade-off A marker works anywhere you can print one and needs no preparation. VPS needs the place scanned first, and in return it works across a whole space with nothing stuck to the walls. ::: ## The path, end to end ### 1. Scan the place Capture a map with the **AR Clip Mapper** app. A phone is enough β€” LiDAR is not required. β†’ [AR Clip Mapper](/vps/spatial-mapping/building-map/arclip-mapper) Larger or more demanding sites have other routes in β€” LiDAR, 360Β° cameras, existing CAD or BIM data, panoramas and video. β†’ [LiDAR](/vps/spatial-mapping/building-map/advanced/lidar_pipeline) Β· [360Β° and video](/vps/spatial-mapping/building-map/advanced/panoramas_video) Β· [CAD and BIM](/vps/spatial-mapping/building-map/advanced/cad_bim) Β· [custom imagery](/vps/spatial-mapping/building-map/advanced/custom_images) ### 2. Wait for processing The capture is processed in the cloud and becomes a map your team can use. Each one gets an id. ### 3. Make the scene In Studio, add a scene and set its trigger to **VPS**, then pick which of your team's maps it should locate against. β†’ [Scenes panel](/studio/panels/scenes) :::warning A VPS scene with no map selected cannot work The editor warns you about it. Pick at least one β€” several is fine, and useful when one place has been scanned in parts. ::: ### 4. Place your content Position objects in the scene the way you would anywhere else. Their positions are relative to the scanned place, so what you arrange here is what a visitor standing there will see. ### 5. Handle the waiting This is the part people skip, and it is the difference between an experience that feels broken and one that feels considered. Localisation is not instant. The visitor arrives, points their phone around, and the system tries to recognise where they are β€” which can take several attempts. | Event | What to do with it | | --------------------------- | --------------------------------------------------------- | | **VPS ready** | positioning has started; show "look around" | | **VPS localized** | they were found; reveal the content | | **VPS localization failed** | **routine** β€” keep encouraging, do not show an error | | **VPS error** | positioning cannot run at all; this one *is* a failure | :::danger Do not treat a failed localisation as an error It fires repeatedly and normally while somebody walks around looking for a recognisable spot. An apologetic error message on every miss makes a working experience feel broken. Show guidance instead β€” "point at the building", "try moving closer" β€” and only give up after many attempts. ::: Unlike a marker scene, VPS content is **not** shown or hidden for you. That is deliberate: a missed localisation would otherwise reveal your content in the wrong place. Wire the reveal yourself, on **VPS localized**. ## Getting a good scan The same things that help a camera recognise a picture help it recognise a place: - **texture and detail** beat blank surfaces β€” brick, signage and clutter are good; a white corridor is hard; - **steady, overlapping passes** beat a quick sweep; - **consistent lighting** β€” a place scanned at night may not be recognised at noon; - **scan what will still be there** β€” furniture moves, walls do not. ## Testing You cannot test VPS from a desk. It needs a phone, in the actual place, with the map processed. Build and check everything else β€” layout, interactions, the interface β€” in a plain 3D scene first, then switch the trigger to VPS once the content is right. --- **Next:** [Figma and Sketchfab](/studio/integrations) --- # Android SDK Source: https://docs.arclip.design/vps/Android Native SDK/introduction ## Overview The WAS VPS Android SDK ships with Sceneform fragments and lifecycle helpers so you can localize users inside VPS maps on ARCore-enabled devices. Follow the Immersal-style flow: confirm prerequisites, install the dependency, prepare the manifest, and start the service. ## Prerequisites - Minimum Android SDK 24 (Android 7.0) - Device with [ARCore](https://developers.google.com/ar/devices) support - Project configured with Kotlin (samples are Kotlin-first) > 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). ## Install the SDK 1. Ensure the project resolves artifacts from Maven Central: ```gradle allprojects { repositories { mavenCentral() // other repositories... } } ``` 2. Add the dependency to your app module: ```gradle dependencies { implementation "com.arstudio:wasvps-sdk:1.0.0" } ``` 3. Sync Gradle. ## Manifest Setup Declare ARCore requirements and override the minimum SDK if needed: ```xml ``` To relax the Play Store device filter, set `android:required="false"` and keep the existing `tools:replace="android:required"` attribute. ## Quick Start with `WASVPSArFragment` Host the provided fragment inside your layout: ```xml ``` ```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) supportFragmentManager.beginTransaction() .replace(R.id.vFragmentContainer, WASVPSArFragment()) .commit() } } ``` Access the service and start localization when you have location IDs: ```kotlin val fragment = supportFragmentManager .findFragmentById(R.id.vFragmentContainer) as WASVPSArFragment val config = WASVPSConfig.getIndoorConfig(listOf("your-location-id")) val service = fragment.vpsService service.setVpsConfig(config) service.setVpsCallback(object : VpsCallback { override fun onSuccess() = Unit override fun onFail() = Unit override fun onStateChange(state: State) = Unit override fun onError(error: Throwable) { Log.e("VPS", "Localization error", error) } }) service.startVpsService() ``` Call `service.stopVpsService()` when the AR experience should pause (e.g., in `onPause`). ## Custom Fragment Integration For projects with a custom `ArFragment` or `ArSceneView`, create and manage `WASVPSService` yourself: ```kotlin class CustomArFragment : ArFragment() { private val vpsService = WASVPSService.newInstance() override fun onAttach(context: Context) { super.onAttach(context) WASVPSSdk.init(context.applicationContext) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) vpsService.bindArSceneView(arSceneView) vpsService.setVpsConfig(WASVPSConfig.getOutdoorConfig(listOf("location-id"))) } override fun onResume() { super.onResume() vpsService.resume() vpsService.startVpsService() } override fun onPause() { vpsService.pause() vpsService.stopVpsService() super.onPause() } override fun onDestroyView() { vpsService.destroy() super.onDestroyView() } } ``` ## Anchoring Content Place nodes relative to `vpsService.worldNode`. Any child added to that node automatically follows the localized global transform once VPS succeeds. ```kotlin val anchorNode = Node().apply { setParent(vpsService.worldNode) localPosition = Vector3(0f, 0f, -1f) } ``` ## Troubleshooting - Ensure Google Play Services for AR is installed and updated on the test device. - If localization never succeeds, verify the API key assigned to the SDK (via secure config) and confirm your location IDs exist. - Review the `/sample` module in the repository for end-to-end usage, including runtime permission handling and UI. --- # AR Clip SDK Source: https://docs.arclip.design/vps/AR Clip SDK/introduction ## Overview AR Clip lets you deliver App Clip–powered AR experiences that stream WebGL content while relying on ARKit and the AR Clip VPS engine for localization. Follow the Immersal-style flow below: verify prerequisites, add the package, prepare the scene, enable VPS, and upload the WebGL build. ## Prerequisites - Unity 2020 LTS or newer (tested with 2021+) - WebGL build target - Access to the AR Clip zip uploader (https://cdn.mobile.web-ar.studio/clip/pages/zip_uploader.html) > **Testing note:** In the Unity Editor most AR Clip APIs are wrapped with `Application.isEditor`. Build WebGL content and test it via the AR Clip mobile app for real tracking. ## Install the Package 1. Open **Window β†’ Package Manager**. 2. Click **+ β†’ Add package from Git URL…**. 3. Paste `https://github.com/WebAR-Studio/arclip_sdk.git` and confirm. Before importing, remove any legacy `Assets/ARLib` folder to avoid duplicate symbol errors such as: ``` error CS0433: The type 'ARLibTester' exists in both 'ARLib' and 'Assembly-CSharp' ``` ### Import samples From **Package Manager β†’ AR Clip β†’ Samples**: - Import **WebGLTemplates** and copy the resulting folder into `Assets/` (root) so it appears under **Project Settings β†’ Player β†’ WebGL Template**. - Import **TransparentBackground** and move `TransparentBackground.jslib` to `Assets/Plugins`. This enables transparent rendering in WebGL. ## Scene Setup 1. Add an empty GameObject named `ARClipController`. 2. Attach `ARLibController` and assign a camera to the `renderCamera` field. 3. Disable the Camera component on that GameObject, set **Clear Flags = Solid Color**, and use **RGBA(0,0,0,0)** for the background. 4. Optional: Add `ARLibTester` (from Samples) in the Editor to simulate native callbacks. ### Minimal bootstrap script ```csharp using UnityEngine; using ARLib; public class ARClipBootstrap : MonoBehaviour { [SerializeField] private Camera renderCamera; private void OnEnable() { ARLibController.Initialized += HandleInitialized; ARLibController.VPSPositionUpdated += HandleVpsPose; } private void OnDisable() { ARLibController.Initialized -= HandleInitialized; ARLibController.VPSPositionUpdated -= HandleVpsPose; } private void Start() { ARLibController.SetRenderCamera(renderCamera); ARLibController.Initialize(); } private void HandleInitialized() { ARLibController.EnableCamera(); ARLibController.EnableAR(); ARLibController.EnableSurfaceTracking("horizontal"); } private void HandleVpsPose(VPSPoseData pose) { Debug.Log($"VPS pose: {pose.Position}"); } } ``` ## Start VPS Localization Configure VPS after initialization and before starting localization: ```csharp var settings = new VPSSettings { apiKey = "your-api-key", locationIds = new[] { "your-location-id" } }; ARLibController.SetupVPS(settings); ARLibController.StartVPS(); ``` Listen to: - `ARLibController.VPSInitialized` to know when the subsystem is ready. - `ARLibController.VPSPositionUpdated` for localization poses. - `ARLibController.OnVPSErrorHappened` for error strings. Timing helpers such as `SetSendPhotoDelay`, `SetGpsAccuracyBarrier`, and `SetFirstRequestDelay` allow tuning request cadence for specific locations. ## Tracking Modules - Register images with `AddTrackingImage` before enabling tracking. Wait for `TrackedImagesArrayUpdate` to confirm they loaded. - Call `EnableSurfaceTracking("horizontal" | "vertical" | "both")` to detect planes. Results are dispatched through `SurfaceTrackingUpdated`. ## Build & Upload Workflow 1. **Select WebGL template:** Under **Project Settings β†’ Player β†’ WebGL β†’ Resolution and Presentation**, choose the template you copied from the package. 2. **Build:** Use **File β†’ Build Settings β†’ WebGL β†’ Build**. 3. **Zip:** Compress the build output so `index.html` resides at the zip root. 4. **Upload:** Submit the archive via https://cdn.mobile.web-ar.studio/clip/pages/zip_uploader.html. 5. **Test:** Scan the generated QR code with the AR Clip iOS app (https://apps.apple.com/app/ar-clip/id6742754238). ## Troubleshooting - No camera feed in WebGL: ensure `TransparentBackground.jslib` lives in `Assets/Plugins` and the render camera is both assigned and disabled. - Editor play mode lacks AR events: this is expected. Use `ARLibTester` or build to WebGL for live data. - Duplicate symbol errors: confirm the legacy `ARLib` folder is removed before importing the package. --- # Supported Platforms Source: https://docs.arclip.design/vps/getting-started/compatibility # Supported Platforms AR Clip VPS powers production-grade positioning across **mobile devices, XR headsets, and the browser**. You can integrate through our SDKs or by using the REST API directly. The platform delivers markerless localization without GPS dependence or app store friction. --- ## 🧩 SDK Coverage ### **Unity SDK** Works with any **ARKit** or **ARCore** capable hardware through Unity AR Foundation. The SDK provides a single API for mapping, localization, and content anchoring. | Device / Platform | SDK / Plugin | Status | |-------------------|--------------|--------| | **iOS / Android** | Unity AR Foundation (ARKit / ARCore) | βœ… Supported | | **Rokid / Rokid Max** | AR Clip Bridge + Rokid SDK | βœ… Supported | | **XREAL Light / ONE** | XREAL SDK for Unity | πŸ”§ In development | | **PICO 4 Enterprise / 4 Ultra** | PICO SDK for Unity | πŸ”§ In development | | **Magic Leap 2** | Magic Leap XR Plugin | πŸ”§ In development | | **Apple Vision Pro** | Unity PolySpatial | πŸ”§ In development | | **Meta Quest 3 / Pro** | Unity OpenXR + AR Clip VPS Bridge | πŸ”§ In development | --- ### **Web SDK** AR Clip was built with a **web-first** mindset. The SDK ships for **JavaScript / TypeScript** and supports **WebXR**, **WebGL2**, and **WASM** deployments. | Integration | Description | Status | |-------------|-------------|--------| | **AR Clip Studio SDK** | Launch and control scenes directly in the browser | βœ… Supported | | **WebXR integration** | Native WebXR API access with VPS positioning | βœ… Supported | | **AR Clip (instant app)** | Instant QR-triggered launch with no installs | βœ… Supported | | **AR Clip Γ— 8thWall Bridge** | AR Clip VPS combined with 8thWall SLAM tracking | πŸ”§ In development | | **AR Clip Γ— Zappar Template** | AR Clip VPS + Zappar SLAM (Mattercraft) | πŸ”§ In development | > πŸ“± **AR Clip** lets you deliver rich WebAR scenes straight from a QR code, complete with real-time VPS localization. --- ### **REST API** The **AR Clip REST API** connects VPS to external services, back-end systems, and automated capture pipelines. Capabilities include: - **Spatial mapping** β€” upload videos or image sets to generate maps in the cloud. - **Visual positioning** β€” submit frames and receive precise `pose` responses. - **Project management** β€” automate map, project, and API key operations from your tooling. > πŸ’‘ Use the API for automated mapping, coverage analytics, and quality assurance across large portfolios. --- ## 🧠 Recommended Hardware ### For Spatial Mapping | Device type | Examples | |-------------|----------| | **Mobile devices** | iPhone (ARKit), Android (ARCore), Huawei (AR Engine) | | **360Β° cameras** | Insta360, Ricoh Theta, GoPro MAX | | **LiDAR / 3D scanners** | Polycam, 3D Scanner App, Matterport Pro3 | | **Professional scanners** | Leica BLK2GO, NavVis VLX | | **AR Clip Mapper** | Built-in mobile capture workflow | > πŸ“¦ Uploaded data is automatically converted into the **AR Clip Map Tile** format, ready for VPS without manual processing. ### For Localization | Device type | Examples | |-------------|----------| | **Mobile devices** | iPhone, Android, Huawei XR | | **XR / AR headsets** | Rokid XR (supported) | | **XR devices in progress** | XREAL Light / Air, PICO 4, Magic Leap 2, Vision Pro, Meta Quest | | **WebXR (browser)** | Chrome, Edge, Safari with camera access | --- ## 🌐 Platform Matrix | Environment | Support | |-------------|---------| | **Web (JS/TS)** | βœ… AR Clip SDK + WebXR | | **iOS (Swift)** | βœ… ARKit + native SDK | | **Android (Kotlin)** | βœ… ARCore + native SDK | | **Unity** | βœ… AR Foundation + XR plugins | | **REST API** | βœ… Full mapping & localization | | **XR headsets** | βœ… Rokid XR; πŸ”§ others in development | --- 🧭 AR Clip VPS unifies localization, mapping, and deployment workflows so teams can ship reliable spatial experiences across web, mobile, and immersive devices. --- # Frequently Asked Questions Source: https://docs.arclip.design/vps/getting-started/faq ## πŸ’¬ How does AR Clip compare to other VPS platforms? | Capability | Typical platforms | AR Clip VPS | |------------|-------------------|-------------| | **Pricing model** | Seat licenses, snapshot/call limits | Usage counted per user or view | | **Commercial use on Free** | Usually restricted or prohibited | Allowed | | **Web SDK availability** | Paywalled behind Pro/Enterprise tiers | Included on every plan | | **Map quota** | 1–5 maps per account | Unlimited | | **Map size limits** | Tied to capture budgets | No hard limits | | **WebAR / instant launch** | Rare or missing | Built into AR Clip | | **Heatmaps & analytics** | Enterprise-only add-on | Available on Creator+ | | **Branding & watermark** | Platform watermark required | No watermark; custom branding on Enterprise | | **Private VPS / self-host** | Enterprise by special request | Included with Enterprise | | **Direct support / SLA** | Enterprise tier only | Creator and above | | **AR/VR headset coverage** | Partial | Full device lineup supported | | **Turn-key production** | Not provided | Offered on Enterprise engagements | ## πŸ“Š Why teams choose AR Clip - πŸš€ **No artificial limits on maps or coverage**, even on Free - πŸ’Ό **Commercial launches from day one** with no upgrade required - βš™οΈ **Unified SDK stack** for Web, Unity, iOS, Android, and AR Clip - 🧭 **Transparent usage billing** β€” we meter real users, not raw frames - 🎨 **Brand-safe experiences** with zero watermarking and optional white-labeling - 🧠 **Complete spatial infrastructure** β€” VPS, SDKs, Studio, and Analytics in one ecosystem > πŸ’¬ *AR Clip is more than an SDK β€” it is the production stack for the spatial internet.* ## πŸ”— Get started now - [Open the AR Clip Console](https://arclip.design) - [Download the SDK](https://github.com/WebAR-Studio/was-vps-unity/tree/master) --- # Introduction Source: https://docs.arclip.design/vps/getting-started/introduction # How AR Clip VPS Works The **AR Clip VPS SDK** makes persistent, markerless spatial experiences possible across mobile, web, and XR devicesβ€”without depending on GPS accuracy or native app installs. Under the hood the platform combines two core workflows: - **Spatial Mapping** β€” capturing a physical space and converting it into a VPS-ready map. - **Localization** β€” calculating the live pose of a device relative to that map with sub-meter precision. --- ## πŸ—ΊοΈ Spatial Mapping Mapping turns real-world footage into a localization-ready data set. With AR Clip you can capture environments in multiple ways: - **AR Clip Mapper** β€” our mobile app for video capture and feature extraction. - **Supported scanners and cameras** β€” LiDAR, 360Β° cameras, and RGB-D sensors. - **AR Clip REST API** β€” upload existing imagery or point clouds programmatically. Once processing finishes in the cloud, the map becomes available to every SDK. > πŸ’‘ Each map receives a unique `mapId`. Pass it to the SDK whenever you want to accelerate localization and boost accuracy. When no `mapId` is provided, AR Clip will still localize by automatically selecting the best matching map. --- ## πŸ“ Localization Localization determines the **position and orientation** of the device in the mapped environment. AR Clip VPS compares live camera frames against map features and delivers a pose (`pose`) with accuracy up to **50 cm** and latency under **300 ms**. You can localize through: - **Platform SDKs** for Web, iOS, Android, and Unity. - The **REST API** if you need server-side or custom client integrations. --- ## πŸš€ Quick Start Checklist 1. **Create an account** at [arclip.design](https://arclip.design). The free tier includes SDK access, the REST API, and mapping tools. Your `apiKey` is issued after sign-up. 2. **Capture a map.** Use AR Clip Mapper or upload data manually. After processing, the map appears in the *Maps* section of the console. 3. **Integrate the SDK.** Install the SDK for your platform, provide the `apiKey`, and optionally specify `mapId` values. You will start receiving `pose` updates as soon as localization succeeds. 4. **Deliver your AR experience.** Attach anchors, routes, and content through Unity, WebAR Studio, Three.js, or your custom rendering pipeline. --- ## πŸ” Your developer token ```text ``` Find your token in your account settings. Use it for REST API calls and Pro features such as automated map management and VPS node control. > πŸ” Treat it like a password: keep it out of client-side code, screenshots and public > repositories. If it leaks, revoke it in your account and issue a new one. 🧭 AR Clip VPS is the spatial infrastructure layer that unifies localization, mapping, and creator tooling across web and native stacks. --- # Pricing Source: https://docs.arclip.design/vps/getting-started/pricing πŸ’Ž AR Clip VPS β€” simple, honest pricing Launch, scale, and operate spatial projects with transparent limits. AR Clip offers three clear plans β€” no sales gatekeepers or hidden restrictions. --- ## 🧭 AR Clip plans | Feature / Plan | **Free (Starter)** | **Creator (Growth)** | **Enterprise (Infra)** | |----------------|-------------------|----------------------|------------------------| | **Monthly price (USD)** | 0 | 49 | from 200 | | **Maps included** | Unlimited | Unlimited | Unlimited | | **Max map size (mΒ²)** | Unlimited | Unlimited | Unlimited | | **SDKs (Web / iOS / Android / Unity / WebXR / AR Clip)** | βœ… | βœ… | βœ… | | **AR/VR headset SDKs** | βœ… | βœ… | βœ… | | **Map operations (merge, alignment)** | ❌ | βœ… | βœ… | | **Analytics & heatmaps** | ❌ | βœ… | βœ… | | **Monthly unique views** | up to 1,000 | up to 50,000 | Unlimited | | **Commercial use** | βœ… | βœ… | βœ… | | **Branding** | No watermarks | No watermarks | No watermarks + AR Clip card branding | | **Private VPS / self-host** | ❌ | ❌ | βœ… | | **Guaranteed QPS / SLA** | ❌ | ❌ | βœ… | | **Direct support** | ❌ | βœ… | βœ… | | **Turnkey navigation projects** | ❌ | ❌ | βœ… | | **Turnkey location digitisation (stadiums, museums, etc.)** | ❌ | ❌ | βœ… | --- ## βš™οΈ Plan highlights ### 🟩 Free (Starter) Perfect for pilots, demos, and first commercial launches. SDKs for every platform, unlimited maps, no watermarking, and instant AR Clip distribution. ### 🟦 Creator (Growth) Designed for studios, agencies, and brands. Advanced analytics, heatmaps, map merge tools, team collaboration, and AR Clip customization. ### πŸŸ₯ Enterprise (Infra) Built for infrastructure and large-scale deployments. Private VPS, 99.9% SLA, custom modules, turnkey digitisation, and navigation projects led by the AR Clip team. --- # iOS SDK Source: https://docs.arclip.design/vps/IOS Native SDK/introduction ## Overview The native iOS VPS SDK lets you localize users inside VPS maps using ARKit tracking. The XCFramework works across UIKit, RealityKit, and SwiftUI apps. Follow the Immersal-style flow below: check prerequisites, add the package, grant permissions, and initialize the service. ## Prerequisites - iOS 12.0 or newer - Xcode 12+ with Swift 5 toolchain - Device capable of running ARKit (no simulator support) ## Install Options ### Add via Swift Package Manager 1. In Xcode pick **File β†’ Add Packages…**. 2. Enter `https://github.com/WebAR-Studio/was-vps-ios.git`. 3. Select the branch or tag you need and add the package to your target. ### Manual integration 1. Clone the repository: ```bash git clone https://github.com/WebAR-Studio/was-vps-ios.git ``` 2. Drag `WASVPS.xcframework` into your project. 3. Under **General β†’ Frameworks, Libraries, and Embedded Content** make sure the framework is embedded. ## Configure Permissions Add camera and location usage descriptions to `Info.plist` so the system can display prompts. ```xml NSCameraUsageDescription This app requires access to the camera to display augmented reality content. NSLocationWhenInUseUsageDescription This app needs access to your location to place AR content accurately. NSLocationTemporaryUsageDescriptionDictionary VPSLocalization Enable precise location for accurate AR content positioning. ``` ## Initialize VPS Use `VPSBuilder.initializeVPS` to create a `VPSService`. Pass in the `ARSession`, your API key, and location IDs. > 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). ```swift import ARKit import WASVPS final class VPSDemoController: UIViewController, ARSCNViewDelegate { @IBOutlet private weak var sceneView: ARSCNView! private var configuration: ARWorldTrackingConfiguration? private var vps: VPSService? override func viewDidLoad() { super.viewDidLoad() sceneView.delegate = self sceneView.scene = SCNScene() configuration = VPSBuilder.getDefaultConfiguration() VPSBuilder.initializeVPS( arSession: sceneView.session, apiKey: "your-api-key", locationIds: ["your-location-id"], url: "https://api.arclip.app/vps/api/v3", gpsUsage: false, delegate: self ) { service in self.vps = service self.vps?.start() } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if let configuration { sceneView.session.run(configuration) } } func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { vps?.frameUpdated() } } extension VPSDemoController: VPSServiceDelegate { func positionVPS(pos: ResponseVPSPhoto) { print("Localized pose:", pos) } func error(err: NSError) { print("VPS error:", err) } func sending(requestData: UploadVPSPhoto?) { print("Sending localization request…") } } ``` Pause the service in `sessionWasInterrupted` and restart it in `sessionInterruptionEnded`. ## RealityKit Integration For RealityKit you forward frames inside `ARSessionDelegate`: ```swift func session(_ session: ARSession, didUpdate frame: ARFrame) { vps?.frameUpdated() } ``` Lifecycle calls (`start`, `stop`, `clearCustomLocPos`) match the SceneKit flow. ## SwiftUI Pattern Wrap `ARSCNView` or `ARView` in `UIViewRepresentable`/`UIViewControllerRepresentable`. Keep `VPSService` in a shared view model and toggle `start()` / `stop()` from SwiftUI controls. ## Advanced Controls - Provide a known transform before the first localization with `setCustomLocPosForFirstRequest`. - Override the default endpoint by passing a different `url` to `initializeVPS`. - Enable `gpsUsage` if you want the SDK to include device coordinates in requests. ## Troubleshooting - `VPSBuilder.getDefaultConfiguration()` returning `nil` signals an unsupported device. - If callbacks never fire, verify that `frameUpdated()` runs every frame and that your API key plus location IDs are valid. - Explore the `/examples` folder for UIKit, RealityKit, and SwiftUI reference implementations. --- # CAD / BIM / Synthetic Assets Source: https://docs.arclip.design/vps/spatial-mapping/building-map/advanced/cad-bim Convert architectural or synthetic assets into AR Clip VPS maps for construction, indoor navigation, or digital twin scenarios. ## Geometry Preparation 1. Import the CAD/BIM model into Blender, Unity, or similar tools. 2. Collapse instanced objects and remove hidden or irrelevant geometry. 3. Reduce polygon count while preserving key features (aim for ≀5β€―million triangles). 4. Generate UVs and bake lightweight textures if you plan to preview the model. ## Anchor Planning ```json [ { "name": "lobby", "position": [0.0, 0.0, 0.0] }, { "name": "elevator_north", "position": [12.4, 0.0, 28.9] } ] ``` - Coordinates are expressed in metres relative to the model origin. - Use stable identifiers (`lobby`, `loading_dock`) so SDK code can consume them directly. ## Upload with the Python REST API ```python # upload_cad.py from pathlib import Path import json import os import requests API_BASE = "https://api.arclip.app/vps/api/v3" TOKEN = os.environ["ARCLIP_TOKEN"] MODEL = Path("exports/mall.glb") ANCHORS = Path("exports/anchors.json") headers = {"Authorization": f"Bearer {TOKEN}"} payload = { "name": "Mall Digital Twin", "dataType": "mesh", "sourceFormat": "glb", "anchors": json.loads(ANCHORS.read_text()), "publish": False } init = requests.post(f"{API_BASE}/maps/import", json=payload, headers=headers) init.raise_for_status() info = init.json() map_id, upload_url = info["mapId"], info["uploadUrl"] with MODEL.open("rb") as src: put = requests.put(upload_url, data=src, headers={"Content-Type": "model/gltf-binary"}) put.raise_for_status() finalize = requests.post(f"{API_BASE}/maps/{map_id}/finalize", json={}, headers=headers) finalize.raise_for_status() print(f"CAD map {map_id} queued β†’ {finalize.json()['status']}") ``` - Anchors can be embedded in the initial payload; alternatively call `POST /maps/{id}/anchors` afterwards. - Append `coordinate` if the model is georeferenced (`{"lat": ..., "lon": ..., "alt": ...}`). ## Validation - Preview the map in arclip.design and adjust anchor offsets if required. - Test localisation with the Unity SDK; align virtual props using the same anchor IDs. - When the CAD model changes, re-run the import. The `mapId` stays stable if you call `POST /maps/{id}/update` rather than creating a new map. --- # Photogrammetry (DSLR / Mirrorless) Source: https://docs.arclip.design/vps/spatial-mapping/building-map/advanced/custom-images Use this workflow when you capture still photos with a DSLR, mirrorless camera, or drone and want to convert them into a AR Clip VPS map. ## Capture Guidelines - Aim for 70–80β€―% overlap between consecutive photos. - Shoot at multiple heights and include easily recognisable landmarks. - Keep exposure consistent; avoid motion blur. - Optional: place QR-sized ground control markers to validate scale. ## Reconstruction 1. Import the photo set into Metashape, RealityCapture, Meshroom, or your preferred photogrammetry suite. 2. Run alignment and dense reconstruction steps. 3. Export the resulting point cloud as LAS/LAZ/PLY with colour attributes. Keep texture resolution manageable (≀8K). ## Upload with the Python REST API The snippet below mirrors the flow from the arclip.design: request an upload slot, stream the LAS/PLY file, and finalise the map so that back-end processing starts. ```python # upload_photogrammetry.py from pathlib import Path import os import requests API_BASE = "https://api.arclip.app/vps/api/v3" TOKEN = os.environ["ARCLIP_TOKEN"] # create a personal token in arclip.design DATA_PATH = Path("outputs/cathedral.las") headers = {"Authorization": f"Bearer {TOKEN}"} payload = { "name": "Old Town Square", "dataType": "point_cloud", "sourceFormat": "las", "coordinate": {"lat": 50.08746, "lon": 14.42125, "alt": 194.2} } init = requests.post(f"{API_BASE}/maps/import", json=payload, headers=headers) init.raise_for_status() info = init.json() upload_url = info["uploadUrl"] map_id = info["mapId"] with DATA_PATH.open("rb") as src: put = requests.put(upload_url, data=src, headers={"Content-Type": "application/octet-stream"}) put.raise_for_status() finalize = requests.post(f"{API_BASE}/maps/{map_id}/finalize", json={"publish": False}, headers=headers) finalize.raise_for_status() print(f"Map {map_id} queued β†’ status: {finalize.json()['status']}") ``` - `ARCLIP_TOKEN` is a bearer token generated in *Console β†’ Settings β†’ API tokens*. - `dataType` / `sourceFormat` describe the payload so the back-end picks the correct pipeline. - Set `publish` to `True` if you wish to auto-publish once processing finishes; otherwise review the map in the Console first. ## Monitor Processing ```python import os import requests API_BASE = "https://api.arclip.app/vps/api/v3" TOKEN = os.environ["ARCLIP_TOKEN"] MAP_ID = "map_12345" resp = requests.get(f"{API_BASE}/maps/{MAP_ID}", headers={"Authorization": f"Bearer {TOKEN}"}) resp.raise_for_status() print(resp.json()["status"]) # e.g. PROCESSING β†’ READY β†’ PUBLISHED ``` Once the status moves to `READY`, open arclip.design β†’ *Maps* β†’ *Preview* to inspect the geometry, heatmap, and anchors. Re-run the photogrammetry step if you notice gaps or mirrored geometry. --- # Mobile Depth Supersets Source: https://docs.arclip.design/vps/spatial-mapping/building-map/advanced/depth-supersets Combine multiple AR Clip Mapper sessions to create denser indoor maps. This workflow is ideal for retail stores, campuses, and other large venues where a single pass is insufficient. ## Workflow 1. **Record several sessions** β€” cover the same area with overlapping trajectories. Use LiDAR-enabled iOS devices for maximum fidelity. 2. **Download sessions** β€” in arclip.design open *Maps β†’ Sessions*, select the recordings, and note their session IDs. 3. **Merge with the REST API** β€” trigger a server-side merge job and retrieve the merged bundle ID. ```python # merge_sessions.py import os import requests API_BASE = "https://api.arclip.app/vps/api/v3" TOKEN = os.environ["ARCLIP_TOKEN"] SESSIONS = ["sess_01h5p", "sess_01h7d", "sess_01hb2"] headers = {"Authorization": f"Bearer {TOKEN}"} payload = { "name": "Flagship Store Superset", "sessions": SESSIONS, "publish": False } resp = requests.post(f"{API_BASE}/maps/merge", json=payload, headers=headers) resp.raise_for_status() info = resp.json() map_id = info["mapId"] print(f"Merge started for map {map_id}") ``` The merge job produces a new map asset that goes through the standard processing pipeline (point cloud clean-up, quality checks). Monitor its status exactly as for a regular import. ## Tips - Keep each recording under five minutes to limit drift. - Leave visual anchors (posters, signage) that appear in multiple sessions. - Once the status becomes `READY`, open the heatmap in the Console to verify even coverage. --- # Terrestrial LiDAR Pipeline Source: https://docs.arclip.design/vps/spatial-mapping/building-map/advanced/lidar Use this pipeline when you work with static or handheld LiDAR scanners (Leica, FARO, Trimble, BLK2GO, etc.) and need to turn point clouds into AR Clip VPS maps. ## Pre-processing 1. **Clean the raw scans** β€” remove tripod artefacts, multiple returns, and moving objects in Cyclone, ReCap, CloudCompare, or similar tools. 2. **Register the stations** β€” align separate scans using targets or cloud-to-cloud methods. 3. **Colourise** β€” apply photographic textures if available; otherwise map LiDAR intensity to greyscale. ## Export - Produce a simplified mesh (≀ 5β€―million triangles) for visual preview. - Export the point cloud as E57/LAZ/PLY. - Keep the coordinate system consistent: use a local tangent plane indoors, or a geo-referenced CRS outdoors. ## Upload with the Python REST API ```python # upload_lidar.py from pathlib import Path import os import requests API_BASE = "https://api.arclip.app/vps/api/v3" TOKEN = os.environ["ARCLIP_TOKEN"] DATA_PATH = Path("outputs/plant.e57") headers = {"Authorization": f"Bearer {TOKEN}"} payload = { "name": "Factory Floor", "dataType": "point_cloud", "sourceFormat": "e57", "hybrid": True, "coordinate": {"lat": 35.68124, "lon": 139.76712} } init = requests.post(f"{API_BASE}/maps/import", json=payload, headers=headers) init.raise_for_status() info = init.json() upload_url, map_id = info["uploadUrl"], info["mapId"] with DATA_PATH.open("rb") as src: put = requests.put(upload_url, data=src, headers={"Content-Type": "application/octet-stream"}) put.raise_for_status() finalize = requests.post(f"{API_BASE}/maps/{map_id}/finalize", json={"publish": False}, headers=headers) finalize.raise_for_status() print(f"LiDAR map {map_id} queued β†’ {finalize.json()['status']}") ``` - `hybrid=True` signals that the upload contains both a point cloud and an optional mesh (if present in the E57/LAZ bundle). - For indoor-only datasets omit the coordinate block. ## Post-processing & Anchors After the map reaches `READY`: 1. Define anchor points for machinery, entries, and exits. 2. Upload them via a small JSON file: ```python import json anchors = [ {"name": "CNC-01", "position": [2.4, 0.0, 8.7]}, {"name": "safety_exit", "position": [-11.2, 0.0, 3.1]} ] resp = requests.post( f"{API_BASE}/maps/{map_id}/anchors", headers=headers, json={"items": anchors} ) resp.raise_for_status() ``` 3. Publish the map from the Console or call `POST /maps/{id}/publish` when QA is done. --- # 360Β° Panorama & Video Pipeline Source: https://docs.arclip.design/vps/spatial-mapping/building-map/advanced/panoramas-video Leverage 360Β° cameras or stabilised walkthrough videos to bootstrap AR Clip VPS maps when traditional photogrammetry is impractical. ## Extract Frames ```bash ffmpeg -i hotel-tour.mp4 -vf fps=1 tour_frames/frame_%04d.jpg ``` - Use 1–2 fps for videos. - For panoramic cameras (Insta360, Ricoh Theta) export equirectangular JPGs. ## Upload the Frame Set with Python Zip the selected frames and trigger the feature-cluster pipeline via REST. ```python # cluster_frames.py from pathlib import Path import zipfile import os import requests API_BASE = "https://api.arclip.app/vps/api/v3" TOKEN = os.environ["ARCLIP_TOKEN"] FRAMES_DIR = Path("tour_frames") ARCHIVE = Path("tour_frames.zip") with zipfile.ZipFile(ARCHIVE, "w", compression=zipfile.ZIP_DEFLATED) as zf: for frame in sorted(FRAMES_DIR.glob("*.jpg")): zf.write(frame, frame.name) headers = {"Authorization": f"Bearer {TOKEN}"} init = requests.post( f"{API_BASE}/pipelines/feature-cluster/import", json={"name": "Hotel Lobby Tour", "frameCount": len(list(FRAMES_DIR.glob('*.jpg')))}, headers=headers, ) init.raise_for_status() cluster_info = init.json() upload_url = cluster_info["uploadUrl"] cluster_id = cluster_info["clusterId"] with ARCHIVE.open("rb") as src: put = requests.put(upload_url, data=src, headers={"Content-Type": "application/zip"}) put.raise_for_status() finalize = requests.post( f"{API_BASE}/pipelines/feature-cluster/{cluster_id}/finalize", json={}, headers=headers, ) finalize.raise_for_status() print(f"Feature cluster {cluster_id} ready") ``` The API returns `clusterId`, pointing to a curated frame subset stored on the server. ## Convert the Cluster to a Map ```python import os import requests API_BASE = "https://api.arclip.app/vps/api/v3" TOKEN = os.environ["ARCLIP_TOKEN"] headers = {"Authorization": f"Bearer {TOKEN}"} payload = { "name": "Hotel Lobby Tour", "dataType": "feature_cluster", "clusterId": "cluster_abc123", "publish": False } resp = requests.post(f"{API_BASE}/maps/import", json=payload, headers=headers) resp.raise_for_status() print(resp.json()["mapId"]) ``` After processing finishes (`READY` status), open the route in the mobile or Web SDK and confirm that localisation remains stable along the tour. ## Best Practices - Maintain constant walking speed and avoid sudden rotations. - Ensure consistent lighting; re-shoot hotspots that cause blown highlights. - Capture at least one full loop so the algorithm can close the trajectory. --- # AR Clip Mapper Source: https://docs.arclip.design/vps/spatial-mapping/building-map/arclipmapper import useBaseUrl from '@docusaurus/useBaseUrl'; AR Clip is the all-in-one WebAR viewer that works **without installing an app** and doubles as the capture tool for **AR Clip VPS** maps. ## Getting started 1. Install the app from the App Store: https://apps.apple.com/us/app/ar-clip/id6742754238 *LiDAR is not required.* The Android app is available, while map capture is still in development. 2. Launch the app and tap **VPS Map** to open the capture workflow.
Selecting VPS Map mode in AR Clip
## Sign in 1. Sign in with the credentials you received on **arclip.design**. 2. Pick your **organization** and **team**.
Authentication flow in AR Clip Selecting an organization and team
## Create a new map The home screen lists every map along with its status, title, ID, and creation time.
AR Clip map list view
Tap the **plus** action at the bottom to start a new map. Grant camera access β€” a dialog displays the current **GPS** accuracy.
Starting capture and reviewing 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.
Capture interface in AR Clip
Move the device slowly while capturing. You will see **feature points**β€”their density gives a fast indication of scene texture.
Feature points during reconstruction
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.
Map context menu in AR Clip
Review reconstruction density by choosing **View Map** β†’ **PLY**.
Point cloud preview (PLY)
To validate localization, tap **Localization Test** and aim the camera at the captured area.
Localization test with point visualization
Adjust point cloud opacity and inspect logs as needed.
Visualization options and log inspector
--- # 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/)