Separazione del disegno e della logica nei giochi


11

Sono uno sviluppatore che proprio ora sta iniziando a fare casino con lo sviluppo del gioco. Sono un ragazzo .Net, quindi ho fatto casino con XNA e ora sto giocando con Cocos2d per iPhone. La mia domanda è davvero più generale però.

Diciamo che sto costruendo un semplice gioco Pong. Avrei una Balllezione e una Paddlelezione. Proveniente dallo sviluppo del mondo degli affari, il mio primo istinto è di non avere alcun codice di disegno o di input in nessuna di queste classi.

//pseudo code
class Ball
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Move(){}
}

Nulla nella classe della palla gestisce l'input o si occupa del disegno. Avrei quindi un'altra classe, la mia Gameo la mia Scene.m(in Cocos2d) che avrebbe rinnovato la palla e, durante il ciclo di gioco, avrebbe manipolato la palla secondo necessità.

Il fatto è che, in molti tutorial sia per XNA che per Cocos2d, vedo uno schema come questo:

//pseudo code
class Ball : SomeUpdatableComponent
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Update(){}
   void Draw(){}
   void HandleInput(){}
}

La mia domanda è: è giusto? È questo il modello che le persone usano nello sviluppo del gioco? In qualche modo va contro tutto ciò a cui sono abituato, che la mia classe Ball faccia tutto. Inoltre, in questo secondo esempio, dove i miei Ballsanno come muoversi, come gestirò il rilevamento delle collisioni con Paddle? La Ballnecessità di conoscere il Paddle? Nel mio primo esempio, la Gameclasse avrebbe riferimenti sia al Balle Paddle, e poi spedire sia di quelli off a qualche CollisionDetectionmanager o qualcosa del genere, ma come faccio a trattare con la complessità dei vari componenti, se ogni singolo componente fa tutto da sé? (Spero di avere un senso .....)


2
Sfortunatamente è così che si fa in molti giochi. Non lo prenderei come significato, tuttavia è la migliore pratica. Molti dei tutorial che stai leggendo potrebbero essere rivolti ai principianti, quindi non inizieranno a spiegare al lettore modelli / vista / controller o schemi di giogo.
1711

@tenpn, il tuo commento sembra suggerire che non pensi che questa sia una buona pratica, mi chiedevo se potessi spiegare ulteriormente? Ho sempre pensato che fosse la sistemazione più adatta a causa di quei metodi contenenti codice che è probabilmente molto specifico per ogni tipo; ma ho poca esperienza me stesso, quindi sarei interessato a sentire i tuoi pensieri.
sebf

1
@sebf non funziona se ti piace la progettazione orientata ai dati e non funziona se ti piace la progettazione orientata agli oggetti. Vedi i principi solidi di
tenpn

@tenpn. L'articolo e il documento collegati sono molto interessanti, grazie!
settembre

Risposte:


10

La mia domanda è: è giusto? È questo il modello che le persone usano nello sviluppo del gioco?

Gli sviluppatori di giochi tendono a usare qualunque cosa funzioni. Non è rigoroso, ma ehi, viene spedito.

In qualche modo va contro tutto ciò a cui sono abituato, che la mia classe Ball faccia tutto.

Quindi, un linguaggio più generalizzato per quello che hai lì è handleMessages / update / draw. Nei sistemi che fanno un uso pesante dei messaggi (che ha pro e contro come ci si aspetterebbe) un'entità di gioco afferra qualunque messaggio si preoccupi, esegue la logica su quei messaggi e quindi si disegna da sola.

Si noti che quella chiamata draw () potrebbe in realtà non significare che l'entità chiama putPixel o qualunque altra cosa dentro di sé; in alcuni casi, potrebbe significare semplicemente aggiornare i dati che vengono successivamente sottoposti a polling dal codice responsabile del disegno. In casi più avanzati, "disegna" se stesso chiamando i metodi esposti da un renderer. Nella tecnologia su cui sto lavorando in questo momento, l'oggetto si disegnava usando le chiamate del renderer e quindi ogni frame il thread di rendering organizza tutte le chiamate fatte da tutti gli oggetti, ottimizza per cose come i cambiamenti di stato, e quindi estrae il tutto (tutto di ciò accade su un thread separato dalla logica di gioco, quindi otteniamo un rendering asincrono).

