Networking per giochi di strategia in tempo reale


16

Sto sviluppando un gioco di strategia in tempo reale per un corso di informatica che sto seguendo. Uno degli aspetti più difficili sembra essere la rete e la sincronizzazione client-server. Ho letto su questo argomento (inclusi 1500 arcieri ), ma ho deciso di adottare un approccio client-server rispetto ad altri modelli (su LAN, ad esempio).

Questo gioco di strategia in tempo reale presenta alcuni problemi. Per fortuna, ogni azione intrapresa dal giocatore è deterministica. Tuttavia, ci sono eventi che si verificano a intervalli pianificati. Ad esempio, il gioco è composto da tessere e quando un giocatore prende una tessera, il "livello di energia", un valore su quella tessera, dovrebbe aumentare di uno al secondo dopo che è stato preso. Questa è una spiegazione molto rapida che dovrebbe giustificare il mio caso d'uso.

In questo momento sto facendo thin client, che inviano pacchetti al server e attendono una risposta. Tuttavia, ci sono diversi problemi.

Quando i giochi tra i giocatori si trasformano in endgame, ci sono spesso più di 50 eventi al secondo (a causa degli eventi programmati, spiegati in precedenza, accumulando), e quindi iniziano a comparire errori di sincronizzazione. Il mio problema più grande è che anche una piccola deviazione nello stato tra i clienti potrebbe significare decisioni diverse che i clienti prendono, che si trasformano in giochi di neve completamente separati. Un altro problema (che non è così importante in questo momento) è che c'è latenza e bisogna aspettare alcuni millisecondi, anche pochi secondi dopo che si muovono per vedere il risultato.

Mi chiedo quali strategie e algoritmi potrei usare per rendere questo più facile, veloce e divertente per l'utente finale. Ciò è particolarmente interessante data l'elevata quantità di eventi al secondo, insieme a diversi giocatori per partita.

TL; DR realizzando un RTS con> 50 eventi al secondo, come si sincronizzano i client?


Puoi forse implementare ciò che fa Eve-online e "rallentare" il tempo per consentire a tutto di elaborare correttamente.
Ryan Erb,

3
Ecco un collegamento obbligatorio al modello client / server di Planetary Annihilation: forrestthewoods.ghost.io/… Questa è un'alternativa al modello lockstep che sembra funzionare molto bene per loro.
DallonF,

Prendi in considerazione la possibilità di ridurre il numero di eventi inviando un singolo aggiornamento per tutte le tessere prese anziché gli aggiornamenti per ogni tessera o, come ha risposto Ilmari, decentralizzando le azioni dei non giocatori.
Lilienthal,

Risposte:


12

Il tuo obiettivo di sincronizzare 50 eventi al secondo in tempo reale mi sembra che non sia realistico. Questo è il motivo per cui l'approccio a passo di blocco di cui all'articolo 1500 arcieri è, bene, parlato!

In una frase: l'unico modo per sincronizzare troppi elementi in un tempo troppo breve su una rete troppo lenta è NON sincronizzare troppi elementi in un tempo troppo breve su una rete troppo lenta, ma invece progredire in modo deterministico su tutti i client e sincronizzare solo il necessità indispensabili (input dell'utente).


6

ogni azione intrapresa dal giocatore è deterministica, tuttavia ci sono eventi che si verificano a intervalli programmati

Penso che ci sia il tuo problema; il tuo gioco dovrebbe avere solo una linea temporale (per le cose che influenzano il gameplay). Dici che certe cose crescono ad una velocità di X al secondo ; scopri quanti passaggi di gioco sono in un secondo e convertilo in una velocità di X per Y passaggi di gioco . Quindi, anche se il gioco potrebbe rallentare, tutto rimane deterministico.

Far funzionare il gioco in modo indipendente in tempo reale ha altri vantaggi:

  • puoi eseguire il benchmark eseguendolo il più velocemente possibile
  • puoi eseguire il debug rallentando il gioco per vedere eventi fugaci e, come detto
  • il gioco rimane deterministico, il che è molto importante per il multiplayer.

