Previsione del movimento per i non tiratori


35

Sto lavorando a un gioco isometrico 2D con multiplayer su scala moderata, circa 20-30 giocatori collegati contemporaneamente a un server persistente. Ho avuto qualche difficoltà ad attuare una buona implementazione della previsione del movimento.

Fisica / Movimento

Il gioco non ha una vera implementazione fisica, ma utilizza i principi di base per implementare il movimento. Anziché eseguire continuamente il polling dell'input, i cambiamenti di stato (ad es. / Eventi del mouse giù / su / sposta) vengono usati per cambiare lo stato dell'entità personaggio che il giocatore controlla. La direzione del giocatore (cioè / nord-est) è combinata con una velocità costante e trasformata in un vero vettore 3D - la velocità dell'entità.

Nel loop di gioco principale, "Update" viene chiamato prima di "Draw". La logica di aggiornamento attiva un "task di aggiornamento della fisica" che tiene traccia di tutte le entità con una velocità diversa da zero e utilizza un'integrazione molto semplice per modificare la posizione delle entità. Ad esempio: entity.Position + = entity.Velocity.Scale (ElapsedTime.Seconds) (dove "Seconds" è un valore in virgola mobile, ma lo stesso approccio funzionerebbe per valori interi in millisecondi).

Il punto chiave è che nessuna interpolazione viene utilizzata per il movimento: il motore fisico rudimentale non ha il concetto di "stato precedente" o "stato attuale", ma solo una posizione e una velocità.

Pacchetti di modifica e aggiornamento dello stato

Quando la velocità dell'entità personaggio controllata dal giocatore cambia, un pacchetto "sposta avatar" viene inviato al server contenente il tipo di azione dell'entità (stare in piedi, camminare, correre), la direzione (nord-est) e la posizione corrente. Questo è diverso da come funzionano i giochi 3D in prima persona. In un gioco 3D la velocità (direzione) può cambiare fotogramma in fotogramma mentre il giocatore si muove. L'invio di ogni cambio di stato trasmetterebbe effettivamente un pacchetto per frame, che sarebbe troppo costoso. Invece, i giochi 3D sembrano ignorare i cambiamenti di stato e inviare pacchetti di "aggiornamento dello stato" a intervalli fissi, ad esempio ogni 80-150 ms.

Poiché gli aggiornamenti di velocità e direzione si verificano molto meno frequentemente nel mio gioco, posso evitare di inviare ogni cambio di stato. Sebbene tutte le simulazioni fisiche si verifichino alla stessa velocità e siano deterministiche, la latenza è ancora un problema. Per questo motivo, invio pacchetti di aggiornamento di posizione di routine (simile a un gioco 3D) ma molto meno frequentemente - in questo momento ogni 250 ms, ma sospetto che con una buona previsione posso facilmente aumentarlo verso i 500 ms. Il problema più grande è che ora mi sono allontanato dalla norma: tutta la documentazione, le guide e i campioni online inviano aggiornamenti di routine e interpolano tra i due stati. Sembra incompatibile con la mia architettura e ho bisogno di elaborare un algoritmo di previsione del movimento più vicino a un'architettura (di base) di "fisica in rete".

Il server quindi riceve il pacchetto e determina la velocità del giocatore dal suo tipo di movimento in base a uno script (il giocatore è in grado di correre? Ottieni la velocità di corsa del giocatore). Una volta che ha la velocità, la combina con la direzione per ottenere un vettore: la velocità dell'entità. Si verifica un rilevamento dei cheat e una convalida di base e l'entità sul lato server viene aggiornata con la velocità, la direzione e la posizione correnti. La limitazione di base viene inoltre eseguita per impedire ai giocatori di inondare il server con richieste di movimento.

Dopo aver aggiornato la propria entità, il server trasmette un pacchetto di "aggiornamento della posizione dell'avatar" a tutti gli altri giocatori nel raggio d'azione. Il pacchetto di aggiornamento della posizione viene utilizzato per aggiornare le simulazioni di fisica lato client (stato mondiale) dei client remoti ed eseguire la previsione e la compensazione del ritardo.

Previsione e compensazione del ritardo

