2010-02-09 11 views
21

Sto progettando un gioco semplice che utilizza la fisica Java 2D e newtoniana. Attualmente il mio principale "gioco loop" sembra qualcosa di simile:Progettazione del gioco in modalità OO

do { 
    for (GameEntity entity : entities) { 
    entity.update(gameContext); 
    } 

    for (Drawable drawable : drawables) { 
    drawable.draw(graphics2d); 
    } 
} while (gameRunning); 

quando un'entità è incaricato di aggiornare se stesso che consente di regolare la sua velocità e la posizione sulla base delle attuali forze applicate ad esso. Tuttavia, ho bisogno che le entità mostrino altri comportamenti; per esempio. se un "cattivo" viene colpito da un giocatore, l'entità dovrebbe essere distrutta e rimossa dal mondo di gioco.

La mia domanda: Qual è il modo migliore per ottenere questo in modo orientato agli oggetti? Tutti gli esempi che ho visto fino ad ora incorporano il ciclo di gioco in una classe di Dio chiamata qualcosa come Game, che esegue i passaggi: rileva collisioni, check-if-bad-guy-killed, check-if-player-killed, repaint , ecc. e incapsula tutto lo stato del gioco (vite rimanenti, ecc.). In altre parole, è molto procedurale e tutta la logica è nella classe di gioco. Qualcuno può consigliare un approccio migliore?

Qui ci sono le opzioni che ho pensato di finora:

  • passare un GameContext a ciascuna entità da cui l'entità può rimuovere se stesso, se richiesto o aggiornare lo stato del gioco (ad esempio, per "non in funzione", se il giocatore viene ucciso).
  • Registrare ciascun GameEntity come ascoltatore nella classe centrale Game e adottare un approccio orientato agli eventi; per esempio. una collisione comporterebbe l'accensione di CollisionEvent nei due partecipanti alla collisione.
+2

Credo di aver trovato il bug - è 'while (gameRunning)' si vuole lì –

risposta

14

ho lavorato a stretto contatto con due motori di gioco commerciali e seguono uno schema simile:

  • oggetti rappresentano componenti o aspetti di un'entità di gioco (come, renderable, qualunque sia fisico), piuttosto che l'intera entità . Per ogni tipo di componente c'è una lista gigante di componenti, uno per ogni istanza di entità che ha il componente.

  • Il tipo di 'entità di gioco' è solo un ID univoco. Ogni lista gigante di componenti ha una mappa per cercare il componente (se esiste) che corrisponde a un ID entità.

  • Se un componente richiede un aggiornamento, viene richiamato da un servizio o da un oggetto di sistema. Ogni servizio viene aggiornato direttamente dal ciclo di gioco. In alternativa, è possibile chiamare i servizi da un oggetto dello scheduler che determina l'ordine di aggiornamento da un grafico di dipendenza.

Qui sono i vantaggi di questo approccio:

  • È possibile combinare liberamente le funzionalità senza scrivere nuove classi per ogni combinazione o utilizzando l'eredità complessa alberi.

  • Non c'è quasi alcuna funzionalità che si può assumere quasi tutti i giochi entità che si potrebbe mettere in un gioco di classe base entità (che cosa fa una luce hanno in comune con una macchina da corsa o di un cielo-box ?)

  • l'ID-to-componente look-up potrebbero sembrare costoso, ma i servizi stanno facendo maggior parte del lavoro ad alta intensità da scorrendo tutti i componenti di un determinato tipo. In questi casi, funziona meglio per memorizzare tutti i dati necessari in un unico elenco ordinato.

+0

@ Evan: Grazie per la risposta. Volevo chiederti: come gestiresti la rimozione di un'entità in questa istanza? Ad esempio, si supponga di avere un aspetto Collidable e CollisionManager rileva una collisione, che dovrebbe causare la rimozione dell'entità. Presumibilmente, il CollisionManager fa riferimento solo all'aspetto Collidable dell'entità, e quindi quale approccio prendi per rimuovere ** tutti ** gli aspetti dell'entità (Drawable, Collidable, etc) dai vari elenchi? – Adamski

+1

Il pezzo mancante che non ho menzionato sono messaggi o eventi. Ogni sistema può abbonarsi a qualsiasi tipo di messaggio o messaggio. Il sistema fisico potrebbe pubblicare un messaggio "Collide" a cui un sistema di gioco può essere abbonato e può inviare un messaggio di "eliminazione entità" in risposta. Un altro sistema responsabile della creazione e dell'eliminazione delle entità potrebbe sottoscrivere il tipo di messaggio dell'entità di eliminazione. Questo è molto più lavoro delle chiamate dirette alle funzioni, ma è tutto nel nome del disaccoppiamento. –

6