Hai anche detto che si verificano problemi quando ci sono> 50 eventi o ci sono ritardi fino a secondi. Questo è molto più piccolo rispetto allo scenario descritto in 1500 arcieri , quindi vedi se riesci a profilare il tuo gioco e scopri dove si trova il rallentamento.


1
+1: basato su frame è la scelta giusta, non basata sul tempo. Puoi tentare di mantenere N frame al secondo, ovviamente. Un leggero intoppo è meglio di una desincronizzazione completa.
Patrick B,

@PatrickB: Vedo che molti giochi usano un tempo "simulato" che non è legato ai frame video. World of Warcraft aggiorna solo cose come il mana ogni 100 ms e la Fortezza dei Nani si imposta automaticamente su 10 tick per fotogramma video.
Mooing Duck,

@Mooing Duck: il mio commento era specifico per gli RTS. Per qualcosa in cui piccoli errori possono essere tollerati e corretti in seguito (ad es. MMORPG, FPS), l'utilizzo di valori continui non è solo corretto, ma critico. Tuttavia, simulazioni deterministiche che devono essere sincronizzate su più macchine? Attenersi alle cornici.
Patrick B,

4

Innanzitutto, per risolvere il problema con eventi programmati, non trasmettere gli eventi quando si verificano , ma quando sono inizialmente programmati. Cioè, invece di inviare un messaggio "incrementa l'energia della piastrella ( x , y )" ogni secondo, basta inviare un singolo messaggio dicendo "incrementa l'energia della piastrella ( x , y ) una volta al secondo fino a quando è pieno, o fino a quando interrotta". Ogni cliente è quindi responsabile della pianificazione locale degli aggiornamenti.

In effetti, puoi prendere ulteriormente questo principio e trasmettere solo le azioni del giocatore : tutto il resto può essere calcolato localmente da ciascun client (e dal server, se necessario).

(Naturalmente, probabilmente dovresti anche occasionalmente trasmettere checksum dello stato del gioco, per rilevare qualsiasi desincronizzazione accidentale e avere un meccanismo per risincronizzare i client se ciò accade, ad esempio rinviando tutti i dati di gioco dalla copia autorevole del server ai client Speriamo che questo dovrebbe essere un evento raro, riscontrato solo durante i test o durante rari malfunzionamenti.)


