Come collegare in rete questo sistema di entità?


33

Ho progettato un sistema di entità per un FPS. In pratica funziona così:

Abbiamo un oggetto "mondo", chiamato GameWorld. Questo contiene una matrice di GameObject, nonché una matrice di ComponentManager.

GameObject contiene un array di componenti. Fornisce anche un meccanismo di eventi che è davvero semplice. I componenti stessi possono inviare un evento all'entità, che viene trasmesso a tutti i componenti.

Component è fondamentalmente qualcosa che dà a GameObject determinate proprietà, e dal momento che GameObject è in realtà solo un contenitore di essi, tutto ciò che ha a che fare con un oggetto di gioco accade nei Componenti. Gli esempi includono ViewComponent, PhysicsComponent e LogicComponent. Se è necessaria la comunicazione tra di loro, ciò può essere fatto attraverso l'uso di eventi.

ComponentManager è solo un'interfaccia come Component e, per ogni classe Component, in genere dovrebbe esserci una classe ComponentManager. Questi gestori di componenti sono responsabili della creazione e dell'inizializzazione dei componenti con proprietà lette da qualcosa come un file XML.

ComponentManager si occupa anche degli aggiornamenti di massa dei componenti, come PhysicsComponent, dove userò una libreria esterna (che fa tutto in una volta).

Per la configurabilità, userò una fabbrica per le entità che leggerà un file XML o uno script, creerò i componenti specificati nel file (che aggiunge anche un riferimento ad esso nel giusto gestore componenti per gli aggiornamenti di massa) e quindi iniettarli in un oggetto GameObject.

Ora arriva il mio problema: proverò a usarlo per i giochi multiplayer. Non ho idea di come affrontare questo.

Primo: quali entità dovrebbero avere i clienti sin dall'inizio? Dovrei iniziare spiegando come un motore a giocatore singolo determinerebbe quali entità creare.

Nell'editor di livello è possibile creare "pennelli" ed "entità". Le spazzole sono per cose come pareti, pavimenti e soffitti, sostanzialmente forme semplici. Le entità sono l'oggetto del gioco di cui ti ho parlato. Quando si creano entità nell'editor di livello, è possibile specificare le proprietà per ciascuno dei suoi componenti. Queste proprietà vengono passate direttamente a qualcosa come un costruttore nello script dell'entità.

Quando si salva il livello per il caricamento del motore, questo viene scomposto in un elenco di entità e delle proprietà associate. I pennelli vengono convertiti in un'entità "worldspawn".

Quando carichi quel livello, istanzia tutte le entità. Sembra semplice, eh?

Ora, per il collegamento in rete delle entità ho riscontrato numerosi problemi. Innanzitutto, quali entità dovrebbero esistere sul client dall'inizio? Supponendo che sia il server che il client dispongano del file di livello, il client potrebbe anche istanziare tutte le entità nel livello, anche se sono presenti solo ai fini delle regole di gioco sul server.

Un'altra possibilità è che il client installi un'entità non appena il server invia informazioni su di essa e ciò significa che il client avrà solo entità di cui ha bisogno.

Un altro problema è come inviare le informazioni. Penso che il server potrebbe usare la delta-compressione, il che significa che invia nuove informazioni solo quando qualcosa cambia, piuttosto che inviare un'istantanea al client in ogni frame. Ciò significa che il server deve tenere traccia di ciò che ogni client conosce al momento.

E infine, come dovrebbe essere iniettata la rete nel motore? Sto pensando a un componente, NetworkComponent, che viene iniettato in ogni entità che dovrebbe essere collegata in rete. Ma come dovrebbe il componente di rete sapere quali variabili mettere in rete, come accedervi e infine come il componente di rete corrispondente sul client dovrebbe sapere come modificare le variabili di rete?

Sto avendo enormi problemi ad avvicinarmi a questo. Ti sarei davvero grato se mi avessi aiutato lungo la strada. Sono aperto a suggerimenti su come migliorare anche la progettazione del sistema dei componenti, quindi non abbiate paura di suggerirlo.

Risposte:


13

Questa è una dannata bestia di una domanda con molti dettagli +1 lì. Decisamente abbastanza per aiutare le persone che si imbattono in esso.

