Talking to other scripts¶
A world is usually many scripts on many objects. There are two ways for one script to drive another.
1. Typed script references (recommended)¶
Declare a field typed as the other script's class, grab it with GetComponent<T>(), then call its public methods and read/write its public fields directly:
public class PoolCue : NexusBehaviour
{
public PoolStateManager state; // a reference to another script's class
void Start()
{
// Or wire it in the inspector instead of GetComponent.
if (state == null) state = gameObject.GetComponent<PoolStateManager>();
}
void OnHit()
{
state._OnCueHit(2); // call a public method
int remaining = state.ballsLeft; // read a public field
state.ballsLeft = remaining - 1; // write a public field
}
}
And the other script just exposes public members:
public class PoolStateManager : NexusBehaviour
{
public int ballsLeft = 15;
public void _OnCueHit(int power) { /* ... */ }
}
Notes:
- Leading underscores are fine.
_OnCueHit,_StartGame, etc. are callable across scripts — the_is just a naming convention, not "private". - Engine lifecycle methods are not callable across scripts (
Start,Update,OnTriggerEnter, …) — call your own methods instead. - Get a reference by
GetComponent<TheScript>(),GetComponentInChildren<TheScript>(),GetComponentInParent<TheScript>(), or a public field you assign in the inspector. - Arrays of scripts work. With a
public PoolCue[] cues;field, bothforeach (var cue in cues) cue._Hit();andcues[0]._Hit();call across to each script. - The plural getters don't collect script types.
GetComponents<T>(),GetComponentsInChildren<T>(), andGetComponentsInParent<T>()only gather built-in Unity components — for your own script classes they returnnull, so.Lengthis0and aforeachover the result runs zero times, silently (no error, no warning). Theforeach/index examples above work because they iterate an inspector-assigned array field, not a plural getter. To loop over several scripts, use apublic PoolCue[] cues;field wired in the inspector, or grab each child individually with the singularGetComponentInChildren<T>(). - A value read from another script is loosely typed. A cross-script member read (anything other than
transform/gameObject) has compile-time typeobject, so using it directly in a relational (<,<=,>,>=) or arithmetic expression fails to compile with "Comparison operators require numeric operands" —if (other.LocalSeat >= 0)won't build. Assign it to a local of the concrete type first:int seat = other.LocalSeat; if (seat >= 0). (==and!=do compare inline for any type.) A read resolves both public fields and read-only get-only properties (public int Score { get; }) — each returns the real value.
A script reference gives you the other script's public methods and fields, and — since a script is a component — its transform and gameObject as well. So this all works:
Vector3 p = otherHand.transform.localPosition; // reach the object's transform through the ref
otherHand.gameObject.SetActive(false); // and its gameObject
From transform and gameObject you can get to anything else on that object, so those two are the door to the rest. What a reference won't expose is the other script's private internals — so keep whatever you call across scripts public. A wrong member name is a normal compile error you'll catch as you type; there's nothing subtle to chase here.
You cannot read another script's static or const — reach it through an instance
Each script is compiled on its own, so another script's static and const members do not exist from where you
are standing. Only an instance reaches across.
// ✗ refused — MaxPoints is a const on a different script
int cap = MarkerInkParticle.MaxPoints;
// ✓ expose it as an instance member and go through a reference
MarkerInkParticle particle = gameObject.GetComponent<MarkerInkParticle>();
int cap = (int)particle.GetMaxPoints();
This is now a compile error that names the line. It did not used to be: the C# editor resolves the sibling
class perfectly well, so the script looked correct, compiled, and then produced no value at all at runtime.
The empty value travelled — a method declared int handed it back — and the world failed later, somewhere else,
with a message about a null on a line that was completely innocent. If you have a constant two scripts both
need, either duplicate it or expose it as a method on one of them.
Resolve on use, not once at startup¶
A reference you grab in Start() can be null and stay null for the whole session. Scripts on the same
object initialize in no guaranteed order, and GetComponentInParent<T>() only returns a script that has already
initialized — so an early call finds nothing, the field stays empty, and every later use silently does nothing.
Resolve lazily instead, from one small helper you call at the top of each entry point:
public class MarkerTip : NexusBehaviour
{
MarkerInk ink;
void EnsureRefs()
{
if (ink == null) ink = gameObject.GetComponentInParent<MarkerInk>();
}
void OnUsed() { EnsureRefs(); if (ink != null) ink._StartStroke(); }
void OnDropped(){ EnsureRefs(); if (ink != null) ink._EndStroke(); }
}
The same rule applies to anything the world does not have yet at startup — most commonly the local player:
// ✗ the world loads BEFORE the player spawns; an empty id captured here sticks forever
void Start() { myId = Networking.LocalPlayer.GetId(); }
// ✓ ask when you actually need it
string MyId() { return Networking.LocalPlayer.GetId(); }
It behaves differently in the SDK than in the live game
In the SDK's editor testing, scripts run immediately so there is no player yet — you will see this. In the live
game your script's Start() is held until the local player exists, so the same code can appear to work there
and fail in the SDK. Resolving on use is correct in both.
2. SendMessage (fire-and-forget)¶
If you only need to trigger something (no return value), send a message by method name to every script on an object:
otherObject.SendMessage("_StartGame"); // this object's scripts
otherObject.BroadcastMessage("_Reset"); // + all children
otherObject.SendMessageUpwards("_Scored", 1); // + all parents
The receiver exposes a matching public method:
public void _StartGame() { /* ... */ }
public void _Scored(int points) { /* ... */ }
SendMessage never returns a value and calls every matching script on the target. Prefer a typed reference (option 1) when you need a return value, a field, or a single specific script.
Which to use¶
| You want to… | Use |
|---|---|
| Call a method and read the result | typed reference — state.GetScore() |
| Read or write another script's field | typed reference — state.ballsLeft |
| Just trigger an event, maybe on several scripts | SendMessage / BroadcastMessage |
→ For replicating a script's own state to other players, see Synced Variables.