Come funziona la previsione sul lato client?


33

Ho letto Valve + Gafferon e centinaia di pagine da Google, ma per qualsiasi motivo non riesco a capire la previsione del cliente.

A mio avviso, il problema di base è:

  • Il client A invia input a T0
  • Il server riceve input a T1
  • Tutti i clienti ricevono la modifica a T2

Alla T2tuttavia, utilizzando la previsione del client, client A è ora in una posizione appropriata per T4.

In che modo si garantisce che il client A, quando prevede che il server accetterà la richiesta di movimento, non sarà in anticipo rispetto al server? Ovviamente per tutto il tempo che li aspetta, questo si traduce in un ritorno a dove il server li ha visti l'ultima volta. Con tutte le correzioni che ho provato, questo è ancora evidente quando ti fermi, perché il server si ferma dietro di te

Risposte:


35

Ho scritto una serie di articoli su questo. Si basa sulle stesse idee che hai letto altrove, ma spiegato in modo molto dettagliato e (spero) accessibile.

In particolare, l'articolo sulla previsione sul lato client è questo .


Articoli eccellenti :-) Mi piacerebbe vedere la quarta parte della serie. Come piccolo suggerimento, un collegamento alla parte successiva alla fine di ciascuno degli articoli migliorerebbe sicuramente la navigazione.
OR Mapper,

5
@ORMapper - Finalmente ho scritto il 4 ° articolo! gabrielgambetta.com/fpm4.html
ggambett

Complimenti per la tua serie di articoli :-) Molto utile, grazie :-)
OR Mapper

Tutti gli articoli (che ho potuto trovare) che parlano di ricostruire il passato usando le istantanee memorizzate prendono le riprese come esempio. Questo vale anche per il movimento? Posso immaginare che la resimulazione del movimento possa portare ad alcune grandi differenze per gli altri giocatori se possono scontrarsi. Diciamo che due giocatori si muovono l'uno contro l'altro e uno di loro smette di spostarsi a pochi "passi" dal punto di collisione. Questo comando di arresto arriva in ritardo a causa del ritardo, quindi se si resimula il mondo, i due giocatori sarebbero in posizioni molto diverse
Lope

