Any GameObject in s&box can become a networked object. Once networked, it is replicated to all connected clients and can have synchronized properties, RPCs, and ownership.
Spawning a networked object
Call NetworkSpawn() on any GameObject to make it networked. From that point on, the object is sent to all clients.
var go = PlayerPrefab.Clone( SpawnPoint.Transform.World );
go.NetworkSpawn();
To assign a specific connection as the owner at spawn time, pass the connection:
var player = PlayerPrefab.Clone( SpawnPoint.Transform.World );
player.NetworkSpawn( connection );
Network mode
Every GameObject has a Network Mode that controls how (or whether) it is networked.
| Mode | Behaviour |
|---|
NetworkMode.Never | The object is never networked to other clients. |
NetworkMode.Object | The object is sent to other clients as its own networked object with synchronized properties and RPCs. |
NetworkMode.Snapshot (default) | The host sends the object as part of the initial scene snapshot when a client joins. |
You can change the Network Mode on a GameObject in the Inspector, or set it in code before calling NetworkSpawn().
Destroying a networked object
Destroy a networked object the same way you would any other GameObject:
The destruction is automatically replicated to all clients.
Sync properties
Add the [Sync] attribute to a property on a Component to have its value automatically sent to all clients whenever it changes.
public class MyComponent : Component
{
[Sync] public int Kills { get; set; }
}
Only the owner of the networked object can change synced properties.
Supported types
[Sync] supports unmanaged value types and string. This includes int, bool, float, Vector3, structs, and specific classes such as GameObject, Component, and GameResource.
Detecting changes
Apply [Change] alongside [Sync] to register a callback that fires when the property’s value changes:
public class MyComponent : Component
{
[Sync, Change( "OnIsRunningChanged" )] public bool IsRunning { get; set; }
private void OnIsRunningChanged( bool oldValue, bool newValue )
{
// IsRunning has changed
}
}
The [Change] callback is not invoked when a collection changes — only when the property itself is assigned a different value.
Sync flags
Customize synchronized property behaviour with SyncFlags:
| Flag | Description |
|---|
SyncFlags.Query | Checks the value for changes every network update instead of relying on the setter. Use this when the backing field can be modified without going through the property setter. |
SyncFlags.FromHost | The host owns the value regardless of who owns the object. Only the host can change it. |
SyncFlags.Interpolate | Interpolates the value over a few ticks for other clients. |
Networked collections
Use NetList<T> and NetDictionary<K,V> to synchronize collections:
public enum AmmoCount { Pistol, Rifle }
public class MyComponent : Component
{
[Sync] public NetList<int> List { get; set; } = new();
[Sync] public NetDictionary<AmmoCount, int> Dictionary { get; set; } = new();
}
NetList and NetDictionary do not currently support the [Property] attribute.
Interpolation
By default, the transform of all networked objects is smoothly interpolated for other clients.
Disabling interpolation
// Disable interpolation for this networked object
Network.DisableInterpolation();
You can also disable interpolation from the Inspector on the networked object.
Clearing interpolation
Use Network.ClearInterpolation() to instantly snap an object to its current position for everyone — useful for teleportation:
Transform.Position = Vector3.Zero;
Network.ClearInterpolation();
Refreshing a networked object
Once you call NetworkSpawn(), any further changes to the object’s components or hierarchy are not automatically networked. Only the state at spawn time is sent to other clients.
If you add new components, change component enabled states, or restructure a networked object’s hierarchy after spawning, call Network.Refresh() to push the updated structure to all clients:
By default, only the host can send refresh updates. You can allow the owner to send them too via connection permissions.
Ownership
Every networked object can have an owner — the connection responsible for simulating it (position, rotation, scale, and synced properties). If no owner is assigned, the host simulates the object.
Checking ownership
Use IsProxy to determine whether the local client should be simulating the object:
public override void Update()
{
// Controlled by another client — skip local input
if ( IsProxy ) return;
if ( Input.Pressed( "use" ) )
{
TryPickup();
}
}
Taking ownership
go.Network.TakeOwnership();
Dropping ownership
Carrying.Network.DropOwnership();
Transferring ownership
By default, only the host can change a networked object’s owner. Change this with SetOwnerTransfer:
// Allow anyone to take ownership
go.Network.SetOwnerTransfer( OwnerTransfer.Takeover );
| Type | Behaviour |
|---|
OwnerTransfer.Fixed (default) | Only the host can change the owner. |
OwnerTransfer.Takeover | Any client can take ownership. |
OwnerTransfer.Request | A client must request ownership from the host. |
Orphaned objects on disconnect
When an owner disconnects, all objects they own are destroyed by default. Change this with SetOrphanedMode:
go.Network.SetOrphanedMode( NetworkOrphaned.Host );
| Type | Behaviour |
|---|
NetworkOrphaned.Destroy (default) | Destroy the object when the owner disconnects. |
NetworkOrphaned.Host | Assign ownership to the host when the owner disconnects. |
NetworkOrphaned.Random | Assign ownership to a random client when the owner disconnects. |
NetworkOrphaned.ClearOwner | Keep the object but clear ownership (host simulates it). |
Network visibility
By default, all networked objects transmit to all connected clients. For larger games, you can cull objects to reduce bandwidth.
Always Transmit
Every networked object has an Always Transmit flag (default: true). When true, the object is never culled and is visible to every player.
Custom visibility
To control visibility per connection, attach a component that implements INetworkVisible to the root GameObject of the networked object, then disable Always Transmit:
public class MyVisibilityComponent : Component, INetworkVisible
{
public bool IsVisibleToConnection( Connection connection, in BBox worldBounds )
{
// Return true to send updates; false to cull
return true;
}
}
| Parameter | Description |
|---|
Connection connection | The target client being tested. |
BBox worldBounds | The object’s world-space bounding box. Useful for distance or frustum checks. |
Only the owner of a networked object decides its visibility for each connection.
Hammer PVS
If no INetworkVisible component is present and the map is a Hammer map with VIS compiled, the engine falls back to PVS (Potentially Visible Set) automatically.
What happens when an object is culled
When an object is culled for a connection:
- Sync var updates and transform updates stop being sent to that client.
- The object remains spawned on the client but is disabled locally.
- RPCs are still delivered to that client.
Custom snapshot data
When a client joins, they receive a snapshot of the current scene state. You can write additional data into this snapshot by implementing Component.INetworkSnapshot on a component.
Writing snapshot data
private byte[] MyVoxelData { get; set; }
void INetworkSnapshot.WriteSnapshot( ref ByteStream writer )
{
writer.Write( MyVoxelData.Length );
writer.WriteArray( MyVoxelData );
}
Reading snapshot data
void INetworkSnapshot.ReadSnapshot( ref ByteStream reader )
{
var length = reader.Read<int>();
MyVoxelData = reader.ReadArray<byte>( length ).ToArray();
}
protected override async Task OnLoad()
{
await LoadVoxelWorld( MyVoxelData );
}
private async Task LoadVoxelWorld( byte[] data )
{
// parse and build the voxel world from data
}
Using async Task OnLoad() causes the loading screen to wait until the task completes before letting the player in.