MODIFICA: Qui di seguito descrivo un sistema di messaggistica di eventi di base che ho usato ripetutamente .. E mi sono reso conto che entrambi i progetti scolastici sono open source e sul web. Puoi trovare la seconda versione di questo sistema di messaggistica (e un po 'di più) a http://sourceforge.net/projects/bpfat/ .. Divertiti e leggi sotto per una descrizione più dettagliata del sistema!
Ho scritto un sistema di messaggistica generico e l'ho introdotto in una manciata di giochi che sono stati rilasciati sulla PSP e in alcuni software applicativi di livello enterprise. Il punto del sistema di messaggistica consiste nel trasmettere solo i dati necessari per elaborare un messaggio o un evento, a seconda della terminologia che si desidera utilizzare, in modo che gli oggetti non debbano conoscere l'uno dell'altro.
A rapida corsa verso il basso della lista degli oggetti utilizzati per raggiungere questo obiettivo è qualcosa sulla falsariga di:
struct TEventMessage
{
int _iMessageID;
}
class IEventMessagingSystem
{
Post(int iMessageId);
Post(int iMessageId, float fData);
Post(int iMessageId, int iData);
// ...
Post(TMessageEvent * pMessage);
Post(int iMessageId, void * pData);
}
typedef float(*IEventMessagingSystem::Callback)(TEventMessage * pMessage);
class CEventMessagingSystem
{
Init ();
DNit ();
Exec (float fElapsedTime);
Post (TEventMessage * oMessage);
Register (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback* fpMethod);
Unregister (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback * fpMethod);
}
#define MSG_Startup (1)
#define MSG_Shutdown (2)
#define MSG_PlaySound (3)
#define MSG_HandlePlayerInput (4)
#define MSG_NetworkMessage (5)
#define MSG_PlayerDied (6)
#define MSG_BeginCombat (7)
#define MSG_EndCombat (8)
Ed ora un po 'di una spiegazione. Il primo oggetto, TEventMessage, è l'oggetto di base per rappresentare i dati inviati dal sistema di messaggistica. Di default avrà sempre l'ID del messaggio inviato, quindi se vuoi essere sicuro di aver ricevuto un messaggio che ti aspettavi, puoi farlo (in genere lo faccio solo nel debug).
Il prossimo è la classe di interfaccia che fornisce un oggetto generico per il sistema di messaggistica da utilizzare per la trasmissione durante le richiamate. Inoltre, questo fornisce anche un'interfaccia 'facile da usare' per Post() in diversi tipi di dati al sistema di messaggistica.
Dopodiché abbiamo il nostro Callback typedef, Simply put si aspetta un oggetto del tipo della classe di interfaccia e passerà lungo un puntatore TEventMessage ... Opzionalmente puoi rendere il parametro const ma Ive ha usato l'elaborazione di trickle up prima per cose come lo stack debugging e tale sistema di messaggistica.
Ultimo e al centro è l'oggetto CEventMessagingSystem. Questo oggetto contiene una serie di stack di oggetti di callback (o elenchi o code collegati o comunque che si desidera memorizzare i dati). Gli oggetti di callback, non mostrati sopra, devono mantenere (e sono definiti in modo univoco da) un puntatore all'oggetto così come il metodo da chiamare su quell'oggetto. Quando si registra() si aggiunge una voce sullo stack di oggetti sotto la posizione dell'array id del messaggio. Quando annulli la registrazione() rimuovi quella voce.
Questo è fondamentalmente .. Ora questo ha la clausola che tutto ciò che deve sapere su IEventMessagingSystem e l'oggetto TEventMessage ...ma questo oggetto non dovrebbe cambiare così spesso e passa solo le parti di informazione che sono vitali per la logica dettata dall'evento chiamato. In questo modo un giocatore non ha bisogno di sapere sulla mappa o sul nemico direttamente per l'invio di eventi. Un oggetto gestito può anche chiamare un'API in un sistema più grande, senza bisogno di sapere nulla a riguardo.
Ad esempio: quando un nemico muore, si desidera che riproduca un effetto sonoro. Supponendo che tu abbia un sound manager che eredita l'interfaccia IEventMessagingSystem, devi impostare un callback per il sistema di messaggistica che accetti un TEventMessagePlaySoundEffect o qualcosa del genere. Il Sound Manager dovrebbe quindi registrare questa richiamata quando gli effetti sonori sono abilitati (o annullare la registrazione della richiamata quando si desidera silenziare tutti gli effetti sonori per abilitare/disabilitare le abilità). Successivamente, l'oggetto nemico dovrebbe ereditare anche da IEventMessagingSystem, mettere insieme un oggetto TEventMessagePlaySoundEffect (sarebbe necessario MSG_PlaySound per il suo ID messaggio e quindi l'ID dell'effetto sonoro da riprodurre, sia esso un ID int o il nome del suono effetto) e chiama semplicemente Post (& oEventMessagePlaySoundEffect).
Ora questo è solo un design molto semplice senza implementazione. Se hai esecuzione immediata, non hai bisogno di bufferizzare gli oggetti TEventMessage (cosa che usavo principalmente nei giochi per console). Se ci si trova in un ambiente multi-thread, questo è un modo molto ben definito per oggetti e sistemi in esecuzione in thread separati per comunicare tra loro, ma si desidera conservare gli oggetti TEventMessage in modo che i dati siano disponibili durante l'elaborazione.
Un'altra modifica è per gli oggetti che necessitano solo di dati Post(), è possibile creare un set statico di metodi in IEventMessagingSystem in modo che non debbano ereditarli da essi (che viene utilizzato per facilità di accesso e capacità di callback , non -direttamente- necessario per le chiamate Post()).
Per tutte le persone che citano MVC, è uno schema molto valido, ma è possibile implementarlo in così tanti modi diversi e a diversi livelli. L'attuale progetto su cui sto lavorando professionalmente è un setup MVC circa 3 volte, c'è il MVC globale dell'intera applicazione e quindi progettiamo ogni MV e C è anche un pattern MVC autonomo. Quindi, quello che ho provato a fare qui è spiegato come creare una C che sia abbastanza generica da gestire praticamente qualsiasi tipo di M senza la necessità di entrare in una vista ...
Ad esempio, un oggetto quando "muore" potrebbe voler giocare un effetto sonoro .. Dovresti creare una struct per il Sound System come TEventMessageSoundEffect che eredita dal TEventMessage e aggiunge un ID effetto sonoro (sia esso un Int precaricato, o il nome del file sfx, ma sono tracciati nel tuo sistema). Quindi tutto l'oggetto deve solo mettere insieme un oggetto TEventMessageSoundEffect con il rumore della morte appropriato e chiamare Post (& oEventMessageSoundEffect); oggetto .. Supponendo che il suono non sia disattivato (cosa vorresti annullare la registrazione dei Sound Man.
MODIFICA: Per chiarire un po 'il commento seguente: Qualsiasi oggetto per inviare o ricevere un messaggio ha solo bisogno di conoscere l'interfaccia IEventMessagingSystem e questo è l'unico oggetto che EventMessagingSystem deve conoscere di tutti gli altri oggetti.Questo è ciò che ti dà il distacco.Qualsiasi oggetto che vuole ricevere un messaggio semplicemente Registra (MSG, Object, Callback) per Quindi, quando un oggetto chiama Post (MSG, Data), lo invia a EventMessagingSystem tramite l'interfaccia di cui è a conoscenza, l'EMS notificherà quindi ogni oggetto registrato dell'evento. Si può fare un MSG_PlayerDied che altri sistemi gestiscono, oppure il giocatore può chiamare MSG_PlaySound, MSG_Respawn, ecc. per lasciare che le cose in ascolto di quei messaggi agiscano su di loro. Posta (MSG, Data) come API astratta ai diversi sistemi all'interno di un motore di gioco.
Oh! Un'altra cosa che mi è stata indicata ... Il sistema che descrivo sopra si adatta al modello di Observer nell'altra risposta data. Quindi se vuoi una descrizione più generale per rendere il mio un po 'più sensato, questo è un breve articolo che gli dà una buona descrizione.
Spero che questo aiuti e divertiti!
+1 per la spiegazione approfondita, ma ho anche un commento: hai affermato che * un giocatore non ha bisogno di sapere sulla mappa * per inviare eventi ad esso, ma il tuo esempio implica che un nemico morente deve sapere su ogni altro parte del programma che deve essere notificato. Mi sarei aspettato che mandasse semplicemente una sorta di messaggio "Sono appena morto" e poi permetti al tuo sistema di messaggistica di informare gli ascoltatori interessati a questo evento (suono di riproduzione, punteggio di aggiornamento, ecc.). In questo modo sembra che ogni entità abbia bisogno di inviare un gruppo di messaggi per un singolo evento (suono di riproduzione, aumento del punteggio). O ho capito male? – Groo
@Groo Non sono riuscito ad accorciare abbastanza la mia risposta, quindi l'ho modificata nella mia risposta sopra. – James
Ciao amico, sono passati più di 5 anni dalla tua risposta, ma questo post è venuto fuori quando stavo cercando una semplice idea di pubsub, e devo dire che ho scaricato le fonti, oltre agli standard di codifica che ho Non sono abituato e il fatto che C++ sia avanzato un po 'dal 2005, il codice è molto molto interessante da ricercare e ho usato alcuni scheletri di EMS per il mio gioco in C#. Sembra davvero incredibile e difficile quello che voi tre ragazzi avete fatto, e spero di saperne di più! –