Questa è una domanda interessante. Sfortunatamente, non ho una risposta definitiva. Immagino che dipenda da quanto siano critici i movimenti per il gioco; ti imbatti in qualcun altro e non succede nulla? In quel caso probabilmente il server non se ne cura, è visto come un errore di predizione (l'abbiamo visto tutti accadere nei punti di strozzamento, giusto?). Uccidi l'altro giocatore al contatto? In tal caso, farlo nel modo giusto è molto più importante e può valere la pena di essere resimulato. Nota che ad un certo punto devi scartare alcuni pacchetti come "troppo vecchi", altrimenti risulteresti potenzialmente da t = 0 in qualsiasi momento.
ggambett,

4

In realtà non l'ho implementato (quindi potrebbero esserci dei problemi che non vedo immediatamente), ma ho pensato di provare ad aiutare.

Ecco cosa hai detto sta succedendo:

Il client A invia input a T0

Il server riceve input su T1

Tutti i clienti ricevono la modifica in T2

In T2, tuttavia, utilizzando la previsione del client, il client A è ora in una posizione appropriata per T4.

Probabilmente sarebbe utile pensare in termini di tempo del server. È (probabilmente) molto simile a come funziona l' interpolazione .

Ogni comando viene inviato con un orario del server. Questo tempo del server viene calcolato all'inizio di una partita eseguendo una query per il tick del server, compensando il tempo di ping. Sul client hai il tuo conteggio di tick locale e ogni comando che invii viene convertito in tick di server (è una semplice operazione di sottrazione)

Inoltre, il client esegue sempre il rendering "in passato". Quindi supponi che il mondo che il client vede sia, diciamo, 100ms dietro quello che è veramente il tempo del server.

Quindi riformuliamo il tuo esempio con l'ora del server (indicata da S).

Il client invia input a T0 con l'ora del server S0 (che suppongo sia in realtà "rappresentazione del client del tempo del server meno il tempo di interpolazione"). Il client non attende la risposta dal server e si sposta immediatamente.

Il server riceve input su T1. Il server rileva la posizione autorevole del client al momento del server S0 fornita dal client. Lo invia al cliente.

Il cliente riceve la posizione autorevole su T2 (sempre con la designazione dell'ora del server S0). Il client tiene traccia della quantità di tempo passata degli eventi precedenti (probabilmente solo una coda di tutte le previsioni non confermate).

Se la posizione / velocità prevista / qualunque cosa il server rispedisca a S0 è diversa da quella che il client ha memorizzato a S0, il client lo gestisce in qualche modo. O riportando il giocatore nella posizione passata, o resimulando l'input precedente, o forse qualcos'altro a cui non ho pensato.


3
È tutto corretto tranne il bit relativo al rendering client in passato. Rispetto al server, il client esegue il rendering in futuro! Il server sa che le informazioni che ha da ogni client sono vecchie e che ogni client sarà già cambiato da allora.
Kylotan,

2

In realtà c'è un'implementazione open source in github che mostra come questo è fatto. Dai un'occhiata a Lance.gg

repository github: https://github.com/lance-gg/lance

Il codice di previsione del client è implementato nel modulo chiamato src/syncStrategies/ExtrapolateStrategy.js

Oltre all'estrapolazione, ci sono due concetti che non ho visto menzionato sopra:

  1. Flessione incrementale. Fondamentalmente piuttosto che applicare la correzione del server tutto in una volta, lasci che il delta si applichi a piccoli incrementi. In questo modo gli oggetti remoti regoleranno gradualmente le loro posizioni in modo che corrispondano alle posizioni del server. Vi è flessione della posizione, flessione della velocità, flessione dell'angolo e flessione della velocità angolare. Inoltre, potresti desiderare diversi fattori di flessione per oggetti diversi.
  2. Rievocazione del passaggio. Il fatto che i dati siano in passato significa che è possibile eseguire il rollback del tempo sul tempo dei dati del server e riavviare da quel punto. Ovviamente dovrai comunque piegarti verso la posizione appena trovata, piuttosto che saltare ad essa.

1

Il client A è sempre davanti al server, ma non importa. Devi solo riavviare il client se il server dice che si è verificato un problema con la posizione segnalata, a quel punto il client esegue nuovamente tutte le modifiche apportate dall'errore con i valori corretti, per renderlo compatibile con il server.

Per fare ciò, il client deve ricordare alcuni dei suoi stati passati e gli aggiornamenti passati. Questo può essere solo alcuni valori semplici come posizione, velocità, orientamento, quel genere di cose. Il server invierà periodicamente un riconoscimento che vari aggiornamenti client erano legittimi, il che significa che ora possono essere dimenticati dal client. Se il server segnala tuttavia che un aggiornamento non era valido, lo stato del client torna a quel punto e le modifiche future vengono applicate a quello stato modificato.

Ci sono alcuni link extra in fondo all'articolo Valve che vale la pena leggere - questo è uno di questi: https://developer.valvesoftware.com/wiki/Prediction


Così, ho ragione nel pensare che il cliente (a t=4) riceve informazioni t=2, in modo che ripristina lo stato di t=2poi ri-corre aggiornamenti per portare gli oggetti da t=2a t=4?
George Duckett,

Non lo afferro ancora per qualche motivo. Al server non viene comunicata la posizione del giocatore, ma solo gli input. Quindi il giocatore si sta spostando dall'ultima posizione in cui il server ha dichiarato di trovarsi. Viene applicato l'input. Il server è informato. Il server conferma l'input a tutti. Supponendo che tutti i comandi siano accettati, il server sarà ancora dietro il client A - quindi quando il client A si ferma, il suo carattere si fermerà immediatamente quindi tornerà alla posizione del server quando riceverà la conferma di arresto.
Chris Evans,

@GeorgeDuckett: sì (anche se non deve essere t = 4, potrebbe essere ogni volta che viene rilevata una discrepanza e potrebbero esserci un numero qualsiasi di aggiornamenti
riapplicati

@ChrisEvans: lo stato noto + le modifiche basate sull'input equivalgono comunque allo stato di invio. Per quanto riguarda l'esempio di arresto, quello in sé è un input e il server sta ancora simulando il movimento fino a quando non riceve quell'input. Supponendo una latenza costante, il server impedirà al giocatore di spostarsi esattamente nella stessa posizione che il client ha visto quando ha smesso di muoversi, perché il client era davanti al server. (Nel mondo reale, la latenza varia, quindi interpoli un po 'per appianare.)
Kylotan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.