Sincronizza mille vector3 attraverso la rete con byte minimi?

4

Sto facendo un gioco con Unity e sto riscontrando problemi nel tentativo di sincronizzare la rete. La simulazione deterministica è impossibile perché la logica interna di Unity è basata sul float. Sto cercando di creare una soluzione che funzioni su un autorevole modello client-server, in cui la posizione di ogni soldato è corretta dal server 5-10 volte al secondo. Ciò si ottiene inviando datagrammi UDP e verranno implementate misure per garantire la consegna. Tra correzioni il client simula il gioco nel miglior modo possibile.

I soldati non si muovono spesso in linea retta. Si impegnano nel floccaggio e incontreranno molti ostacoli che devono percorrere. I soldati esistono come individui e non esistono come parte di un oggetto squadra.

Ci sono cose che possono essere fatte per ridurre il consumo di larghezza di banda che non sono l'attenzione qui, perché sono al massimo miglioramenti temporanei. Come solo inviare le posizioni dei soldati che si spostano e / o osservano e comprimono i dati prima della trasmissione.

Il problema è semplice. Se ho dieci soldati che corrono, va bene, ma se ne avrò un migliaio allora l'uso della larghezza di banda diventa irragionevole. Per lo meno abbiamo bisogno di inviare un soldato id (ushort) e posizionare (float, float, float) per soldato. Al momento questo è 2 + 4 + 4 + 4 = 14 byte . Potrebbero essere necessari più dati, ad esempio la direzione di orientamento (i soldati non guardano mai in alto o in basso).

14 * 10 * 1000 = 140kb / s ... troppo!

La domanda è questa:

Come possiamo ridurre il numero di byte inviati per soldato al minimo assoluto e avere ancora un risultato sincronizzato?

POSSIBLE SOLUTIONS:

1. Un'idea che avevo era che, poiché la velocità massima di un soldato non cambia, questo ci permette di correggere la posizione di ogni soldato con un valore assoluto relativo all'ultima posizione nota. Ma anche questo potrebbe risentirne.

Invece di inviare un float per l'asse XYZ della posizione, inviamo un sbyte. Poiché questo è compreso tra -128 e 127, possiamo leggerlo / scriverlo per dire che la nuova posizione è una percentuale della velocità massima del soldato dall'ultima posizione. Se funzionasse, ridurre i dati inviati da 14 byte a 5. Ma anche il 36% è ancora 50kb / s per 1000 soldati.

Potenzialmente ciò potrebbe essere ridotto a 4 byte alcune volte, poiché i soldati non sempre si muovono su o giù (asse Y). Ma questo è, come ignorare i soldati che non si muovono, non il problema.

2. Un'altra idea che ho avuto è stata quella di ridurre l'asse XYZ a 12 bit (1,5 byte). Ogni asse ha 4 bit, 3 dei quali vengono utilizzati per trovare un valore di distanza. 3 bit ci permettono di contare da 0 a 8. Ci sono sei valori tra la fine di zero o la velocità massima. Abbiamo una gamma di valori che possono essere utilizzati in quanto "abbastanza vicini". L'altro bit determina se questo è positivo o negativo. Il server forza quindi una correzione sui soldati prima che invii le loro posizioni al cliente, forzando la loro distanza dall'ultima posizione per conformarsi a uno dei valori "abbastanza vicini".

Inoltre, se non ci saranno più di mille soldati vivi in una volta sola, potremo assegnare soldati a un ID vivente, e quindi accorciare l'ID di rete da 16 bit a 10, consentendoci di contare fino a 1024.

In totale ciò ridurrebbe i dati inviati per soldato da 14 byte a 22 bit (2,75 byte). Sospetto che sarebbe meno probabile che la prima idea mantenga la sincronizzazione. Il risultato potrebbe semplicemente essere troppo nervoso. Se funzionasse sarebbe il 20% della larghezza di banda originale a 28kb / s.

...

Tutte le idee sono benvenute a questo punto. Mi sono concentrato sulla creazione di vector3 più piccoli e più piccoli, ma potrebbe esserci un modo molto migliore di fare ciò che fa qualcosa di completamente diverso.

    
posta inappropriateCode 19.10.2017 - 18:01
fonte

7 risposte

4