Volevo solo aggiungere i miei 2 centesimi per non inviare dati di fisica !! Onestamente non posso sottolineare abbastanza. Anche se l'hai finora ottimizzato al punto che puoi praticamente inviare 40 sfere che rimbalzano con una microcollisione e potrebbero andare a tutta velocità in una stanza tremante che non riduce nemmeno la frequenza dei fotogrammi. Mi riferisco all'esecuzione della "compressione / codifica delta", nota anche come differenziazione dei dati di cui hai discusso. È abbastanza simile a quello che stavo per sollevare.

Dead Reckoning VS Data Differencing: sono abbastanza diversi e in realtà non occupano gli stessi metodi, il che significa che è possibile implementare entrambi per aumentare ulteriormente l'ottimizzazione! Nota: non li ho usati entrambi insieme, ma ho lavorato con entrambi.

Codifica Delta o differenziazione dei dati: il server trasporta i dati su ciò che i client sanno e invia solo le differenze tra i vecchi dati e ciò che dovrebbe essere modificato al client. ad esempio pseudo-> in un esempio potresti inviare i dati "315 435 222 3546 33" quando i dati sono già "310 435 210 4000 40" Alcuni sono solo leggermente modificati e uno non è cambiato affatto! Invece, invieresti (nel delta) "5 0 12 -454 -7" che è considerevolmente più breve.

Esempi migliori potrebbero essere qualcosa che cambia molto più lontano di quello, ad esempio, diciamo che ho un elenco collegato con 45 oggetti collegati in questo momento. Voglio ucciderne 30, quindi lo faccio, quindi invio a tutti i dati dei nuovi pacchetti, che rallenterebbero il server se non fosse già stato creato per fare cose del genere, ed è successo perché ci stava provando per correggersi, per esempio. Nella codifica delta, dovresti semplicemente inserire (pseudo) "list.kill 30 at 5" e rimuoverebbe 30 oggetti dall'elenco dopo il 5, quindi autenticare i dati, ma su ciascun client anziché sul server.

Pro: (Posso solo pensare a uno di ciascuno in questo momento)

  1. Velocità: ovviamente nel mio ultimo esempio ho descritto. Sarebbe molto più grande di una differenza rispetto all'esempio precedente. In generale, non posso onestamente dire per esperienza quale di questi sarebbe più comune, poiché lavoro molto di più con la resa dei conti morta

Contro:

  1. Se stai aggiornando il tuo sistema e vuoi aggiungere altri dati che dovrebbero essere modificati attraverso il delta, dovrai creare nuove funzioni per cambiare quei dati! (es. come prima "list.kill 30 a 5" Oh merda ho bisogno di un metodo di annullamento aggiunto al client! "list.kill undo")

Calcolo dei morti: in poche parole, ecco un'analogia. Sto scrivendo una mappa per qualcuno su come raggiungere una posizione e includo solo i punti su dove andare in generale, perché è abbastanza buono (fermati all'edificio, gira a sinistra). Qualcuno mappa degli altri include i nomi delle strade e anche quanti gradi girare a sinistra, è necessario? (No...)

La resa dei conti è dove ogni cliente ha un algoritmo che è costante per cliente. I dati vengono praticamente modificati semplicemente dicendo quali dati devono essere modificati e come farlo. Il client modifica i dati da solo. Un esempio è che se ho un personaggio che non è il mio giocatore, ma viene spostato da un'altra persona che gioca con me, non dovrei aggiornare i dati ogni fotogramma perché molti dei dati sono coerenti!

Diciamo che il mio personaggio si sta muovendo in una certa direzione, molti server invieranno dati ai client che dicono (quasi per frame) dove si trova il giocatore e che si sta muovendo (per motivi di animazione). Sono così tanti dati superflui! Perché diavolo devo aggiornare ogni singolo fotogramma, dove si trova l'unità e quale direzione è rivolta E che si sta muovendo? In poche parole: non lo so. Aggiorna i client solo quando cambia la direzione, quando cambia il verbo (isMoving = true?) E qual è l'oggetto! Quindi ogni client sposta l'oggetto di conseguenza.