La delega di responsabilità spetta al programmatore; per un semplice gioco di pong, ha senso che ogni oggetto gestisca completamente il proprio disegno (completo di chiamate di basso livello). È una questione di stile e saggezza.

Inoltre, in questo secondo esempio, in cui la mia palla sa come muoversi, come gestirò il rilevamento delle collisioni con la paletta? La palla dovrebbe avere conoscenza della pagaia?

Quindi, un modo naturale per risolvere il problema qui è fare in modo che ogni oggetto prenda ogni altro oggetto come una sorta di parametro per il controllo delle collisioni. Questo si ridimensiona male e può avere risultati strani.

Avere ogni classe implementare una sorta di interfaccia Collidable e quindi avere un po 'di codice di alto livello che scorre semplicemente su tutti gli oggetti Collidable nella fase di aggiornamento del gioco e gestisce le collisioni, impostando le posizioni degli oggetti secondo necessità.

Ancora una volta, devi solo sviluppare un senso (o prendere una decisione) su come delegare la responsabilità di cose diverse nel tuo gioco. Il rendering è un'attività solitaria e può essere gestita da un oggetto nel vuoto; collisione e fisica generalmente non possono.

Nel mio primo esempio, la classe Game avrebbe riferimenti sia a Ball che a Paddle, quindi spedire entrambi a un manager CollisionDetection o qualcosa del genere, ma come posso gestire la complessità dei vari componenti, se ogni singolo componente lo fa tutto da soli?

Vedi quanto sopra per un'esplorazione di questo. Se sei in un linguaggio che supporta facilmente le interfacce (ad es. Java, C #), questo è facile. Se sei in C ++, questo può essere ... interessante. Sono favorevole all'utilizzo della messaggistica per risolvere questi problemi (le classi gestiscono i messaggi che possono, ignorano gli altri), alcune altre persone come la composizione dei componenti.

Ancora una volta, qualunque cosa funzioni ed è più semplice per te.


2
Oh, e ricorda sempre: YAGNI (non ne avrai bisogno) è molto importante qui. Puoi perdere un sacco di tempo cercando di trovare il sistema flessibile ideale per gestire ogni possibile problema e quindi non fare mai nulla. Voglio dire, se davvero volessi un buon backbone multiplayer per tutti i possibili risultati, useresti CORBA, giusto? :)
ChrisE,

5

Di recente ho realizzato un semplice gioco Space Invadors usando un "sistema di entità". È un modello che separa estremamente bene attributi e comportamenti. Mi ci sono volute alcune iterazioni per comprenderlo appieno, ma una volta progettati alcuni componenti, diventa estremamente semplice comporre nuovi oggetti utilizzando i componenti esistenti.

Dovresti leggere questo:

http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/

Viene aggiornato frequentemente da un ragazzo estremamente ben informato. È anche l'unica discussione sul sistema di entità con esempi di codice concreti.

Le mie iterazioni sono state le seguenti:

La prima iterazione aveva un oggetto "EntitySystem" che era come descrive Adam; tuttavia i miei componenti avevano ancora dei metodi: il mio componente "renderable" aveva un metodo paint () e il mio componente di posizione aveva un metodo move () e così via. Quando ho iniziato a perfezionare le entità mi sono reso conto che dovevo iniziare a passare il messaggio tra componenti e ordinare l'esecuzione degli aggiornamenti dei componenti .... troppo disordinato.

