Comunicazione guidata dagli eventi in un motore di gioco: Sì o No?


62

Sto leggendo il codice di gioco completo e l'autore consiglia la comunicazione Event Driven tra oggetti e moduli di gioco.

Fondamentalmente, tutti gli attori di gioco viventi dovrebbero comunicare con i moduli chiave (fisica, intelligenza artificiale, logica di gioco, vista di gioco, ecc.) Tramite un sistema di messaggistica eventi interno. Ciò significa dover progettare un efficiente gestore di eventi. Un sistema mal progettato consuma i cicli della CPU, in particolare le piattaforme mobili.

È un approccio provato e raccomandato? Come devo decidere se usarlo?


Ciao James, grazie per la tua risposta. Mi piace, anche se il libro descrive la comunicazione Event Driven come un sistema davvero complesso che sembra consumare significativamente molte risorse della CPU.
Bunkai.Satori,

Inseriscilo in una risposta e aggiunto un link alla panoramica di alto livello di un sistema di messaggistica di eventi che ho fornito come risposta allo overflow dello stack. Godere! Ricorda solo che ciò che alcune persone considerano complesso non deve essere :)
James

È possibile controllare questa risposta come ben fatto utilizzando c ++ 11: stackoverflow.com/a/22703242/2929762
user2826084