Personalmente questa è una tattica di buon senso. È qualcosa che pensavo di essere stato intelligente nel concepire molto tempo fa, che si è rivelato essere usato tutto il tempo.

risposte

Per essere sinceri, leggi il post di James e leggi quello che ho detto sui dati. Sì, dovresti assolutamente usare la delta-encoding, ma pensa anche all'utilizzo dei calcoli morti.

Personalmente vorrei istanziare i dati sul client, quando riceve informazioni su di esso dal server (qualcosa che hai suggerito).

Solo gli oggetti che possono cambiare dovrebbero mai essere considerati modificabili in primo luogo, giusto? Mi piace la tua idea di includere che un oggetto dovrebbe avere dati di rete, attraverso il tuo sistema componente e entità! È intelligente e dovrebbe funzionare bene. Ma non dovresti mai dare ai pennelli (o ai dati assolutamente coerenti) alcun metodo di rete. Non ne hanno bisogno, dal momento che è qualcosa che non può nemmeno cambiare (client to client).

Se è qualcosa come una porta, gli darei i dati di rete ma solo un booleano sul fatto che sia aperto o meno, quindi ovviamente che tipo di oggetto è. Il client dovrebbe sapere come modificarlo, ad es. È aperto, chiudilo, ogni client riceve che dovrebbero chiuderlo tutti, quindi cambiate i dati booleani, quindi animate la porta per chiuderla.

Per quanto riguarda il modo in cui dovrebbe sapere quali variabili mettere in rete, potrei avere un componente che è veramente un oggetto SUB, e dargli componenti che vorresti mettere in rete. Un'altra idea è non solo avere, AddComponent("whatever")ma anche AddNetComponent("and what have you")solo perché sembra più intelligente personalmente.


Questa è una risposta ridicolmente lunga! Mi dispiace terribilmente per quello. Dato che intendevo fornire solo una piccola quantità di conoscenza e poi i miei 2 centesimi su alcune cose. Quindi capisco che molto potrebbe essere un po 'inutile da notare.
Joshua Hedges,

3

Stavo per scrivere un commento, ma ho deciso che potrebbe trattarsi di informazioni sufficienti per una risposta.

In primo luogo, +1 per una domanda così ben scritta con tonnellate di dettagli per giudicare la risposta.

Per il caricamento dei dati vorrei che il client caricasse il mondo dal file mondiale. Se le tue entità hanno degli ID che provengono dal file di dati, li caricerei anche per impostazione predefinita in modo che il tuo sistema di rete possa fare riferimento a loro per sapere di quali oggetti sta parlando. Chiunque carica gli stessi dati iniziali dovrebbe significare che tutti hanno gli stessi ID per quegli oggetti.

In secondo luogo, non creare un componente NetworkComponent in quanto ciò non farebbe altro che replicare i dati in altri componenti esistenti (fisica, animazione e simili sono alcune cose comuni da inviare). Per usare il tuo nome potresti voler creare un NetworkComponentManager. Questo sarebbe leggermente diverso dall'altra relazione Component-ComponentManager che hai, ma potrebbe essere istanziato quando avvii un gioco in rete e hai qualsiasi tipo di componenti che hanno un aspetto di rete per fornire i loro dati al gestore in modo che possa impacchettarlo e mandalo via. È qui che la funzionalità Salva / Carica potrebbe essere utilizzata se si dispone di una sorta di meccanismo di serializzazione / deserializzazione che è possibile utilizzare anche per impacchettare i dati per, come detto,

Data la tua domanda e il livello di informazione, non credo di dover entrare in molti più dettagli, ma se qualcosa non è chiaro ti preghiamo di inviare un commento e aggiornerò la risposta per affrontare questo.

Spero che sia di aiuto.


Quindi, quello che stai dicendo è che i componenti che dovrebbero essere collegati in rete dovrebbero implementare una sorta di interfaccia come questa ?: void SetNetworkedVariable (nome stringa, valore NetworkedVariable); NetworkedVariable GetNetworkedVariable (nome stringa); Dove NetworkedVariable viene utilizzato a scopo di interpolazione e altri elementi della rete. Non so come identificare quali componenti implementano questo però. Potrei usare l'identificazione del tipo di runtime, ma mi sembra brutto.
Carter,
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.