Gli algoritmi di compressione standard sono abbastanza efficienti anche con le impostazioni più veloci. Come test rapido ho riempito un file di testo con 34k byte. La compressione con 7-zip sull'impostazione più veloce mi ha dato un file di 1k. Questo è ~ 3% della dimensione originale. Certo, i miei numeri sono stati memorizzati come ascii, ma va a dimostrare che valori simili possono essere ridotti a una piccola frazione.

L'utilizzo di algoritmi standard può probabilmente essere superato da tecniche speciali discusse in altre risposte, ma le tecniche di compressione standard dovrebbero essere molto più veloci da implementare e meno inclini ad usare algoritmi ben testati, ma se il risultato è "abbastanza buono ®" puoi scegliere se vuoi utilizzare il tempo guadagnato per andare sul mercato più velocemente o fare un gioco più dolce nelle aree che l'utente può effettivamente provare.

    
risposta data 19.10.2017 - 19:36
fonte
3

Mi sembra che se hai 1000 persone sullo schermo e stanno floccando, la posizione esatta di ciascuna non sarà così importante come quando ne hai 10?

Potresti far raggruppare i soldati mentre aumenta il numero totale? così, quando ho 10 ogni cosa in movimento è un solitario singolo. Quando ne ho 100 sono in squadre di dieci, con una sola posizione inviata e una formazione. Quando ho 1000 sono in 100 formazioni di unità?

Ciò ti consentirà di scalare all'infinito, anche se ti verrà perso il dettaglio

    
risposta data 19.10.2017 - 18:12
fonte
1

Molte delle idee che hai postato sono buone. Ad un certo punto si raggiungerà il punto di saturazione, quindi ci sono alcuni compromessi che dovrete fare. Alcuni di questi trade-off dipendono dalla grandezza della tua griglia e dalla velocità con cui le tue unità si muovono. Proverò ad aggiungere un paio di altre opzioni che puoi guardare:

  • Compressione (assicurati che in realtà salvi i byte)
  • Invia XYZ, rilevamento e velocità in modo da poter inviare aggiornamenti meno spesso
  • Aggiornamenti interleave (ad esempio, aggiorna 1/5 delle unità con ciascun messaggio)

Probabilmente è possibile imballare il rilevamento e la velocità in un byte. I primi 4 bit sarebbero la direzione (16 direzioni dovrebbero essere abbastanza buone su schermi piccoli) ei secondi 4 bit per la velocità (16 gradazioni dalla velocità massima) per rappresentare i rallentamenti dovuti agli ostacoli.

Questo prende i dati da 14 a 6, ma invii solo dati per 200 soldati alla volta. Significa che il dispositivo riceve 2 aggiornamenti al secondo per ogni soldato, ma ha abbastanza dati per interpolare accuratamente la differenza. Questo fa sì che il datagramma passi dai 5 * 10 * 1000 (50.000 byte) a 6 * 10 * 200 (12.000) byte. Ancora non carina, ma migliore. Aggiungete una compressione LZ4 veloce e potreste ottenere all'interno di quel punto dolce da 1300 byte dove i router tenderanno a lasciare il vostro pacchetto da solo. Ciò dipende in realtà dalla regolarità dei dati.

Puoi salvare un paio di byte per l'ID se invii sempre i dati nello stesso ordine. Puoi usare 2 byte per il primo ID per il gruppo, e dopo avremo un ordine noto per l'ID truppa. Questo ci porta a 3 * 10 * 1000 + 2 (30,002 byte) o 4 * 10 * 200 + 2 (8,002 byte).

    
risposta data 20.10.2017 - 22:46
fonte
0

Uno degli scopi del flocking è creare comportamenti emergenti difficili da modellare, quindi la tua necessità di modellarli in un piccolo numero di byte è complicato.

Un approccio che potreste fare è di ancorare il gregge ad un gregge "virtuale" che è più deterministico nel comportamento. Aggiorna l'algoritmo di floccaggio in un modo che permetta "un'eventuale coerenza" tra il gruppo reale e il gregge virtuale. In altre parole, regola l'algoritmo in modo tale che il gregge reale si allineerà sempre con il gregge virtuale nel lungo periodo.

