Interactables API¶
Read and control interactables — grabbables, levers, doors and buttons — from a script. Ask whether something is being held and by whom, read a lever or door's position as a 0..1 value, lock an object so nobody can grab it, or force whoever's holding it to let go.
There are two ways in:
Interactable.*acts on the object this script is attached to — its own interactable. No id needed. This is what you use most.Interactables.*acts on any interactable, by its network id — pass the object's id as the first argument. Use it when one object's script needs to read or control a different one.
Both expose the same set of calls.
Reading state¶
These reads work anywhere — server logic, client scripts, or the play-mode test harness.
| Own object | Any object | Returns |
|---|---|---|
Interactable.GetValue() |
Interactables.GetValue(objectId) |
A Constrained handle's position as a float 0..1 — a HingeJoint's angle across its limits (lever/knob/door/hammer) or a linear ConfigurableJoint's travel across its Linear Limit (slider/valve/gun slide/pump). A grab or button returns 0. |
Interactable.IsHeld() |
Interactables.IsHeld(objectId) |
true while a player is holding it. |
Interactable.GetHolder() |
Interactables.GetHolder(objectId) |
The holder's player id — the same id Player API uses — or "" when nobody holds it. |
Interactable.GetGrabCount() |
Interactables.GetGrabCount(objectId) |
How many grips are on it: 0, 1, or 2. 2 is a VR two-handed grab — an object held with both hands (a pool cue, a two-handed rifle). |
Interactable.GetAttachPoint() |
Interactables.GetAttachPoint(objectId) |
The body anchor it's attached to as a name ("Hip", "RightHand", …), or "" when it isn't attached. See Attaching to a player. |
// A lever's own script: open a door once the lever is pulled most of the way.
public class DoorLever : NexusBehaviour
{
public void Update()
{
if (Interactable.GetValue() > 0.9f)
Debug.Log("Lever pulled — open the door");
}
}
Reading a sliding two-handed grip (pool cue)
GetValue() is Constrained-only — it returns 0 for a Grab or Press, so there is no draw-distance / stroke
call for a two-handed sliding grip (a pool cue, a rifle's charging pull). Read the stroke geometrically from the
object's own transform instead: the object slides in world space and that motion replicates to everyone, so a
distance you measure off its position (e.g. cue tip → cue ball) is consistent for all players. Use GetGrabCount() == 2
to know both hands are on it.
GetHolder returns a Player-API id
GetHolder() returns the holder's id in the same form every Player.* call uses, so you can feed it straight in — check whether you're the holder, get their name, find them:
string holder = Interactable.GetHolder();
if (holder != "" && Player.IsLocal(holder))
Debug.Log("I'm the one holding this");
// string name = Player.GetUsername(holder); // or read anything else about them
Controlling an object¶
Locking and force-dropping change shared state, so they're authority-gated: they take effect on the peer that currently owns the object (the server while it rests, the holder while it's held) and replicate to everyone. Calling them from a script on the object works for the common cases (a resting door, an object the caller holds); routing a control call from an unrelated player is a later addition.
| Own object | Any object | What it does |
|---|---|---|
Interactable.Lock() |
Interactables.Lock(objectId) |
Block grabbing — the object can't be picked up. A player already holding it keeps it until they drop it. |
Interactable.SetLocked(locked) |
Interactables.SetLocked(objectId, locked) |
Lock (true) or unlock (false). |
Interactable.ForceDrop() |
Interactables.ForceDrop(objectId) |
Make the current holder let go. |
// A trap plate that locks a key in place until a puzzle is solved.
public class KeyHolder : NexusBehaviour
{
public void OnStart() => Interactable.Lock(); // can't be taken yet
public void PuzzleSolved() => Interactable.SetLocked(false); // now grabbable
}
Attaching to a player¶
Snap a grabbable to a spot on the holder's body — a sword to the hip, a torch to a hand, a backpack to the back — so it rides that spot as they move. This is how you build holsters: the item stops being carried and sticks to the body until it's taken again.
Interactable.AttachTo(anchor) pins the object the script is on to the anchor named by anchor, on the player currently
holding it. Interactable.Detach() puts it back into the world. The anchor is one of these curated names — the hip is
the steadiest and the best default:
"Hip" · "RightHand" · "LeftHand" · "Back" · "Head" · "LeftFoot" · "RightFoot"
Like lock and force-drop, attach is authority-gated and synced: the holder issues it and every player sees the item glued to the same body spot (each player's own view resolves it on the copy of that avatar they see, so it stays right even when players look different to each other). It's meaningful only for a grabbable the caller is holding — call it from that object's own script, on the holder's side.
| Own object | Any object | What it does |
|---|---|---|
Interactable.AttachTo(anchor) |
Interactables.AttachTo(objectId, anchor) |
Stop carrying and pin the item to the holder's anchor bone (no throw). Unknown name → nothing happens. |
Interactable.Detach() |
Interactables.Detach(objectId) |
Unpin it back into the world (it drops from where it was attached). |
// A holsterable sword: press "use" while holding it to holster it on your hip; grab it off your hip to draw it again.
public class Holsterable : NexusBehaviour
{
public void OnUsed() => Interactable.AttachTo("Hip"); // fires on the holder → holsters to THEIR hip, synced to all
public void OnAttached() => Debug.Log("Sheathed"); // play a sheath sound / effect here
public void OnDetached() => Debug.Log("Drawn");
}
Grabbing an attached item automatically detaches it (you just pick it back up), so a holster needs no "un-holster" button —
drawing it is grabbing it. Reading Interactable.GetAttachPoint() tells you where it currently sits ("" if it's free).
Synced tablets & control surfaces
Because attach is server-authoritative, you can drive it from an in-world control — e.g. a button on a tablet that
holsters everyone's tool at once — the same way the AudioLink tablet drives synced tuning. Wire the button
to a script that calls Interactables.AttachTo(id, "Hip") for each object; every player converges on the same result.
Reacting to interaction¶
The engine calls these methods by name on your NexusBehaviour when the matching thing happens to the object your script is on — there's no wiring or subscription. Declare only the ones you want.
| Callback | When it fires |
|---|---|
OnGrabbed() |
The object is picked up. |
OnDropped() |
The object is released (a normal drop, or when it's taken from you). |
OnUsed() |
A Press button, or a grabbable's "use", is triggered. Fires on PRESS. |
OnUseDown() · OnUseUp() |
The use input going down and coming up — the pair that makes a held action authorable (draw a bow, charge an attack, hold a horn). Desktop left-click and the VR trigger/button both drive them. |
OnHoverEnter() · OnHoverExit() |
The reticle / controller started or stopped targeting this object. |
OnValueChanged(value) |
A Constrained handle moves — value is its new 0..1 position (a lever, a valve, a gun slide). |
OnValueMax() |
The handle reached the open / racked end (crossed above ~0.85). |
OnValueMin() |
The handle returned to rest (crossed below ~0.15). |
OnRackCycle() |
One full pull‑and‑return completed — e.g. pump the slide → chamber a round. |
OnAttached() |
The object was pinned to a body anchor (see Attaching to a player). |
OnDetached() |
The object was unpinned from a body anchor (including when it's grabbed back off). |
Script callbacks vs. the no-code Events dropdown — two different name sets
The reserved script methods above (OnUsed, OnGrabbed, OnDropped) are not spelled the same as the
no-code Event triggers you pick in the inspector's Events list (On use, On pickup, On drop, On hover
enter/exit). A NexusBehaviour method the engine auto-invokes must use the callback name from the table —
OnUsed(), not OnUse(); OnGrabbed(), not OnPickup(); OnDropped(), not OnDrop(). Name it the
short (trigger) way and it simply never fires. The _-prefixed convention is fine either way — only the base name
has to match.
public class Valve : NexusBehaviour
{
public void OnValueChanged(float value)
{
// value is 0..1 as the wheel turns — drive a flow rate, a gauge, a sound pitch…
Debug.Log("Valve at " + (value * 100f) + "%");
}
}
Pump‑action reload — a slide/pump on a ConfigurableJoint (Constrained). Rack it to chamber a round, and gate firing on it:
public class PumpShotgun : NexusBehaviour
{
private bool chambered = false;
public void OnRackCycle() { chambered = true; } // pulled the pump all the way and back → round chambered
// Called from your Fire event (Held input → NexusScript method), or from OnUsed().
public void Fire()
{
if (!chambered) return; // nothing chambered — a dry click
chambered = false;
Debug.Log("Bang"); // spawn the projectile / play the shot (a Networked event effect)
}
}
Callbacks vs. the inspector Events list
These callbacks are the scripting shortcut. You can also react with no scripting using the interactable's On pickup / On drop / On use events (and their effects) in the inspector — see Interactables. Both work on the same object; use whichever fits. For a reaction every player must see (a door swinging open for the whole room), drive it from a Networked event effect rather than a per-player callback.
Testing offline¶
In the offline SDK tester, the Interactable.* self calls run fully against your test grab — GetValue, IsHeld, GetHolder, GetGrabCount, Lock/SetLocked and ForceDrop all behave, and the OnGrabbed / OnDropped / OnUsed / OnValueChanged / OnValueMax / OnValueMin / OnRackCycle callbacks fire — so you can prototype interactable logic (including a pump‑to‑reload gun) before going live. The Interactables.*(objectId) global calls resolve an object by its multiplayer network id, which only exists in a live session; offline they return the safe defaults (0 / false / ""). Attaching to a player (AttachTo / Detach / GetAttachPoint) also needs a live session — a networked player and their avatar bones — so offline it's a safe no-op (GetAttachPoint returns ""); test holsters in a live world. Test a single interactable's logic with the self calls on its own script.
Quick reference¶
| Group | Calls |
|---|---|
| Read (own object) | Interactable.GetValue · IsHeld · GetHolder · GetGrabCount · GetAttachPoint |
| Control (own object) | Interactable.Lock · SetLocked · ForceDrop · AttachTo · Detach |
| Read / control (any object by id) | Interactables.GetValue · IsHeld · GetHolder · GetGrabCount · GetAttachPoint · Lock · SetLocked · ForceDrop · AttachTo · Detach |
| Callbacks | OnGrabbed() · OnDropped() · OnUsed() · OnValueChanged(value) · OnValueMax() · OnValueMin() · OnRackCycle() · OnAttached() · OnDetached() |
→ See the Interactables feature page for the no-code component (Grab / Constrained / Press, events and networking), and Player API for reading and comparing player ids.