Come accennato in precedenza, i clienti sono autorevoli per la propria posizione. Salvo casi di imbrogli o anomalie, l'avatar del client non verrà mai riposizionato dal server. Non è richiesta alcuna estrapolazione ("sposta ora e correggi dopo") per l'avatar del cliente - ciò che il giocatore vede è corretto. Tuttavia, è necessaria una sorta di estrapolazione o interpolazione per tutte le entità remote in movimento. Una sorta di previsione e / o compensazione del ritardo è chiaramente richiesta nel motore di simulazione / fisica locale del cliente.

I problemi

Ho lottato con vari algoritmi e ho una serie di domande e problemi:

  1. Dovrei estrapolare, interpolare o entrambi? La mia "sensazione viscerale" è che dovrei usare la pura estrapolazione basata sulla velocità. Il cambiamento di stato viene ricevuto dal client, il client calcola una velocità "prevista" che compensa il ritardo e il normale sistema fisico fa il resto. Tuttavia, sembra in contrasto con tutti gli altri codici e articoli di esempio: sembrano tutti memorizzare un certo numero di stati ed eseguire l'interpolazione senza un motore fisico.

  2. Quando arriva un pacchetto, ho provato a interpolare la posizione del pacchetto con la velocità del pacchetto per un periodo di tempo fisso (diciamo 200 ms). Prendo quindi la differenza tra la posizione interpolata e l'attuale posizione di "errore" per calcolare un nuovo vettore e posizionarlo sull'entità invece della velocità che è stata inviata. Tuttavia, il presupposto è che un altro pacchetto arriverà in quell'intervallo di tempo, ed è incredibilmente difficile "indovinare" quando arriverà il pacchetto successivo, specialmente dal momento che non arrivano tutti a intervalli fissi (cioè anche i cambiamenti di stato). Il concetto è fondamentalmente imperfetto o è corretto ma necessita di alcune correzioni / regolazioni?

  3. Cosa succede quando un lettore remoto si ferma? Posso immediatamente fermare l'entità, ma sarà posizionata nel punto "sbagliato" fino a quando non si sposta di nuovo. Se stimo un vettore o provo a interpolare, ho un problema perché non memorizzo lo stato precedente: il motore fisico non ha modo di dire "devi fermarti dopo aver raggiunto la posizione X". Capisce semplicemente una velocità, niente di più complesso. Sono riluttante ad aggiungere le informazioni sullo "stato di movimento dei pacchetti" alle entità o al motore fisico, poiché violano i principi di progettazione di base e diffondono il codice di rete in tutto il resto del motore di gioco.

  4. Cosa dovrebbe accadere quando le entità si scontrano? Esistono tre scenari: il giocatore di controllo si scontra localmente, due entità si scontrano sul server durante un aggiornamento di posizione o un aggiornamento di entità remota si scontra sul client locale. In tutti i casi non sono sicuro di come gestire la collisione - a parte barare, entrambi gli stati sono "corretti" ma in periodi di tempo diversi. Nel caso di un'entità remota non ha senso disegnarlo mentre cammina attraverso un muro, quindi eseguo il rilevamento delle collisioni sul client locale e lo faccio "arrestare". Sulla base del punto 2 sopra, potrei calcolare un "vettore corretto" che tenta continuamente di spostare l'entità "attraverso il muro" che non avrà mai successo - l'avatar remoto è bloccato lì fino a quando l'errore non diventa troppo alto e "scatta" in posizione. Come funzionano i giochi attorno a questo?


1
Cosa c'entra un gioco in 3D o 2D con quale tipo di server usi? e perché un server athoritive non funziona per il tuo gioco?
Attaccando

1
@Roy T. compromessi della larghezza di banda. La larghezza di banda è la risorsa più preziosa nei sistemi informatici di oggi.
FxIII,

1
È falso, i giochi online sono in gran parte dominati dal tempo di risposta, ad esempio su una linea da 10 Mb (1,25 MB / s) la latenza tra server-client è di 20 ms, l'invio di un pacchetto da 1,25 KB richiederà 20 ms + 1 ms. L'invio di un pacchetto da 12,5 KB richiederà 30 ms. Su una linea due volte più veloce, un pacchetto da 1,25kb richiederà comunque 20ms + 0,5ms e 20ms + 5ms per il pacchetto da 12.kb. La latenza è il fattore limitante, non la larghezza di banda. Comunque, non so quanti dati ci siano, ma l'invio di 50 vector3 (posizione 25x + rotazione 25x) è di soli 600 byte, l'invio di questo ogni 20 ms avrà un costo di 30kb / s. (+ pacchetto overhead).
Roy T.