Quindi, sono tornato indietro e ho riletto il blog di T-machines. Ci sono molte informazioni nei thread dei commenti - e in essi sottolinea davvero che i componenti non hanno comportamenti - i comportamenti sono forniti dai sistemi di entità. In questo modo non è necessario passare messaggi tra i componenti e ordinare gli aggiornamenti dei componenti perché l'ordine è determinato dall'ordine globale di esecuzione del sistema. Ok. Forse è troppo astratto.

Ad ogni modo per l'iterazione n. 2 questo è ciò che ho raccolto dal blog:

EntityManager: funge da "database" del componente, che può essere interrogato per entità che contengono determinati tipi di componenti. Questo può anche essere supportato da un database in memoria per un accesso rapido ... vedi t-machine parte 5 per maggiori informazioni.

EntitySystem - Ogni sistema è essenzialmente solo un metodo che opera su un insieme di entità. Ogni sistema utilizzerà i componenti x, yey di un'entità per fare il suo lavoro. Quindi dovresti interrogare il gestore per le entità con i componenti x, ye z quindi passare il risultato al sistema.

Entità - solo un ID, come un lungo. L'entità è ciò che raggruppa un insieme di istanze di componenti in un'entità.

Componente - un insieme di campi .... nessun comportamento! quando inizi ad aggiungere comportamenti, inizia a diventare confuso ... anche in un semplice gioco di Space Invadors.

Modifica : a proposito, 'dt' è il tempo delta dall'ultima chiamata del ciclo principale

Quindi il mio ciclo principale di Invadors è questo:

Collection<Entity> entitiesWithGuns = manager.getEntitiesWith(Gun.class);
Collection<Entity> entitiesWithDamagable =
manager.getEntitiesWith(BulletDamagable.class);
Collection<Entity> entitiesWithInvadorDamagable = manager.getEntitiesWith(InvadorDamagable.class);

keyboardShipControllerSystem.update(entitiesWithGuns, dt);
touchInputSystem.update(entitiesWithGuns, dt);
Collection<Entity> entitiesWithInvadorMovement = manager.getEntitiesWith(InvadorMovement.class);
invadorMovementSystem.update(entitiesWithInvadorMovement);

Collection<Entity> entitiesWithVelocity = manager.getEntitiesWith(Velocity.class);
movementSystem.move(entitiesWithVelocity, dt);
gunSystem.update(entitiesWithGuns, System.currentTimeMillis());
Collection<Entity> entitiesWithPositionAndForm = manager.getEntitiesWith(Position.class, Form.class);
collisionSystem.checkCollisions(entitiesWithPositionAndForm);

All'inizio sembra un po 'strano, ma è incredibilmente flessibile. È anche molto facile da ottimizzare; per diversi tipi di componenti è possibile disporre di archivi dati di backup diversi per velocizzare il recupero. Per la classe 'form' è possibile supportarlo con un quadrifoglio per velocizzare l'accesso per il rilevamento delle collisioni.

Sono come te; Sono uno sviluppatore esperto ma non ho avuto esperienza nella scrittura di giochi. Ho trascorso un po 'di tempo a fare ricerche per dare schemi agli sviluppatori e questo ha attirato la mia attenzione. Non è in alcun modo l'unico modo per fare le cose, ma l'ho trovato molto intuitivo e robusto. Credo che il modello sia stato discusso ufficialmente nel libro 6 della serie "Game Programming Gems" - http://www.amazon.com/Game-Programming-Gems/dp/1584500492 . Non ho letto nessuno dei libri, ma ho sentito che sono di fatto il riferimento per la programmazione del gioco.


1

Non ci sono regole esplicite che devi seguire durante la programmazione dei giochi. Trovo entrambi i modelli perfettamente accettabili, purché segua lo stesso modello di design in tutto il gioco. Saresti sorpreso che ci siano molti più schemi di design per i giochi, molti dei quali non sono nemmeno in cima a OOP.

