groupsCommunity
Multiplayer Authority Patterns
Multiplayer Authority Patterns
Community guide for structuring multiplayer code based on official networking documentation and proven patterns.
Host-Authoritative Pattern
The host/server has final say on game state:
CSHARP
public class GameManager : Component, INetworkListener
{
// Only host manages game state
[Sync] public GameState CurrentState { get; set; }
[Rpc.Host]
public void RequestGameStart()
{
// Host validates and decides
if (CanStartGame())
{
StartGameRpc();
}
}
[Rpc.Broadcast]
void StartGameRpc()
{
// All clients receive this
CurrentState = GameState.Playing;
}
}Owner-Auth with Host Validation
Clients simulate their owned objects, host validates:
CSHARP
public class PlayerController : Component
{
[Sync(SyncFlags.Interpolate)]
public Vector3 Position { get; set; }
protected override void OnUpdate()
{
// Client predicts movement
if (Network.IsOwner)
{
var input = Input.AnalogMove;
var newPos = Position + input * Speed * Time.Delta;
// Request host validation
MoveRequestRpc(newPos);
}
}
[Rpc.Host]
void MoveRequestRpc(Vector3 requestedPosition)
{
// Host validates (anti-cheat)
if (IsValidMove(requestedPosition))
{
Position = requestedPosition;
}
}
}Client-Side Prediction Pattern
Smooth gameplay despite latency:
CSHARP
public class Weapon : Component
{
void Fire()
{
// 1. Client shows effect immediately (prediction)
PlayMuzzleFlash();
PlayFireSound();
// 2. Server validates and confirms
FireRequestRpc();
}
[Rpc.Host]
void FireRequestRpc()
{
// Server checks: ammo, cooldown, line of sight
if (!CanFire()) return;
// Apply damage
var tr = Scene.Trace.Ray(Owner.AimRay, 10000).Run();
if (tr.Hit && tr.GameObject.IsValid())
{
ApplyDamageRpc(tr.GameObject.Id, Damage);
}
}
[Rpc.Broadcast]
void ApplyDamageRpc(int targetId, float damage)
{
// All clients apply damage (if they have the object)
var target = Scene.Directory.FindByIndex(targetId);
if (target.IsValid() && target.Components.TryGet<Health>(out var health))
{
health.Damage(damage);
}
}
}Spawn Ownership Transfer
Proper object lifecycle in multiplayer:
CSHARP
public class ItemSpawner : Component
{
void SpawnItem(Vector3 position)
{
// Spawn networked object
var item = new GameObject();
item.WorldPosition = position;
// Host owns spawned items by default
item.NetworkSpawn();
// Allow players to take ownership when picked up
item.Network.SetOwnerTransfer(OwnerTransfer.Takeover);
}
}
public class PickupItem : Component
{
void OnInteract(Player player)
{
// Player takes ownership
if (player.Network.IsOwner)
{
GameObject.Network.TakeOwnership();
// Parent to player
GameObject.SetParent(player.GameObject);
}
}
}Network Visibility (PVS)
Control what each player receives:
CSHARP
public class StealthPlayer : Component, INetworkVisible
{
public bool IsInvisible { get; set; }
public bool IsNetworkVisible(Connection connection)
{
// Only visible to self and host when invisible
if (!IsInvisible) return true;
return connection == Network.OwnerConnection ||
connection == Connection.Host;
}
}Common Multiplayer Structure
CSHARP
public class MultiplayerGame : Component, INetworkListener
{
// Host-only game state
[Sync(SyncFlags.FromHost)]
public float GameTime { get; set; }
// Spawned on player connect
[Property] public GameObject PlayerPrefab { get; set; }
public void OnActive(Connection connection)
{
// Host spawns player for new connection
if (Network.IsHost)
{
var player = PlayerPrefab.Clone();
player.NetworkSpawn(connection);
}
}
public void OnDisconnected(Connection connection)
{
// Cleanup player's objects
foreach (var obj in Scene.GetAllObjects(true))
{
if (obj.Network.OwnerId == connection.Id)
{
obj.Destroy();
}
}
}
}Source
- Official networking docs: https://sbox.game/dev/doc/networking
- Facepunch multiplayer examples
- Community-tested patterns from s&box Discord
Was this helpful?