2
Il motore di Quake ha una previsione sin dalla prima versione. La previsione del terremoto è descritta lì e in alcuni altri luoghi. Controlla.
user712092,

1
Fai questa posizione + = velocità * deltatime per ogni entità in parallelo (imperativamente: nel codice hai 2 matrici di parametri fisici di entità, a un frame a parte, aggiorni quella più vecchia per essere più nuova e scambiale)? Ci sono alcuni problemi con l'iterazione di Sean Barret, che ha creato la base del motore di Thief 1 .
user712092,

Risposte:


3

L'unica cosa da dire è che 2D, isometrico, 3D, sono tutti uguali quando si tratta di questo problema. Dato che vedi molti esempi di 3D e stai usando solo un sistema di input 2D a ottanta limitato con velocità istantanea non significa che puoi buttare via i principi di rete che si sono evoluti negli ultimi 20 anni.

I principi del design devono essere dannati quando il gioco è compromesso!

Eliminando quelli precedenti e quelli attuali, scarti le poche informazioni che potrebbero risolvere il tuo problema. A quei dati aggiungerei timestamp e ritardo calcolato in modo che l'estrapolazione possa prevedere meglio dove si troverà quel giocatore e l'interpolazione può appianare meglio le variazioni di velocità nel tempo.

Quanto sopra è un grande motivo per cui i server sembrano inviare molte informazioni sullo stato e non controllare gli input. Un altro grande motivo è basato sul protocollo in uso. UDP con perdita di pacchetti accettata e consegna fuori ordine? TCP con consegna garantita e nuovi tentativi? Con qualsiasi protocollo otterrai pacchetti in tempi strani, ritardati o impilati uno sopra l'altro in una raffica di attività. Tutti quei pacchetti strani devono adattarsi a un contesto in modo che il client possa capire cosa sta succedendo.

Infine, anche se i tuoi input sono molto limitati a 8 direzioni, l'effettivo cambiamento può avvenire in qualsiasi momento - l'applicazione di un ciclo di 250ms frustrerà i giocatori veloci. 30 giocatori non sono grandi per qualsiasi server. Se stai parlando di migliaia ... anche allora gruppi di loro sono divisi in più boxen, quindi i singoli server stanno solo caricando un carico ragionevole.

Hai mai profilato un motore fisico come Havok o Bullet in esecuzione? Sono davvero abbastanza ottimizzati e molto, molto veloci. Potresti cadere nella trappola dell'ipotesi che l'operazione ABC sia lenta e che ottimizzi qualcosa che non ne ha bisogno.


Saggi consigli qui! È facile perdere di vista il quadro generale. Sto usando TCP in questo caso. Il problema delle "8 direzioni" non è tanto un problema in termini di input, ma piuttosto un problema di interpolazione ed estrapolazione. La grafica è limitata a quegli angoli e usa gli sprite animati: il gameplay "sembra strano" se il giocatore si muove in un angolo o velocità diversa che è troppo lontano dalla norma.
ShadowChaser

1

Quindi il tuo server è essenzialmente un "arbitro"? In questo caso, credo che tutto nel tuo cliente debba essere deterministico; devi assicurarti che tutto su ogni cliente dia sempre lo stesso risultato.

Per la tua prima domanda, una volta che il giocatore locale riceve la direzione degli altri giocatori, oltre a essere in grado di decellerare il suo movimento nel tempo e applicare collisioni, non vedo come potresti prevedere in quale direzione il prossimo turno del giocatore, specialmente in un Ambiente a 8 direzioni.

Quando ricevi l'aggiornamento della "posizione reale" di ciascun giocatore (che potresti provare a scaglionare sul server) sì, dovrai interpolare la posizione e la direzione del giocatore. Se la posizione "indovinata" è molto sbagliata (cioè il giocatore ha cambiato completamente direzione subito dopo che è stato inviato l'ultimo pacchetto di direzione) avrai un enorme divario. Ciò significa che il giocatore salta la posizione oppure è possibile interpolare alla successiva posizione indovinata . Ciò fornirà un'interpolazione più fluida nel tempo.