In un particolare motore su cui ho lavorato, abbiamo disaccoppiato la logica dalla rappresentazione grafica e poi avevamo oggetti che avrebbero inviato messaggi per quello che volevano fare. L'abbiamo fatto in modo che potessimo avere giochi esistenti su una macchina locale o collegati in rete e che non fossero distinguibili l'uno dall'altro da un punto di vista del codice. (Modello di comando)

Abbiamo anche eseguito la modellazione fisica effettiva in un oggetto separato che potrebbe essere modificato al volo. Questo ci permette di risolvere facilmente problemi di gravità, ecc.

Abbiamo fatto un uso pesante del codice basato sugli eventi (pattern listener) e un sacco di timer.

Ad esempio, avevamo una classe base per un oggetto intersecabile che poteva ascoltare l'evento di collisione. Lo abbiamo sottoclassato in una scatola della salute. In caso di collisione, se è stata colpita da un'entità giocatore, ha inviato al Collider un comando che avrebbe dovuto ottenere salute, inviato un messaggio per trasmettere un suono a tutti quelli che potevano sentirlo, collisioni disattivate, attivato l'animazione per rimuovere la grafica da il grafico della scena e imposta un timer per riprendersi più tardi. Sembra complicato, ma in realtà non lo era.

Se ricordo (e sono passati 12 anni), avevamo la nozione astratta di scene, quindi un gioco era una sequenza di scene. Al termine di una scena, è stato generato un evento che normalmente inviava un comando per rimuovere la scena corrente e avviarne un'altra.

+0

Grazie - Alcuni buoni consigli qui. – Adamski

+0

Probabilmente sarebbe una buona idea usare qui il modello di mediatore per facilitare l'invocazione/registrazione/gestione dei dati e i dati, altrimenti dovresti accoppiare tra loro tutti i sottosistemi e le entità di gioco. – dvide

3

Non sono d'accordo sul fatto che, avendo una classe di gioco principale, tutta la logica deve accadere in quella classe.

semplificazione eccessiva qui imitando tuo esempio solo per fare il mio punto:

mainloop: 
    moveEntities() 
    resolveCollisions() [objects may "disappear"/explode here] 
    drawEntities()  [drawing before or after cleanEntitites() ain't an issue, a dead entity won't draw itself] 
    cleanDeadEntities() 

Ora avete una classe Bubble:

Bubble implements Drawable { 

handle(Needle needle) { 
    if (needle collide with us) { 
     exploded = true; 
    } 
} 

draw (...) { 
    if (!exploded) { 
     draw(); 
    } 
    } 
} 

Quindi, certo, c'è un ciclo principale che si occupa di passare i messaggi tra le entità ma la logica relativa alla collisione tra una bolla e un ago non è sicuramente nella classe principale del gioco.

Sono abbastanza sicuro che anche nel tuo caso tutta la logica relativa al movimento non sta accadendo nella classe principale.

Quindi sono d'accordo con la sua dichiarazione, che è scritto in grassetto, che "tutta la logica avviene nella classe principale".

Questo semplicemente non è corretto.

Per quanto riguarda il buon design: se puoi facilmente fornire un'altra "visione" del tuo gioco (come, ad esempio, una mini-mappa) e se puoi facilmente codificare un "fotogramma per fotogramma perfetto", allora il tuo il design probabilmente non è poi così male (cioè registrando solo gli input e il momento in cui sono accaduti, dovresti essere in grado di ricreare il gioco esattamente come è stato giocato. Ecco come Age of Empires, Warcraft 3, ecc. replay: sono solo gli input dell'utente e l'ora in cui sono avvenuti che viene registrato [è anche il motivo per cui i file di replay sono tipicamente così piccoli]).

+0

Grazie. Non stavo sostenendo di eliminare del tutto il ciclo del gioco, semplicemente mantenendo il più semplice possibile, quindi quello che hai detto ha senso. – Adamski

1

Questo game è stato un esperimento per mantenere separati il ​​modello e la vista. Usa il pattern di osservatore per notificare la vista (o le viste) dei cambiamenti nello stato del gioco, ma gli eventi potrebbero forse offrire un contesto più ricco. Originariamente, il modello era guidato dall'input da tastiera, ma la separazione facilita l'aggiunta dell'animazione guidata dal timer.

Addendum: È necessario mantenere il modello del gioco a parte, ma è possibile ri-fattore che il modello in tante classi, come richiesto.

2

Scrivo i miei motori (grezzo & sporco), ma un motore precostruito con un modello OO decente, è Ogre. Consiglierei di dargli un'occhiata (è un modello di oggetto/API). L'assegnazione del nodo è un po 'funky, ma ha perfettamente senso, più lo si guarda. È anche estremamente ben documentato con un sacco di esempi di giochi di lavoro.

Ho imparato un paio di trucchi da me stesso.

+0

Grandi cose - Verificherò. – Adamski

Problemi correlati