Basebound is a multiplayer gamemode with full networking support. The game runs at 50 ticks/second with client-server architecture.
- Architecture: Client-server with RPC system
- Tick Rate: 50/sec (20ms per tick)
- Max Players: 64
- Synchronization: Event-based RPC with state broadcast
// Clone a networked prefab at a location
var go = PlayerPrefab.Clone(SpawnPoint.Transform.World);
go.NetworkSpawn();Broadcasting an action to all clients:
[Rpc.Broadcast]
public void OnJump() => Log.Info($"{this} jumped!");When you don't control an object (another player's character), it's a proxy:
// Bad: Processes input for remote players
if (Input.Down("attack")) Fire();
// Good: Only process input if this client controls the object
if (!IsProxy && Input.Down("attack"))
{
Fire();
}sequenceDiagram
participant Client1
participant Server
participant Client2
Client1->>Server: Action (Jump, Build, etc)
Server->>Server: Validate
Server->>Client1: RPC Response
Server->>Client2: Broadcast Update
Client2->>Client2: Update Visual
- Client: Apply input immediately for responsiveness (optimistic update)
- Server: Validate all input before applying state changes
- Broadcast: Update all other clients with validated results
- Keep RPC methods focused and simple
- Validate parameters server-side
- Use
[Rpc.Broadcast]for events all clients should see - Consider bandwidth for frequently-called RPCs
- Use fixed timestep
OnFixedUpdate()for state that changes every tick - Broadcast critical state changes via RPC
- Minimize network traffic with delta updates
- Avoid excessive RPC calls (use bundling when possible)
- Synchronize only necessary game state
- Use proxy checks to avoid duplicate processing
- Profile network usage during development
public class EconomySystem : Component
{
protected override void OnFixedUpdate()
{
if (IsProxy) return; // Server-only
// Update economy state
UpdatePlayerBalance();
// Broadcast changes to all clients
BroadcastEconomyUpdate();
}
[Rpc.Broadcast]
public void BroadcastEconomyUpdate()
{
// All clients update their local economy view
RefreshUI();
}
}public class BaseBuilder : Component
{
[Rpc.Broadcast]
public void PlaceBlock(Vector3 position, int blockType)
{
if (!IsProxy) return; // Only execute on non-owning clients
var block = SpawnBlock(blockType, position);
// All clients now see the placed block
}
}- CONTRIBUTING.md - Networking guidelines for contributors
- architecture.md - Component lifecycle and architecture