Quando le entità si scontrano, se riesci a creare un sistema deterministico, ogni giocatore può simulare la collisione localmente e i loro risultati non dovrebbero essere troppo lontani dalla realtà. Ogni macchina locale dovrebbe simulare la collisione per entrambi i giocatori, nel qual caso assicurarsi che lo stato finale sia non bloccante e accettabile.

Per quanto riguarda il lato server, un server arbitro può ancora eseguire semplici calcoli per verificare, ad esempio, la velocità di un giocatore in tempi brevi da utilizzare come un semplice meccanismo anti-cheat. Se esegui un ciclo continuo di monitoraggio di ciascun giocatore per 1 secondo alla volta, il rilevamento dei cheat sarà scalabile, solo ci vorrà più tempo per trovare gli imbroglioni.


Grazie - sembra abbastanza vicino a ciò di cui ho bisogno, soprattutto sul lato server. Un punto interessante è che sebbene i giocatori siano bloccati in 8 direzioni, il movimento interno è un vettore 3D. Ci ho pensato un po 'di più in passato e penso che sto lottando per il fatto che non ho implementato affatto l'interpolazione: utilizzo semplicemente un'integrazione molto semplice, impostando la velocità e aggiornando la posizione in base al vector ogni aggiornamento.
ShadowChaser,

Non sono sicuro di come combinarlo con l'interpolazione o la previsione. Ho provato a prendere la posizione aggiornata inviata nel pacchetto, integrandolo in un periodo di tempo fisso (diciamo 200 ms) e quindi determinando il vettore (velocità) necessario per raggiungere quel punto in 200 ms. In altre parole, indipendentemente dall'attuale posizione errata del giocatore sul lato client, dovrebbero comunque raggiungere la stessa "posizione corretta stimata" in 200 ms. Ha finito per inviare il mio personaggio in direzioni folli - presumo perché i 200ms dovrebbero davvero essere il momento del pacchetto successivo, che non posso stimare.
ShadowChaser,

Ti sei assicurato di integrare prima la posizione giusta da t a t + 1 prima di integrare la posizione sbagliata nella posizione giusta indovinata da t + 1?
Jonathan Connell,

Sì, ho ricontrollato che stavo usando la posizione corretta per l'integrazione originale. Inizialmente si trattava di un bug, ma risolverlo non sembrava creare un notevole miglioramento. Il mio sospetto è il "+1" - deve essere molto dipendente dal tempo tra i pacchetti. Esistono due problemi: inviare cambiamenti di stato oltre agli aggiornamenti regolari (250 ms) e non riesco a prevedere quando si verificheranno. Inoltre, sono riluttante a bloccare in un intervallo specifico poiché ha senso che il server invii meno aggiornamenti per entità più lontane dal lettore. Il tempo tra i pacchetti può cambiare.
ShadowChaser,

1
Sì, includere un tipo di timestep fisso probabilmente non è una buona idea. Sono preoccupato, tuttavia, che l'irregolarità del movimento in 8 direzioni sarà molto difficile (se non impossibile?) Da prevedere. Anche così, si può essere in grado di provare a utilizzare la latenza media del cliente di prevedere t + 1, e hanno una soglia oltre la quale si sempre "teleport" gli altri giocatori nelle loro nuove posizioni.
Jonathan Connell,

0

Non puoi includere la velocità nei tuoi messaggi di cambio di stato e utilizzarla per prevedere il movimento? ad esempio, supponi che la velocità non cambi fino a quando non ricevi un messaggio che dice che è cambiato? Penso che tu stia già inviando posizioni, quindi se qualcosa "supera" a causa di ciò hai comunque la posizione corretta dal prossimo aggiornamento. Quindi è possibile scorrere le posizioni durante gli aggiornamenti come già si fa utilizzando la velocità dell'ultimo messaggio e sovrascrivendo la posizione ogni volta che si riceve un messaggio con una nuova posizione. Ciò significa anche che se la posizione non cambia, ma la velocità è necessario per inviare un messaggio (se questo è anche un caso valido nel tuo gioco), ma ciò non influirà molto sull'utilizzo della larghezza di banda, se non del tutto.