In secondo luogo, per mantenere sincronizzati i client, assicurati che il tuo gioco sia deterministico. Altre risposte hanno già fornito buoni consigli per questo, ma lasciami includere un breve riassunto di cosa fare:

  • Crea il tuo gioco internamente a turni, con ogni turno o "tick" che richiede, diciamo, 1/50 secondi. (In effetti, probabilmente potresti cavartela con 1/10 di secondo o più turni.) Qualsiasi azione del giocatore che si verifica durante un singolo turno deve essere considerata simultanea. Tutti i messaggi, almeno dal server ai client, devono essere contrassegnati con il numero di svolta, in modo che ciascun client sappia su quale svolta si verifica ogni evento.

    Dato che il tuo gioco utilizza un'architettura client-server, puoi fare in modo che il server funga da arbitro finale di ciò che accade durante ogni turno, il che semplifica alcune cose. Si noti, tuttavia, che significa che i client devono anche riconfermare le proprie azioni dal server: se un client invia un messaggio che dice "Sposto unità X di un riquadro a sinistra" e la risposta del server non dice nulla sull'unità X in movimento, il client deve presumere che non sia accaduto, e possibilmente annullare qualsiasi animazione di movimento predittiva che potrebbero aver già iniziato a riprodurre.

  • Definire un ordine coerente per gli eventi "simultanei" che si verificano nello stesso turno, in modo che ciascun client li esegua nello stesso ordine. Questo ordine può essere qualsiasi cosa, purché sia ​​deterministico e uguale per tutti i client (e il server).

    Ad esempio, puoi prima incrementare tutte le risorse (cosa che può essere fatta tutta in una volta, se la crescita delle risorse in una tessera non può interferire con quella in un'altra), quindi sposta le unità di ciascun giocatore in una sequenza predeterminata, quindi sposta le unità NPC. Per essere onesti con i giocatori, potresti voler variare l'ordine dei movimenti delle unità tra i turni, in modo che ogni giocatore inizi prima ugualmente spesso; questo va bene, purché sia ​​fatto in modo deterministico (ad es. in base al numero di svolta).

  • Se stai usando la matematica in virgola mobile, assicurati di utilizzarla in modalità IEEE rigorosa. Questo può rallentare un po 'le cose, ma è un piccolo prezzo da pagare per la coerenza tra i clienti. Assicurarsi inoltre che non si verifichino arrotondamenti accidentali durante le comunicazioni (ad es. Un client che trasmette un valore arrotondato al server, ma che utilizza comunque il valore non arrotondato internamente). Come notato sopra, anche avere un protocollo per rilevare e recuperare dalla desincronizzazione è una buona idea, per ogni evenienza.


1
Inoltre, sincronizzare l'RNG per l'avvio e estrarre l'RNG sincronizzato solo quando il server te lo dice. Starcraft1 ha avuto a lungo un bug in cui il seme RNG non veniva salvato durante i replay, quindi i replay si sarebbero lentamente deviati dai giochi reali.
Mooing Duck,

1
@MooingDuck: buon punto. In effetti, suggerirei di trasmettere il seme RNG corrente ad ogni turno, in modo che la desincronizzazione RNG venga immediatamente rilevata. Inoltre, se il tuo codice UI richiede casualità, non estrarlo dalla stessa istanza RNG utilizzata per la logica di gioco.
Ilmari Karonen,

3

Dovresti rendere la tua logica di gioco completamente indipendente dal tempo reale e essenzialmente renderla a turni. In questo modo si conosce esattamente la svolta in cui "avviene il cambiamento di energia delle piastrelle". Nel tuo caso, ogni turno è solo 1/50 di secondo.

In questo modo devi preoccuparti solo degli input dei giocatori, tutto il resto viene gestito dalla logica dei giochi e completamente identico su tutti i client. Anche se il gioco si blocca per un momento, a causa del ritardo della rete o di calcoli estremamente complicati, gli eventi continuano a essere sincronizzati per tutti.


1

Prima di tutto devi capire che PC float / double math NON È deterministico, a meno che tu non specifichi di usare rigorosamente IEEE-754 per il tuo calcolo (sarà lento)

Quindi è così che lo implementerei: il client si connetterà al server e sincronizzerà il tempo (prenditi cura della latenza del ping!) (Per un gameplay lungo potrebbe essere necessario risincronizzare il timestamp / girare)

ora, ogni volta che un client esegue un'azione, include un timestamp / turn e spetta al server rifiutare il timestamp / turn errato. Quindi il server rinvia l'azione ai client e ogni volta che un turno viene "chiuso" (ovvero il server non accetterà il turno / timestamp così vecchio), il server invia e termina l'azione del turno ai client.

I clienti avranno 2 "mondo": uno è sincronizzato con il turno finale, l'altro è calcolato a partire dal turno finale, sommando l'azione arrivata in coda, fino al turno / timestamp del cliente corrente.

poiché il server accetterà un'azione un po 'obsoleta, il client potrebbe aggiungere la propria azione direttamente nella coda, quindi il tempo di andata e ritorno sulla rete verrà nascosto, almeno per la propria azione.

l'ultima cosa è mettere in coda più azioni in modo da poter riempire il pacchetto MTU, causando un sovraccarico del protocollo inferiore; una buona idea è quella di farlo sul server, quindi ogni evento end-turn contiene azioni sulla coda.

io uso questo algoritmo su un gioco di tiro in tempo reale, e funziona benissimo (con un client senza client che esegue il chasing della propria azione, ma con un ping del server basso come 20 / 50ms), anche ogni server X end-turn invia uno speciale "all pacchetto client map ", per correggere valori alla deriva.


I problemi matematici in virgola mobile possono generalmente essere evitati in quanto tali: in un RTS, di solito è possibile eseguire facilmente la simulazione e il movimento con un numero intero / fisso e utilizzare il virgola mobile solo per il livello di visualizzazione che non influisce sul comportamento del gioco.
Peteris,

Con numeri interi è difficile eseguire tessere orizzontali, a meno che non si tratti di una tavola ottagonale. Non c'è alcuna accelerazione hw per il punto fisso, quindi potrebbe essere più lento del galleggiante ieee754
Lesto,
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.