Aggiungere il multiplayer online a un gioco non significa semplicemente inviare posizioni da un computer all'altro. Bisogna fare i conti con un vincolo fisico: i dati impiegano tempo per viaggiare, e quel tempo varia. Tutto il cosiddetto netcode esiste per nascondere questo ritardo al giocatore, mantenendo al tempo stesso la partita coerente e difficile da manipolare.
Latenza, perdita di pacchetti e jitter
Tre grandezze descrivono la qualità di una connessione:
- latenza: il tempo che un messaggio impiega per arrivare. Spesso si misura come tempo di andata e ritorno (round-trip time, RTT);
- perdita di pacchetti: la percentuale di messaggi che non arrivano affatto;
- jitter: la variazione della latenza da un pacchetto all'altro.
Anche con una connessione ottima, la distanza geografica impone un ritardo minimo: i segnali non viaggiano più veloci della luce, e nella fibra vanno sensibilmente più piano. Il netcode deve quindi funzionare bene con ritardi di decine di millisecondi e sopravvivere a ritardi maggiori.
Chi decide: il server autorevole
Nell'architettura più diffusa per i giochi d'azione, un server autorevole mantiene lo stato ufficiale della partita. I client inviano solo i propri input, come «premo avanti» o «sparo in questa direzione», e il server simula il gioco e comunica il risultato.
| Approccio | Vantaggi | Svantaggi |
|---|---|---|
| Server autorevole | Stato coerente, difficile barare | Serve un server, il client vede in ritardo |
| Peer-to-peer | Nessun server dedicato | Più facile barare, problemi con molti giocatori |
| Client autorevole | Semplice, nessun ritardo locale | Il client può dichiarare qualsiasi cosa |
Il principio di fondo è non fidarsi mai del client: se il client potesse dire «sono in questa posizione» o «ho colpito quel giocatore», modificarlo per barare sarebbe banale.
Tick rate e stati
Il server aggiorna la simulazione a una frequenza fissa, il tick rate, per esempio 30 o 60 volte al secondo, e invia ai client istantanee dello stato. Una frequenza più alta riduce il ritardo e migliora la precisione, ma aumenta il carico del server e la banda necessaria. Per ridurre i dati si inviano spesso solo le differenze rispetto all'ultimo stato confermato dal client.
Il problema del ritardo locale
Con un server autorevole puro, quando il giocatore preme avanti, l'input va al server, il server simula e invia il risultato, e solo allora il personaggio si muove sullo schermo. Con 100 ms di andata e ritorno, ogni comando ha un ritardo percepibile e il gioco sembra «molle».
Predizione lato client
La soluzione è la predizione lato client. Il client applica subito l'input al proprio personaggio, usando la stessa logica di movimento del server, senza aspettare la risposta. Il giocatore vede una risposta immediata.
Nel frattempo il client conserva una lista degli input inviati e non ancora confermati, ciascuno con un numero di sequenza.
Riconciliazione
Quando arriva lo stato dal server, rappresenta il passato: tiene conto degli input solo fino a un certo numero di sequenza. Il client esegue la riconciliazione:
- sostituisce lo stato del proprio personaggio con quello autorevole ricevuto;
- scarta gli input già confermati;
- riapplica in ordine gli input non ancora confermati.
stato = stato_dal_server
input_in_attesa.rimuovi_fino_a(ultimo_confermato)
per ogni input in input_in_attesa:
stato = simula(stato, input)
Se la predizione era corretta, il risultato coincide con ciò che il giocatore vedeva già e non si nota nulla. Se divergeva, per esempio perché un altro giocatore ha spinto il personaggio, la posizione viene corretta. Per evitare scatti evidenti, piccole correzioni si applicano in modo graduale.
Perché tutto funzioni, la simulazione deve essere il più possibile deterministica e identica tra client e server: stessi calcoli, stesso passo fisso.
Interpolazione delle altre entità
Per gli altri giocatori e i nemici la predizione è difficile, perché non si conoscono i loro input futuri. L'approccio comune è l'interpolazione delle entità: il client mostra gli altri con un piccolo ritardo deliberato, per esempio 100 ms, e interpola tra due istantanee ricevute. Il movimento appare fluido anche se gli aggiornamenti arrivano a intervalli e con jitter.
Il prezzo è che ciò che si vede degli avversari è leggermente nel passato. Per questo molti sparatutto adottano la compensazione della latenza: quando un colpo arriva, il server riporta temporaneamente le posizioni degli altri giocatori all'istante che il tiratore vedeva sul proprio schermo e verifica il colpo in quel momento. Ne nasce un compromesso noto: a volte chi viene colpito ha la sensazione di essere già al riparo.
Consigli per iniziare
- Progettare il gioco pensando al multiplayer fin dall'inizio: aggiungerlo dopo richiede quasi sempre di riscrivere la logica.
- Separare nettamente input, simulazione e rendering.
- Usare una libreria di rete o le funzioni del motore invece di scrivere da zero il protocollo.
- Provare con latenza e perdita di pacchetti simulate, non solo in rete locale, dove tutto sembra perfetto.
In sintesi
- Latenza, perdita di pacchetti e jitter sono vincoli da gestire, non da ignorare.
- Il server autorevole riceve gli input e decide lo stato ufficiale, rendendo difficile barare.
- La predizione lato client elimina il ritardo percepito sui propri comandi.
- La riconciliazione corregge le divergenze riapplicando gli input non confermati.
- Interpolazione e compensazione della latenza rendono fluidi e coerenti gli altri giocatori.