L'interpolazione non dovrebbe importare qui, ad esempio quando sai dove si troverà qualcosa in futuro, se ce l'hai, quale metodo stai usando ecc. Sei forse confuso con l'estrapolazione? (per cui quello che descrivo è uno, semplice, approccio)


-1

Le mie prime domande sarebbero: Cosa c'è di sbagliato nell'usare un modello in cui il server ha autorità? Perché è importante se l'ambiente è 2D o 3D? Renderebbe la tua protezione cheat molto più semplice se il tuo server fosse autorevole.

La maggior parte dei campioni che ho visto hanno strettamente associato la previsione del movimento direttamente nelle entità stesse. Ad esempio, memorizzando lo stato precedente insieme allo stato corrente. Vorrei evitarlo e mantenere solo le entità con il loro "stato attuale". C'è un modo migliore per gestirlo?

Quando si esegue la previsione, è necessario mantenere diversi stati (o almeno delta) sul client in modo che quando lo stato / delta autorevole viene ricevuto dal server, possa essere confrontato con quelli del client e sia possibile rendere necessario correzioni. L'idea è quella di mantenere il più possibile deterministico per ridurre al minimo la quantità di correzioni richieste. Se non mantieni gli stati precedenti, non puoi sapere se sul server è successo qualcosa di diverso.

Cosa dovrebbe succedere quando il giocatore si ferma? Non posso interpolare nella posizione corretta, poiché potrebbero aver bisogno di camminare all'indietro o in un'altra strana direzione se la loro posizione è troppo avanti.

Perché hai bisogno di interpolare? Il server autorevole dovrebbe sovrascrivere eventuali movimenti errati.

Cosa dovrebbe accadere quando le entità si scontrano? Se il giocatore attuale si scontra con qualcosa, la risposta è semplice: basta che il giocatore si muova. Ma cosa succede se due entità occupano lo stesso spazio sul server? Cosa succede se la previsione locale fa collidere un'entità remota con il giocatore o un'altra entità? Se la previsione ha avuto la sfortuna di attaccarli di fronte a un muro su cui il giocatore ha girato, la previsione non sarà mai in grado di compensare e una volta che l'errore arriva a un livello elevato l'entità passerà alla nuova posizione.

Queste sono le situazioni in cui ci sarebbe un conflitto tra il server e il client ed è il motivo per cui è necessario mantenere gli stati sul client in modo che il server possa correggere eventuali errori.

Scusate le risposte rapide, devo andarmene. Leggi questo articolo , menziona i tiratori ma dovrebbe funzionare per qualsiasi gioco che richieda reti in tempo reale.


Alcune risposte: * Se il server ha autorità, sarebbe responsabile del monitoraggio di tutte le entità in movimento e dell'aggiornamento delle loro posizioni a intervalli regolari. In altre parole, deve far funzionare il motore fisico, che potrebbe essere costoso. La scalabilità è uno dei miei principali obiettivi di progettazione. * Devo interpolare sul lato client, altrimenti ogni aggiornamento del server inviato ai client farà saltare le entità. In questo momento la mia interpolazione è fatta nel motore fisico - imposta semplicemente la velocità. Non ci sono stati o delta.
ShadowChaser,

Ho letto tutti gli articoli di Glenn, ma nei commenti afferma che sono orientati solo verso i tiratori (ovvero / alte frequenze di aggiornamento). Alcuni dei suoi articoli parlano di clienti autorevoli, che è l'implementazione per cui mi sto impegnando. Non voglio fare alcuna interpolazione / fisica sul server, ma sono disposto a cambiare idea se questo è davvero l'unico modo :)
ShadowChaser,

1
-1. Ciò che hai scritto tocca solo vagamente l'argomento; sembra ambiguo. Le risposte si sentono al di sotto della media quando sono essenzialmente "leggere questo lungo articolo", pur non contenendo informazioni utili dall'articolo in questione.
Attaccando

1
@AttackingHobo Dovrei essere d'accordo con te. Ho detto che avevo fretta ma non è una scusa. Se non avessi avuto il tempo sarebbe stato meglio lasciarlo da solo. Lezione appresa.
Gyan aka Gary Buyn,
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.