Ora, sulla mia preferenza personale, preferisco separare il codice per comportamento (una classe / thread da disegnare, un'altra da aggiornare), pur avendo oggetti minimalisti. Trovo più facile parallelizzare e spesso mi ritrovo a lavorare su un minor numero di file contemporaneamente. Direi che questo è più un modo procedurale di fare le cose.

Ma se vieni dal mondo degli affari, probabilmente ti senti più a tuo agio nel scrivere classi che sanno come fare tutto da sole.

Ancora una volta, ti consiglio di scrivere ciò che ritieni più naturale per te.


Penso che tu abbia frainteso la mia domanda. Sto dicendo che NON mi piacciono le lezioni che fanno tutto da sole. Caduto come una palla dovrebbe essere solo una rappresentazione logica di una palla, tuttavia non dovrebbe sapere nulla del ciclo di gioco, della gestione degli input, ecc ...
BFree

Ad essere onesti, nella programmazione aziendale se lo si riduce, ci sono due treni: oggetti business che caricano, persistono ed eseguono la logica su se stessi e DTO + AppLogic + Repository / Persitance Layer ...
Nate

1

l'oggetto 'Ball' ha attributi e comportamenti. Trovo che sia logico includere gli attributi e i comportamenti univoci dell'oggetto nella propria classe. Il modo in cui un oggetto Ball si aggiorna è diverso dal modo in cui una paletta si aggiorna, quindi ha senso che si tratti di comportamenti unici che garantiscono l'inclusione nella classe. Lo stesso con il disegno. Spesso ci sono differenze uniche nel modo in cui una pala viene disegnata contro una palla e trovo più facile modificare il metodo di disegno di un singolo oggetto per soddisfare queste esigenze piuttosto che elaborare valutazioni condizionali in un'altra classe per risolverle.

Pong è un gioco relativamente semplice in termini di numero di oggetti e comportamenti, ma quando entri in dozzine o centinaia di oggetti di gioco unici potresti trovare il tuo secondo esempio un po 'più semplice per modulare tutto.

Molte persone usano una classe di input i cui risultati sono disponibili per tutti gli oggetti di gioco tramite un servizio o una classe statica.


0

Penso che ciò a cui probabilmente sei abituato nel mondo degli affari sia la separazione tra astrazione e implementazione.

Nel mondo degli sviluppatori di gioco, di solito vuoi pensare in termini di velocità. Più oggetti hai, più si verificheranno i cambi di contesto che potrebbero rallentare le cose a seconda del carico corrente.

Questa è solo la mia speculazione dal momento che sono un aspirante sviluppatore di giochi ma uno sviluppatore professone.


0

No, non è "giusto" o "sbagliato", è solo una semplificazione.

Alcuni codici aziendali sono esattamente gli stessi, a meno che tu non stia lavorando con un sistema che separa forzatamente la presentazione e la logica.

Qui, un esempio di VB.NET, in cui la "logica aziendale" (aggiunta di un numero) è scritta in un gestore eventi della GUI - http://www.homeandlearn.co.uk/net/nets1p12.html

come posso gestire la complessità dei vari componenti, se ogni singolo componente fa tutto da solo?

Se e quando diventa così complesso, allora puoi scomporlo.

class Ball
{
    GraphicalModel ballVisual;
    void Draw()
    {
        ballVisual.Draw();
    }
};

0

Dalla mia esperienza nello sviluppo, non esiste un modo giusto o sbagliato di fare le cose, a meno che non ci sia una pratica accettata nel gruppo con cui lavori. Ho incontrato opposizione da parte degli sviluppatori che sono contrari a separare comportamenti, skin, ecc. E sono sicuro che diranno lo stesso della mia prenotazione a scrivere un corso che includa tutto ciò che è sotto il sole. Ricorda che non devi solo scriverlo, ma essere in grado di leggere il tuo codice domani e in una data futura, eseguirne il debug e possibilmente costruirlo su di esso nella prossima iterazione. Dovresti scegliere un percorso che funzioni per te (e il tuo team). Scegliere un percorso che altri usano (ed è controintuitivo per te) ti rallenterà.

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.