s&box networking is built around a host-authoritative model using Steam’s networking stack. Objects are networked per-GameObject, and synchronization happens automatically for properties you mark with [Sync]. For one-off events you call Remote Procedure Calls (RPCs).
NetworkMode
Every GameObject has a NetworkMode that controls whether and how it is networked.
| Value | Description |
|---|
Never | The object is never networked. This is the default. |
Object | The object is networked as a single network object. It can have an owner and [Sync] properties. |
Snapshot | The object is networked as part of the scene snapshot (position/rotation only). |
Set it from the Inspector or in code:
GameObject.NetworkMode = NetworkMode.Object;
Spawning a networked object
To spawn a networked GameObject that is owned by a specific connection, call NetworkSpawn on the GameObject after enabling it:
// Host-side: spawn a prefab and assign ownership to a specific connection
var go = SceneUtility.GetPrefabScene( prefab ).Clone();
go.NetworkSpawn( connection );
Only the host can spawn networked objects. Clients must request the host to spawn objects on their behalf.
Sync properties
Mark any property on a Component with [Sync] and the engine will automatically replicate it from the owner to all other clients every network tick.
public sealed class HealthComponent : Component
{
[Sync] public float Health { get; set; } = 100f;
[Sync] public bool IsAlive { get; set; } = true;
}
[Sync] hooks into the property setter via code generation. The value is sent reliably when it changes, so you can set it as often as you like without flooding the network.
HostSync
Use [HostSync] when only the host should be able to write the value and all clients (including the owning client) should receive it read-only:
[HostSync] public int Score { get; set; }
SyncFlags
Pass SyncFlags to control sync behaviour:
// Poll the getter every tick instead of relying on set being called
[Sync( SyncFlags.Query )] public Vector3 ExternalPosition { get; set; }
RPCs
RPCs let you call a method across the network. Decorate a method with the appropriate attribute and the engine code-generates the networking plumbing.
Rpc.Broadcast
The method is called on every connected client, including the host.
[Rpc.Broadcast]
public void OnPlayerDied( string killerName )
{
// Runs on all clients
Sound.Play( "player_death" );
Log.Info( $"{killerName} killed someone" );
}
Call it normally on the owner — the engine intercepts the call and sends it over the network:
Rpc.Owner
The method is called only on the owner of the GameObject (or the host if the object is unowned).
[Rpc.Owner]
public void ReceiveAmmo( int amount )
{
// Only runs on the owning client
CurrentAmmo += amount;
}
Rpc.Host
The method is called only on the host.
[Rpc.Host]
public void RequestSpawnItem( string itemId )
{
// Only runs on the host — safe to trust for game logic
SpawnItemForPlayer( Rpc.Caller, itemId );
}
Rpc.Caller
Inside an RPC method you can check who invoked it:
[Rpc.Host]
public void TakeDamage( float amount )
{
Log.Info( $"Damage from {Rpc.Caller.DisplayName}" );
}
Filtering RPC recipients
By default Rpc.Broadcast sends to every connection. Wrap a call in a filter scope to restrict delivery.
FilterInclude
// Send only to a single connection
using ( Rpc.FilterInclude( targetConnection ) )
{
OnPlayerDied( "Alice" );
}
// Send to a set of connections
using ( Rpc.FilterInclude( teamConnections ) )
{
SendTeamMessage( "Enemy spotted!" );
}
// Send to connections that match a predicate
using ( Rpc.FilterInclude( c => c.Ping < 200 ) )
{
SendHighPriorityEvent();
}
FilterExclude
// Send to everyone except one connection
using ( Rpc.FilterExclude( localConnection ) )
{
BroadcastEvent( "something happened" );
}
// Exclude multiple connections
using ( Rpc.FilterExclude( spectatorConnections ) )
{
BroadcastGameplayEvent();
}
You cannot nest filter scopes. Attempting to set a second filter while one is already active throws an InvalidOperationException.
Ownership
A networked object can be owned by at most one Connection. The engine tracks ownership so that:
[Sync] values flow from the owner to everyone else.
Rpc.Owner is routed to the right client.
IsProxy returns true on every client that does not own the object.
// Check ownership from within a component
if ( IsProxy )
{
// We don't own this object — read only
return;
}
// Transfer ownership to another player
GameObject.Network.AssignOwnership( newOwnerConnection );
NetworkOrphaned
When the owning connection disconnects, the engine executes the object’s NetworkOrphaned policy:
| Policy | Behaviour |
|---|
Destroy | The object is destroyed. |
ClearOwner | Ownership is cleared (host takes control). |
Host | Ownership is transferred to the host connection. |
Random | Ownership is given to a random remaining connection. |
Networking state helpers
// Is this peer the host?
bool amHost = Networking.IsHost;
// Is this peer a connected client (not the host)?
bool amClient = Networking.IsClient;
// Is there an active network session?
bool sessionActive = Networking.IsActive;
Minimal networked component example
public sealed class NetworkedCounter : Component
{
// Replicated from owner to all clients
[Sync] public int Count { get; set; }
protected override void OnUpdate()
{
// Only the owner increments
if ( IsProxy ) return;
if ( Input.Pressed( "Attack1" ) )
{
Count++;
NotifyAll( Count );
}
}
[Rpc.Broadcast]
private void NotifyAll( int newCount )
{
Log.Info( $"Counter is now {newCount} (told by {Rpc.Caller.DisplayName})" );
}
}
Add NetworkObject mode
Set the GameObject’s NetworkMode to Object in the Inspector.
Mark synced properties
Add [Sync] to any property that should replicate from owner to clients.
Guard owner-only logic
Check IsProxy before writing state or calling host-side RPCs.
Use RPCs for events
Use [Rpc.Broadcast] for events all clients need to react to, and [Rpc.Host] for authoritative requests.