libuvè recentemente emerso come una biblioteca di eventi di successo. (È in C. Node.js il suo caso d'uso più famoso.)
Anko,

Risposte:


46

Questa è un'espansione del mio commento a una risposta completa, come suggerito.

, chiaro e semplice. La comunicazione deve avvenire e mentre ci sono situazioni in cui "Ci siamo ancora?" è richiesto il polling di tipo, facendo controllare le cose per vedere se dovrebbero fare qualcos'altro in genere fa perdere tempo. Potresti invece farli reagire alle cose che gli viene detto di fare. Inoltre, un percorso di comunicazione ben definito tra oggetti / sistemi / moduli migliora significativamente le configurazioni parallele.

Ho fornito una panoramica di alto livello di un sistema di messaggistica eventi su StackTranslate.it . L'ho usato da scuola in titoli di gioco professionali e ora prodotti non di gioco, adattati al caso d'uso ogni volta ovviamente.

EDIT : per rispondere a una domanda di commento su come si fa a sapere quali oggetti devono ricevere il messaggio : gli oggetti stessi devono Requestessere informati sugli eventi. Il tuo EventMessagingSystem(EMS) avrà bisogno Register(int iEventId, IEventMessagingSystem * pOjbect, (EMSCallback)fpMethodToUseForACallback)sia di una corrispondenza Unregister(facendo una voce unica per un iEventIdpuntatore e callback fuori dell'oggetto). In questo modo, quando un oggetto vuole conoscere un messaggio, può farlo Register()con il sistema. Quando non ha più bisogno di conoscere gli eventi, può farloUnregister(). Chiaramente, vorresti un pool di questi oggetti di registrazione callback e un modo efficace per aggiungerli / rimuoverli dagli elenchi. (Di solito ho usato array di auto-ordinamento; un modo fantasioso per dire che tracciano le loro allocazioni tra una pila di pool di oggetti inutilizzati e array che spostano le loro dimensioni in posizione quando necessario).

EDIT : per un gioco completamente funzionante con un ciclo di aggiornamento e un sistema di messaggistica di eventi, potresti voler dare un'occhiata a un mio vecchio progetto scolastico . Si riferisce anche al post Stack Overflow collegato sopra.


Ciao James, poiché sto pensando di più al sistema di messaggistica interna degli eventi, penso che non debba aggiungere più costi al motore. Ad esempio, se ci sono 50 oggetti sullo schermo, ma solo 5 di loro dovrebbero cambiare il loro comportamento; secondo il sistema tradizionale, tutti e 50 gli obbiettivi dovrebbero verificare tutte le loro possibili azioni, per vedere se dovrebbero fare qualcosa. Tuttavia, con l'utilizzo della messaggistica degli eventi, verranno inviati solo 5 messaggi a quei 5 oggetti con specifico cambio di azione. Sembra un approccio che fa risparmiare tempo.
Bunkai.Satori,

Il modo in cui funziona il sistema collegato nella mia risposta sopra è che gli oggetti si registrano solo per ascoltare i messaggi che vogliono .. Lo useremmo a nostro vantaggio nei videogiochi in cui potrebbero esserci 100-200 oggetti in un livello, ma tu 'attiva' quelli con cui il giocatore può interagire direttamente, mantenendo il numero di cose in ascolto fino a circa 10 circa. Tutto sommato questo tipo di sistema dovrebbe essere "più intelligente" di un "ci siamo ancora?" sistema di polling, e dovrebbe ridurre il sovraccarico di un motore, almeno per quanto riguarda la comunicazione.
James,

C'è una cosa legata al sistema event driven che mi confonde: nel sistema tradizionale in cui ogni oggetto ha un set predefinito di metodi (OnUpdate (), OnMove (), OnAI (), OnCollisionCheck () ...) regolarmente chiamati, ogni oggetto è responsabile del controllo e della gestione del suo stato. Nel sistema guidato da eventi, ci deve essere una condizione di controllo del sistema principale di ogni oggetto e quindi l'invio di messaggi a quelli in cui è stato rilevato un evento. Per me, questo standardizza i possibili stati degli oggetti e limita la libertà di creare un comportamento unico dell'oggetto. È vero?
Bunkai.Satori,

Il modello Observer è la tipica alternativa al polling. en.wikipedia.org/wiki/Observer_pattern
Ricket

1
@ hamlin11 Sono contento che queste informazioni aiutino ancora le persone :) Per quanto riguarda la registrazione, se ciò accade un po ', ricorda che vorrai ottimizzarlo per la velocità, avere un pool di oggetti di registrazione da cui attingere, ecc.
James

22

La mia opinione è che dovresti semplicemente iniziare a creare il tuo gioco e implementare ciò che ti piace. Mentre lo fai ti ritroverai ad utilizzare MVC in alcuni luoghi, ma non in altri; eventi in alcuni luoghi, ma non in altri; componenti alcuni luoghi, ma eredità altri; design pulito in alcuni luoghi e design croccante in altri.

E va bene, perché quando hai finito avrai effettivamente un gioco e un gioco è molto più bello di un sistema di passaggio di messaggi.


2
Ciao Joe, grazie per la tua risposta, mi piace. Credi che non sia necessario spingere artificialmente qualsiasi approccio. Dovrei progettare le applicazioni come penso funzionerebbero, e se qualcosa non funziona in seguito, la rifarò semplicemente.
Bunkai.Satori,

Questo è il motivo per cui esiste il sistema. Molte persone hanno fatto esattamente ciò che hai detto, sono stato testimone dell'incubo e hanno affermato che deve esserci un modo migliore. Puoi ripetere i loro errori o imparare da essi e magari avanzare di più.
user441521

16

Una domanda migliore è: quali alternative ci sono? In un sistema così complesso con moduli adeguatamente suddivisi per fisica, intelligenza artificiale, ecc., In quale altro modo è possibile orchestrare questi sistemi?

Il passaggio dei messaggi sembra essere la soluzione "migliore" a questo problema. Non riesco a pensare alle alternative in questo momento. Ma ci sono molti esempi di passaggi di messaggi in pratica. In effetti, i sistemi operativi utilizzano il passaggio dei messaggi per molte delle loro funzioni. Da Wikipedia :

I messaggi sono anche comunemente usati nello stesso senso come mezzo di comunicazione tra processi; l'altra tecnica comune è stream o pipe, in cui i dati vengono inviati invece come una sequenza di elementi di dati elementari (la versione di livello superiore di un circuito virtuale).

(La comunicazione tra processi è la comunicazione tra processi (ovvero istanze di programmi in esecuzione) all'interno dell'ambiente del sistema operativo)

Quindi, se è abbastanza buono per un sistema operativo, probabilmente è abbastanza buono per un gioco, giusto? Ci sono anche altri vantaggi, ma lascerò che l' articolo di Wikipedia sul passaggio dei messaggi faccia la spiegazione.

Ho anche posto una domanda su Stack Overflow, "Strutture di dati per il passaggio di messaggi all'interno di un programma?" , che potresti voler rileggere.


Ciao Ricket, grazie per la tua risposta. Hai chiesto quali altri modi potrebbero essere utilizzati per consentire agli oggetti di gioco di comunicare tra loro. Quello che mi viene in mente sono le chiamate ai metodi diretti. È corretto, che non ti dà così tanta flessibilità, ma d'altra parte, evita la generazione di messaggi di eventi, passaggi, letture, ecc. Parliamo ancora di piattaforme mobili, non di giochi di fascia alta. Il sistema operativo utilizza in modo avido il sistema di messaggistica interno. Tuttavia, non è progettato per funzionare in tempo reale, dove anche piccoli ritardi possono causare interruzioni.
Bunkai.Satori,

Vorrei tenere aperta questa domanda per un po '. Vorrei raccogliere più opinioni, prima di chiuderlo.
Bunkai.Satori

Le chiamate al metodo diretto generalmente comportano l'accoppiamento delle classi che si chiamano a vicenda. Questo non va bene per un sistema separato in componenti che dovrebbero essere intercambiabili. Esistono alcuni modelli che possono facilitare chiamate di metodo dirette con minore coesione (ad esempio la parte "controller" di MVC serve sostanzialmente a questo scopo per facilitare le query di visualizzazione del modello, a meno che non vengano accoppiate) ma in generale, il passaggio di messaggi è l'unico modo per un sistema con zero accoppiamento tra sottosistemi (o almeno l'unico modo che conosco).
Ricket,

Ah, quindi ora abbiamo iniziato a discutere di schemi. Ho sentito parlare di questo approccio di programmazione. Ora, comincio a capire quali sono i modelli. È completamente diverso il design dell'applicazione. Controlli masivelly oggetti, mentre usi lo stesso codice per controllare ogni oggetto. Dopo aver verificato l'oggetto, si impostano i relativi attributi di conseguenza.
Bunkai.Satori,

1
La domanda "Strutture dati ..." ha una risposta INCREDIBILE. Direi che la tua risposta selezionata e l'articolo di accompagnamento di Nebula3 sono la migliore descrizione dell'architettura del motore di gioco delle loro dimensioni che io abbia mai visto.
deft_code,

15

I messaggi generalmente funzionano bene quando:

  1. La cosa che invia il messaggio non importa se viene ricevuto.
  2. Non è necessario che il mittente riceva una risposta immediata dal destinatario.
  3. Possono esserci più ricevitori che ascoltano un singolo mittente.
  4. I messaggi verranno inviati di rado o imprevedibilmente. (In altre parole, se ogni oggetto ha bisogno di ricevere un messaggio di "aggiornamento" in ogni singolo frame, i messaggi non ti stanno davvero acquistando molto.)

Più le tue esigenze corrispondono a tale elenco, migliore sarà la corrispondenza dei messaggi. A livello molto generale, sono abbastanza buoni. Fanno un buon lavoro nel non sprecare i cicli della CPU solo per fare nulla a differenza del polling o di altre soluzioni più globali. Sono fantastici nel disaccoppiare parti di una base di codice, il che è sempre utile.


4

Grandi risposte finora da James e Ricket, rispondo solo per aggiungere una nota di cautela. La comunicazione basata sul passaggio di messaggi / eventi è certamente uno strumento importante nell'arsenale dello sviluppatore, ma può essere facilmente abusata. Quando hai un martello, tutto sembra un chiodo e così via.

Assolutamente, 100%, dovresti pensare al flusso di dati attorno al tuo titolo ed essere pienamente consapevole di come le informazioni passano da un sottosistema a un altro. In alcuni casi il passaggio dei messaggi è chiaramente il migliore; in altri, può essere più appropriato che un sottosistema operi su un elenco di oggetti condivisi, consentendo ad altri sottosistemi di operare anche sullo stesso elenco condiviso; e molti altri metodi inoltre.

In ogni momento dovresti essere consapevole di quali sottosistemi devono accedere a quali dati e quando devono accedervi. Ciò influisce sulla tua capacità di parallelizzare e ottimizzare, oltre a aiutarti a evitare i problemi più insidiosi quando parti diverse del tuo motore diventano troppo strettamente intrecciate. È necessario pensare a ridurre al minimo la copia non necessaria dei dati e a come il layout dei dati influisce sull'utilizzo della cache. Tutto ciò ti guiderà alla migliore soluzione in ogni caso.

Detto questo, praticamente ogni gioco su cui abbia mai lavorato ha un solido sistema di notifica / trasmissione di messaggi asincroni. Ti consente di scrivere codice gestibile breve ed efficiente, anche di fronte a esigenze progettuali complesse e in rapida evoluzione.


+1 per buone informazioni aggiuntive. Ciao MrCranky, grazie. Secondo quello che dici, vale la pena prendere in considerazione il sistema di messaggistica degli eventi anche per i giochi mobili. Tuttavia, la pianificazione non deve essere trascurata e deve essere ben considerato dove verrà utilizzato il sistema di messaggistica.
Bunkai.Satori,

4

Bene, so che questo post è piuttosto vecchio, ma non ho potuto resistere.

Di recente ho costruito un motore di gioco. Utilizza le librerie di parti 3d per il rendering e la fisica, ma ho scritto la parte principale, che definisce ed elabora le entità e la logica di gioco.

Il motore segue sicuramente un approccio tradizionale. Esiste un ciclo di aggiornamento principale che chiama la funzione di aggiornamento per tutte le entità. Le collisioni vengono segnalate direttamente per richiamata sulle entità. Le comunicazioni tra entità vengono effettuate tramite puntatori intelligenti scambiati tra entità.

Esiste un sistema di messaggi primitivo, che elabora solo un piccolo gruppo di entità nei messaggi del motore. È preferibile che tali messaggi vengano elaborati al termine di un'interazione di gioco (ad esempio, la creazione o la distruzione di un'entità) perché possono interferire con l'elenco di aggiornamento. Quindi, alla fine di ogni ciclo di gioco, viene consumato un piccolo elenco di messaggi.

Nonostante il primitivo sistema di messaggi, direi che il sistema è in gran parte "basato su loop di aggiornamento".

Bene. Dopo aver usato questo sistema, penso che sia molto semplice, veloce e ben organizzato. La logica del gioco è visibile e autonoma all'interno delle entità, non dinamica come una quewe di messaggi. Davvero non lo farei basato su eventi perché, a mio avviso, i sistemi di eventi introducono complessità inutili nella logica di gioco e rendono il codice di gioco molto difficile da capire e da debug.

Ma penso anche che un puro sistema di "aggiornamento basato su loop" come il mio abbia anche alcuni problemi.

Ad esempio, in alcuni momenti, un'entità può trovarsi in uno stato "non fare nulla", potrebbe essere in attesa che il giocatore si avvicini o qualcos'altro. Nella maggior parte dei casi, l'entità brucia il tempo del processore per nulla ed è meglio spegnere l'entità e accenderla quando si verifica un determinato evento.

Quindi, nel mio prossimo motore di gioco, adotterò un approccio diverso. Le entità si registreranno per le operazioni del motore, come l'aggiornamento, il disegno, il rilevamento delle collisioni e così via. Ognuno di questi eventi avrà elenchi separati di interfacce di entità per le entità effettive.


Scoprirai che le entità diventano basi di mostri che diventano difficili da gestire con qualsiasi tipo di complessità nel gioco. Finirai per accoppiarli insieme e farà schifo per aggiungere o modificare funzionalità. Rompendo le funzionalità in piccoli componenti, sarà più facile mantenerle e aggiungerle. Quindi la domanda diventa: come comunicano questi componenti? La risposta sono gli eventi di un componente che aumentano le funzioni di un altro mentre passano dati primitivi negli argomenti.
user441521

3

Sì. È un modo molto efficiente per i sistemi di gioco di comunicare tra loro. Gli eventi ti aiutano a disaccoppiare molti sistemi e consentono anche di compilare le cose separatamente senza conoscere l'esistenza reciproca. Ciò significa che le tue classi possono essere più facilmente prototipate e i tempi di compilazione sono più rapidi. Ancora più importante, si finisce con un design a codice piatto invece di un pasticcio di dipendenze.

Un altro grande vantaggio degli eventi è che possono essere facilmente trasmessi in streaming su una rete o su un altro canale di testo. Puoi registrarli per una riproduzione successiva. Le possibilità sono infinite.

Un altro vantaggio: è possibile avere diversi sottosistemi in ascolto per lo stesso evento. Ad esempio, tutte le tue viste remote (giocatori) possono iscriversi automaticamente all'evento di creazione di entità e generare entità su ciascun cliente con poco lavoro da parte tua. Immagina quanto più lavoro sarebbe se non usassi gli eventi: dovresti effettuare Update()chiamate da qualche parte o forse chiamare view->CreateEntitydalla logica di gioco (dove la conoscenza della vista e quali informazioni richiede non appartengono). È difficile risolvere quel problema senza eventi.

Con gli eventi ottieni una soluzione elegante e disaccoppiata che supporta un numero infinito di oggetti di tipo illimitato che possono semplicemente iscriversi agli eventi e fare le loro cose quando succede qualcosa nel tuo gioco. Ecco perché gli eventi sono fantastici.

Maggiori dettagli e un'implementazione qui .


Grandi punti, anche se unilaterali. Sono sempre sospettoso di generalità: Ci sono anti- -use-casi per gli eventi?
Anko,

1
Punti anti-uso: quando devi assolutamente avere una risposta diretta. Quando qualunque cosa tu voglia fare su qualche evento viene sempre eseguita direttamente e nello stesso thread. Quando non c'è assolutamente alcun ritardo esterno che può ritardare il completamento delle chiamate. Quando hai solo dipendenze lineari. Quando raramente fai più cose contemporaneamente. Se guardi node.js, tutte le chiamate io sono basate sugli eventi. Node.js è un luogo in cui gli eventi sono stati implementati correttamente al 100%.
user2826084

Il sistema di eventi non deve essere asincrono. È possibile utilizzare le coroutine per simulare l'asincronizzazione, ottenendo al contempo la sincronizzazione quando necessario.
user441521

2

Da quello che ho visto, non sembra molto comune avere un motore completamente basato sulla messaggistica. Naturalmente, ci sono sottosistemi che si prestano molto bene a un tale sistema di messaggistica, come la rete, la GUI e forse altri. In generale, tuttavia, ci sono alcuni problemi a cui posso pensare:

  • I motori di gioco gestiscono grandi quantità di dati.
  • I giochi devono essere veloci (20-30 FPS dovrebbero essere il minimo).
  • Spesso è necessario sapere se è stato fatto qualcosa o quando sarà fatto.

Con l'approccio comune degli sviluppatori di giochi "deve essere il più efficiente possibile" tale sistema di messaggistica non è quindi così comune.

Tuttavia, sono d'accordo che dovresti semplicemente andare avanti e provarlo. Sicuramente ci sono molti vantaggi con un tale sistema e la potenza di calcolo è oggi disponibile a buon mercato.


Non so se sono d'accordo con questo. L'alternativa è il polling che è dispendioso. Rispondere solo agli eventi è il più efficiente. Sì, ci saranno ancora alcuni sondaggi che accadranno e genereranno eventi, ma ci sono anche un buon numero di cose orientate agli eventi.
user441521

Non sono d'accordo neanche con questo. La quantità di motori di giochi di dati gestiti è in gran parte irrilevante, poiché la messaggistica di eventi è progettata per la comunicazione sottosistema sottosistema. Gli oggetti di gioco non devono comunicare direttamente con altri oggetti di gioco (sebbene i gestori di eventi personalizzati negli oggetti possano costituire un'eccezione). A causa di questo numero relativamente piccolo di sottosistemi e delle loro aree di "interesse", il costo delle prestazioni di una dorsale di messaggistica ben progettata, in un motore multi-thread è trascurabile.
Ian Young,
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.