Una volta che hai questo, allora puoi iniziare a considerare il gregge virtuale più prevedibile, usando qualunque approccio abbia senso (forse la matematica intera invece di fluttuare), e il vero gregge si aggira intorno ad esso. Se lo fai, il 90% della comunicazione sarà di piccoli errori non importanti nelle posizioni. Alla fine otterrai un soldato che si muoverà fuori linea, perché il vero gregge lo droga lontano dal gregge virtuale. Quindi puoi gestirlo come un caso speciale, che è molto più efficiente di gestire ogni soldato in quel modo.

    
risposta data 19.10.2017 - 22:53
fonte
0

Quando 1.000 soldati ti stanno avvicinando, importa se quello che si trova a 100 metri a 50 ° dalla tua posizione in avanti è esattamente posizionato quando ce ne sono 30 che sono a 10 metri di distanza direttamente di fronte a te?

Potresti inviare solo le posizioni di quei soldati che sono effettivamente nel raggio d'azione della capacità del giocatore di attaccare o essere attaccati. Il resto potrebbe essere generato sul client e il giocatore probabilmente non sarebbe in grado di dire la differenza. Quando i veri soldati entrano nel raggio in cui contano, mandano le loro reali posizioni al cliente.

Potresti scegliere di non inviare le posizioni di nessun soldato che si trova dietro il giocatore (o, meglio ancora, non nel loro campo visivo), in quanto non saranno disegnati. Poiché il server è la verità fondamentale, può ancora decidere se un soldato dietro al giocatore ha attaccato e colpito il giocatore, ma non è necessario inviare la posizione del soldato finché il giocatore non gira il proprio personaggio per affrontare quel soldato.

    
risposta data 20.10.2017 - 07:32
fonte
0

XOR il valore di ogni campo nella struttura dati con il valore della struttura precedente, quindi utilizza una codifica a byte variabile o compressione a bit variabile per inviare i risultati. Raccoglierei i dati per tutti i soldati in un unico stream e utilizzare un algoritmo di compressione veloce (dopo il processo xor precedente) per comprimerlo prima di inviarlo come blocco. Raccomando lz4 come algoritmo.

    
risposta data 20.10.2017 - 21:33
fonte
0

Un'altra proposta che tenta di evitare l'invio di dati invece di inviarli in modo efficiente ...

Si noti correttamente che non è possibile utilizzare la replica del comportamento del client esatta a causa della dubbia validità del float, ma si può effettuare la replicazione del client inesatta e quindi eseguire un'interpolazione costante torna ai valori esatti quando gli aggiornamenti arrivano dal server. Questo è un modo estremamente complicato di fare le cose, ma può anche fornire dei vantaggi interessanti come la compensazione della latenza (in effetti, dal codice di gioco che ho visto, sembra che alcuni server facciano compensazione della latenza su input del client: supponiamo che vadano in linea retta e li riposizionino agevolmente in base a ciò che hanno fatto in passato di cui al momento il server non era al corrente).

Fondamentalmente, impostare gli agenti su off, quindi aggiornarli regolarmente (ma sfalsati in modo da non provare a inviare tutti i vettori 1k all'interno di un frame) o quando fanno una "decisione" lato server (cioè non predicibile comportamento). Se il comportamento di controllo "di base" è abbastanza semplice, è possibile perdonare un secondo o più di "ipotesi" del client (che comporterà l'integrazione di incoerenze in virgola mobile) seguita da un'interpolazione lineare verso la posizione effettiva .

Hai suggerito che il 'desyning' è evidente dopo circa 5 secondi: questo metodo ti permetterà di sintonizzare / modificare la velocità di aggiornamento in modo che i client non debbano apportare aggiornamenti drammatici alle posizioni degli agenti e in modo che la rete non sia sono sommersi da aggiornamenti inutili. In effetti, la velocità può variare in tempo reale per rispondere allo stato del gioco: se ci sono troppe unità da gestire, può iniziare a fallire con grazia invece di rilasciare frame / qualunque cosa.

Non sono un programmatore di giochi, ma suona perfettamente funzionante, anche se un po 'complicato. Quando ottieni un aggiornamento dal server puoi rendere il "fixing" fluido e complicato come preferisci, e per i test iniziali puoi semplicemente farlo "saltare" che rimuove questo elemento di sofferenza all'inizio e fornisce un buon indicatore di se l'idea funzionerà o no.

    
risposta data 20.10.2017 - 22:44
fonte

Leggi altre domande sui tag