Server‐authoritative networking - Development-Illustrated/the-game GitHub Wiki

What makes a Server-Authoritative game?

The core principle of our multiplayer architecture is that the server is the ultimate authority on everything that happens in the game. This means:

  • Clients (players) only send their intentions (inputs, actions they want to take)
  • The server validates these inputs and decides what actually happens
  • The server broadcasts the results back to all clients
  • Clients update their local view based on the server's decisions

This approach prevents cheating and ensures all players see a consistent game state, even if it sometimes means a slight delay between input and visible result.

Overview

Our game will be made up of several interconnected systems that span both the server and client. Here's the 10,000 foot view of how everything fits together:

  1. Player Input: Captured on the client, sent to the server for validation
  2. Server Logic: Validates input, runs physics, calculates damage, manages game state
  3. Network Synchronisation: Sends authoritative-data from server to clients
  4. Client Rendering: Updates visuals, plays effects, shows UI based on server data
  5. Client Prediction: Makes movement feel responsive despite network latency

The beauty of Netcode for GameObjects is that it abstracts away much of the networking complexity while still letting us maintain server authority.

Core Components and Their Responsibilities

Server-Side Components

These components run on the server and contain the authoritative game logic:

GameNetworkManager is our central connection point. It handles player connections, spawns player objects, and initialises our core systems. There's only one instance of this in a scene, and it's the "boss" of our network setup. It doesn't do much gameplay logic itself - it's more of a coordinator or orchestrator.

GameStateSystem tracks match-level information like scores, kill counts, and game status. It's the authority on who's winning and when the game should end. When a player reaches X kills, this system will trigger the match end sequence.

SpawnManager handles player respawning after death. It selects spawn points, manages respawn timers, and makes sure players don't spawn on top of each other.

PickupManager controls weapon spawns throughout the level, it decides when and where weapons appear, and manages their collection by players.

Network-Synchronised Components

These components exist on both server and client, but the server version is always authoritative.

PlayerNetworkController is attached to each player and handles basic movement. On the server, it validates movement requests to prevent cheating (moving through walls or faster than allowed, etc). On clients, it synchronises position and rotation.

PlayerCombatController manages weapons, shooting and damage. The server version does hit detection, damage calculation, and confirms kills. The client version shows weapon effects and sends shooting requests.

HealthManager tracks player health and handles death. The server version is the source of truth for health values, while the client version updates health bars and triggers damage effects.

ProjectileController handles bullets or other projectiles. The server calculates true trajectories and hit detection, while clients visualise them.

Client-Only Components

These components only run on the local client machine and handle user experience.

InputManager captures and processes player inputs from keyboard, mouse, or controller. It translates these inputs into network messages that get sent to the server.

LocalPlayerController handles camera control and local player-specific behaviours. It makes the game feel responsive by working with client prediction.

ClientEffectsManager handles visual and audio effects that don't require network synchronisations - not sure what needs to be in there

UIController manages all UI elements including health displays, ammo counters, kill feed, and chat. It takes information from network components and updates what the player sees.

ClientNetworkPredictor implements client-side prediction and reconciliation for smoother gameplay despite network latency. It predicts where the player will be based on inputs, then adjusts if the server disagrees.

Data Flow

Let's walk through some common gameplay scenarios and see how the data flows through:

Player Movement

  1. Client: InputManager detects WASD/Controller input
  2. Client: Immediately moves character locally (prediction)
  3. Client: Sends movement input to server via MovePlayerServerRpc()
  4. Server: Validates movement (checking for collisions, etc)
  5. Server: Updates authoritative position
  6. Server: Broadcasts position via NetworkTransform
  7. Client: Receives authoritative position
  8. Client: Reconciles any differences between predicted and actual position

This cycle will happen multiple times per second, the client prediction is what makes movement feel responsive despite network delay.

Combat and Damage

  1. Client: Player clicks to fire weapon
  2. Client: Shows any visual effects immediately (purely visual)
  3. Client: Sends fire request to server via FireWeaponServerRpc()
  4. Server: Validates shot (weapon cooldowns, ammo, etc)
  5. Server: Performs hit detection using raycasts or projectile physics
  6. Server: If hit, calculates damage and updates targe's health
  7. Server: Broadcasts hit confirmation via HitConfirmedClientRpc()
  8. Server: Updates health NetworkVariable (sync automatically, like a NetworkTransform)
  9. Client: Plays hit effects based on server confirmation
  10. Client: Updates health bar UI based on NetworkVariable change

Note that all important decisions (did the shot hit, how much damage, etc) happen on the server.

Player Death and Respawning

  1. Server: HealthManager detects health <= 0
  2. Server: Triggers player death sequence
  3. Server: Updates GameStateSystem with kill information
  4. Server: Broadcasts death via PlayerKilledClientRpc()
  5. Client: Shows death effects and UI
  6. Server: SpawnManager starts respawn timer
  7. Server: After timer, selects spawn point and respawns player
  8. Server: Updates kill feed via UpdateKillFeedClientRpc()
  9. Client: Updates kill feed UI

Game End

  1. Server: GameStateSystem detects player reached X kills
  2. Server: Sets game state to ended
  3. Server: Broadcasts game end via GameEndClientRpc()
  4. Client: Shows game end UI with results
  5. Server: After cooldown, GameNetworkManager loads next level or returns to a lobby or whatever

Communication Methods: ServerRpc and ClientRpc

Netcode for GameObjects gives us two main ways to communicate between client and server:

ServerRpc functions are called by clients but execute on the server. They're used when clients want to do something that needs validation:

  • Player movement
  • Weapon firing
  • Using items
  • Chat messages

Most ServerRpc functions return void - the result comes back later via NetworkVariables or ClientRpc.

ClientRpc functions are called by the server and execute on clients. They're used to broadcast information or trigger effects:

  • Hit confirmation
  • Kill feed updates
  • Game events
  • Chat message distribution

NetworkVariables automatically sync whenever their value changes. They're perfect for:

  • Health values
  • Ammo counts
  • Player scores
  • Game state

Events vs Direct References

For communication between components, we'll use a mix of events and direct references.

When to use events

  • UI updates from network components
  • Visual/audio effects
  • One-to-many notifications
  • When you want loose coupling between systems

When to use direct references

  • Core gameplay functions called frequently
  • Performance-critical code paths
  • When the relationship is direct and clear

For client-side to network component communication, create a ClientGameManager that servers as a bridge.