Gotchas¶
Most C# you write here just works. This page is the short list of places it doesn't — the things that cost real time if you walk into them blind. If you're porting an existing world, read this before you start; it's mostly the stuff that generates confusing errors an hour in.
Cross-script references are loose¶
A reference to another script behaves like a normal component reference — you can call its public methods and fields, and reach the object's transform and gameObject right through it (otherHand.transform.localPosition works). The one rule to remember: only public members cross the boundary, so keep whatever you call from another script public. Bad member names are ordinary compile errors, not silent failures. Full story on Talking to other scripts.
Lock a grabbable with the interactable, not the collider¶
When you want to stop something being picked up — a cue that's already in someone's hands, a door that's sealed for now — use the interactable's own lock:
Interactable.SetLocked(true); // blocks grabbing; stays solid; every player sees it
Interactable.SetLocked(false);
Interactable.ForceDrop(); // make the current holder let go
Disabling the object's collider is the obvious-looking shortcut and it half-works, but it also switches off every other collision the object has, and it isn't networked — so other players won't be in the same state you are. SetLocked is the thing that's actually meant for this, and it replicates.
A documented call that seems to do nothing¶
NexusVM has no reflection fallback: an unrecognised call logs Unknown bridge function and returns null —
no compile error, no exception, just silence. Check the console for that line first.
- No such line? It was bound and it ran; the bug is in your surrounding logic.
- Line present? That is a missing binding — a bug on our side. Report it rather than redesigning around it: a shipped world is sandboxed, so any workaround needing first-party code cannot ship anyway.
- Neither, but nothing happens? You are probably on the wrong side.
Input.*,Controls.*andScreen.*are local to one player;HTTP.*,AI.*andPlayer.GetUsernameare server-only and no-op in editor play mode.
The networking attributes need using SocialScape.SDK;¶
NexusBehaviour is in NexusVM.Unity, but [Synced], [ServerRpc] and [ClientRpc] are in
SocialScape.SDK. A script with only the first using builds fine — right up until you add an attribute, and
then it fails with CS0246: The type or namespace name 'SyncedAttribute' could not be found. It reads like a
broken SDK and it is a missing using. Add both; see Networking.
If it still fails after adding the using, the script is probably inside a folder with its own .asmdef
(third-party assets ship these routinely). That puts it in a separate assembly where auto-referencing does not
apply — add NexusVM.Unity.Runtime and SocialScape.SDK to that asmdef's references, or move the script to a
folder with no .asmdef above it.
Networking.LocalPlayer is a struct — test it with IsValid(), never != null¶
PlayerAPI is a struct, so if (Networking.LocalPlayer != null) does not compile. Use
IsValid():
var me = Networking.LocalPlayer;
if (!me.IsValid()) return; // ✅ no player yet (offline tester, or before spawn)
// if (me != null) // ❌ does not compile — it is a struct
And do NOT guard on the id being empty — it never is
With no player, GetPlayerId() returns an all-zeros GUID
(00000000-0000-0000-0000-000000000000) — not null, not "". So the obvious guard silently passes:
string id = Networking.LocalPlayer.GetPlayerId();
if (id == "") return; // ❌ never true — falls through with a bogus id
Player.GetHeadPosition(id); // … which then reads as a valid lookup
Gate on IsValid() once and keep the result, rather than testing the id afterwards. This bites
hardest in the offline tester, where there is no networked player at all, so every id you fetch is
the zero GUID and every downstream read quietly returns a default instead of failing.
[Synced] fields must be public¶
A synced variable is public by nature — it's read on every other player's machine. Declare one private and it won't compile. If you'd rather nothing else wrote to it, just leave it public and don't write to it from elsewhere; there's no private synced variable.
Numbers behave, with two small exceptions¶
The full numeric surface works the way you'd expect — every cast, uint/ulong unsigned math, int.MaxValue / float.NaN constants, int.TryParse(s, out n), Convert.ToInt32(...), compound *=/+= (on numbers and on Color/Vector3). See Numbers, casts & parsing for the details. A few edges to know:
charprints as its number.$"{someChar}"shows the character code ("65"), not the letter — treatcharas a number.decimalis adouble. It compiles but carries no extra precision.
Some things only run live¶
The in-editor test player covers a lot — triggers, interactables, motion, your own logic all run — but anything that needs other people has nobody to talk to offline, so it quietly no-ops. That's Player.* reads of other players, voice, avatars, synced variables actually replicating, and RPCs. It's expected, not a bug; test those in a real session. Each API's page notes which calls are live-only.
Things that do work like normal C# (so just use them)¶
Worth saying, because the sandbox is less strict than people assume and second-guessing it wastes time:
- Several declarations on one line —
float h, s, v;— is fine. if (gameObject)/if (!target)truthiness works on objects (it means "not null"). Numbers, strings andVector3still need a real comparison —if (count > 0), notif (count).- TextMeshPro works, not just legacy
Text— type a fieldTextMeshProUGUI(orTMP_Text) and set.text/.fontSize/.color. MeshFilter.meshswaps a mesh at runtime;transform.eulerAngles,Color.HSVToRGB/RGBToHSV,Image.fillAmount, andCollider.enabledare all there.foreach (var x in someArray)infers the element type — even for an array of another script, soforeach (var cue in cues)over aPoolCue[]lets you callcue._Method()on each one.
When you're unsure, write it the normal Unity way first. Most of the time it compiles, and the API reference that ships with the SDK settles the rest.