Come mantenere una struttura di dati sincronizzata su una rete?


12

Contesto

Nel gioco a cui sto lavorando (una sorta di avventura grafica punta e clicca), praticamente tutto ciò che accade nel mondo di gioco è controllato da un gestore di azioni strutturato un po 'come:

inserisci qui la descrizione dell'immagine

Quindi, ad esempio, se il risultato dell'esame di un oggetto dovesse far salutare il personaggio, camminare un po 'e poi sedersi, ho semplicemente generato il seguente codice:

var actionGroup = actionManager.CreateGroup();
actionGroup.Add(new TalkAction("Guybrush", "Hello there!");
actionGroup.Add(new WalkAction("Guybrush", new Vector2(300, 300));
actionGroup.Add(new SetAnimationAction("Guybrush", "Sit"));

Ciò crea un nuovo gruppo di azioni (un'intera riga nell'immagine sopra) e lo aggiunge al gestore. Tutti i gruppi vengono eseguiti in parallelo, ma le azioni all'interno di ciascun gruppo sono concatenate insieme in modo che il secondo inizi solo dopo il primo. Al termine dell'ultima azione in un gruppo, il gruppo viene distrutto.

Problema

Ora ho bisogno di replicare queste informazioni attraverso una rete, in modo che in una sessione multiplayer, tutti i giocatori vedano la stessa cosa. La serializzazione delle singole azioni non è il problema. Ma sono un principiante assoluto quando si tratta di networking e ho alcune domande. Penso che per semplicità in questa discussione possiamo astrarre il componente di Action Manager in modo da essere semplicemente:

var actionManager = new List<List<string>>();

Come devo procedere per mantenere sincronizzati i contenuti della suddetta struttura di dati tra tutti i giocatori?

Oltre alla domanda principale, ho anche alcune altre preoccupazioni ad esso correlate (cioè tutte le possibili implicazioni dello stesso problema sopra):

  • Se utilizzo un'architettura server / client (con uno dei giocatori che agiscono sia da server che da client) e uno dei client ha generato un gruppo di azioni, dovrebbe aggiungerli direttamente al gestore o inviare solo una richiesta al server, che a sua volta ordinerà ad ogni client di aggiungere quel gruppo ?
  • Che dire delle perdite di pacchetti e simili? Il gioco è deterministico, ma sto pensando che qualsiasi discrepanza nella sequenza delle azioni eseguite in un cliente potrebbe portare a stati incoerenti del mondo. Come posso proteggermi da questo tipo di problema ?
  • Che cosa succede se aggiungo troppe azioni contemporaneamente, ciò non causerà problemi alla connessione? Qualche modo per alleviarlo?

5
Link "leggi questo primo" obbligatorio gafferongames.com/networking-for-game-programmers che non è molto tecnico nonostante abbia un po 'di codice ma è prolisso e spiega abbastanza bene le tue opzioni. Inoltre ti metterà su un piano di parità con tutti gli altri che hanno letto quel link e che è un posto felice.
Patrick Hughes,

@PatrickHughes Grazie per il link! Sicuramente leggerò tutto.
David Gouveia,

Ci sono alcuni pacchetti che gestiscono questo tipo di cose per te. Non so quanto sia efficiente o veloce, ma NowJS ( nowjs.com ) è un esempio interessante. Dal punto di vista degli sviluppatori, hai semplicemente strutture di dati condivise tra client e server. Cambia uno, l'altro cambia.
Tim Holt,

Risposte:


9

Se utilizzo un'architettura server / client (con uno dei giocatori che agiscono sia da server che da client) e uno dei client ha generato un gruppo di azioni, dovrebbe aggiungerli direttamente al gestore o inviare solo una richiesta al server, che a sua volta ordinerà ad ogni client di aggiungere quel gruppo?

Hai tre opzioni in questo caso, due delle quali hai già menzionato. La scelta dell'opzione scelta dipende da una varietà di fattori di cui solo tu puoi essere il migliore giudice:

1: il client genera un gruppo di azioni che le aggiunge direttamente e quindi le invia al server. Il server lo accetta senza alcun controllo

* I vantaggi di questo approccio sono che, per quanto riguarda il cliente, le sue azioni forniscono loro un feedback immediato indipendentemente dal ritardo della rete. Lo svantaggio è che i messaggi del client sono accettati dal server come un fatto (che potrebbero non essere) *

2: il client invia una richiesta al server, che a sua volta trasmette la richiesta a tutti, incluso il client che ha originato la richiesta.

I vantaggi di questo approccio sono che il server è ora in controllo di tutto ciò che accade nel gioco che allevia la maggior parte dei problemi con attività illegali (qui illegale può variare dal client che taglia un muro all'esecuzione di una mossa che ora è illegale perché il server ha più informazioni che il cliente non aveva quando il cliente ha fatto una mossa particolare)

3: il client genera un gruppo di azioni, le aggiunge direttamente e quindi le invia al server. Il server elabora la richiesta, esegue i propri controlli sulle azioni per assicurarsi che non siano illegali e quindi trasmette il passaggio a tutti i giocatori incluso il client.

Questo prende il meglio di 1 e 2 al costo di requisiti di larghezza di banda leggermente più elevati. I vantaggi sono il feedback immediato al client per le proprie mosse e se le mosse effettuate dal client sono illegali, il server intraprende le azioni appropriate (corregge forzatamente il client).

Che dire delle perdite di pacchetti e simili? Il gioco è deterministico, ma sto pensando che qualsiasi discrepanza nella sequenza di azioni eseguite in un cliente potrebbe portare a stati incoerenti del mondo. Come posso proteggermi da questo tipo di problema?

La perdita di pacchetti e il ritardo estremo sono un problema difficile da risolvere . Soluzioni diverse (nessuna perfetta) vengono utilizzate per affrontare il problema a seconda del tipo di gioco (ad es. Calcoli morti). Presumo che il tuo gioco non debba sincronizzare la fisica.

Una delle prime cose che dovresti fare è timbrare tutte le tue mosse. Il tuo sistema dovrebbe quindi tenerne conto e giocare le mosse di conseguenza (forse il server ha questa responsabilità e quindi negare le mosse illegali). Quando si implementa questo sistema, assumere 0 latenza (test in locale).

Ovviamente la latenza 0 non è una buona ipotesi, anche sulle reti locali, quindi ora che tutte le mosse rispettano il tempo, a che ora ti fidi? È qui che entra in gioco il problema della sincronizzazione temporale. Molti articoli / documenti online ti guideranno nella risoluzione di questo problema.

Che cosa succede se aggiungo troppe azioni contemporaneamente, ciò non causerà problemi alla connessione? Qualche modo per alleviarlo?

Prima le cose ovvie. Non inviare mai stringhe o oggetti serializzati. Non inviare messaggi velocemente mentre il gioco stesso si sta aggiornando. Mandali a pezzi (ad es. 10 volte al secondo). Si verificherà un errore (in particolare un errore di arrotondamento) per cui il server / client dovrà correggere gli errori di tanto in tanto (quindi ogni secondo, il server invia tutto [tutto = tutti i dati importanti per mantenere le cose nel gioco corrette state] a cui il client esegue quindi lo snap e / o prende in considerazione per correggere il suo stato).EDIT: Uno dei miei colleghi ha affermato che se si utilizza UDP [che si dovrebbe, se si dispone di molti dati], non esiste un ordinamento di rete. L'invio di più di 10 pacchetti al secondo aumenta le modifiche dei pacchetti che arrivano fuori servizio. Vedrò se posso citare quell'affermazione. Per quanto riguarda i pacchetti fuori servizio, è possibile scartarli o aggiungerli al messaggio del sistema / spostare la coda che è progettata per gestire le modifiche in passato per correggere lo stato attuale

Dovresti avere un sistema di messaggistica che si basa su qualcosa che occupa pochissimo spazio. Se devi inviare qualcosa che può avere solo due valori, invia solo un bit. Se sai che puoi avere solo 256 mosse, quindi invia un carattere senza segno. Esistono vari trucchi per modificare i messaggi nel modo più stretto possibile.

Molto probabilmente il tuo sistema di messaggistica utilizzerà molte enumerazioni per permetterti di estrarre facilmente le mosse da un messaggio in arrivo (suggerirei di dare un'occhiata a RakNet e agli esempi in essa se non capisci cosa intendo qui).

Sono sicuro che sai già che non puoi risolvere i problemi che sorgono con alta latenza e perdita di pacchetti, ma puoi renderne gli effetti meno pronunciati. In bocca al lupo!

EDIT: il commento di Patrick Hughes sopra con il link rende questa risposta incompleta e specifica nella migliore delle ipotesi. Consiglio vivamente di leggere gli argomenti collegati invece


Grazie mille per la risposta dettagliata, che copre praticamente tutte le mie preoccupazioni. Solo una domanda sulla parte in cui dici so every second, the server sends everything ... which the client then snaps to. Posso semplicemente inviare una grande quantità di informazioni come questa in una volta? Qual è una dimensione massima ragionevole?
David Gouveia,

Un massimo ragionevole sarà un numero che non introduce il ritardo:) ... So che non stavi cercando quella risposta, ma non voglio mettere un numero perché non ho familiarità con il tuo gioco.
Samaursa,

Inoltre, non vi è alcuna regola rigida che si dovrebbe inviare un grosso pezzo, l'ho appena dato come esempio.
Samaursa,

@Samaursa - Non sarei d'accordo con il commento "Se stai usando UDP [che dovresti, se hai molti dati]". Dal momento che sono nuovi nella programmazione di rete, TCP si occupa della maggior parte delle cose difficili per te a un costo molto più lento. Nel loro caso avrei usato TCP fino a quando non è diventato un collo di bottiglia. A quel punto avrebbero una migliore comprensione di come la loro app sta usando la rete, quindi implementare le cose UDP sarebbe più facile.
Chris,

@ Chris: Non sarò d'accordo :) ... la decisione tra l'utilizzo di TCP e UDP è come (imo) la decisione tra la scelta di Python e C ++ per lo sviluppo. In teoria potrebbe sembrare ok, ma in pratica sarà difficile semplicemente passare (di nuovo, questo è imo).
Samaursa,
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.