Gli attori di un gioco dovrebbero essere responsabili di disegnare se stessi?


41

Sono molto nuovo nello sviluppo del gioco, ma non nella programmazione.

Sto (di nuovo) giocando con un gioco di tipo Pong usando l' canvaselemento JavaScript .

Ho creato un Paddleoggetto che ha le seguenti proprietà ...

  • width
  • height
  • x
  • y
  • colour

Ho anche un Pongoggetto che ha proprietà come ...

  • width
  • height
  • backgroundColour
  • draw().

Il draw()metodo attualmente sta reimpostando canvased è qui che è emersa una domanda.

Se l' Paddleoggetto ha un draw()metodo di responsabile per il suo disegno, o dovrebbe l' draw()del Pongoggetto è responsabile della redazione suoi attori (suppongo che è il termine corretto, per favore correggetemi se sono errato).

Ho pensato che sarebbe stato vantaggioso Paddledisegnare se stesso, mentre istanzio due oggetti, Playere Enemy. Se non fosse nelle Pong's draw(), avrei bisogno di scrivere codice simile due volte.

Qual è la migliore pratica qui?

Grazie.


Risposte:


49

Far disegnare gli attori non è un buon design, per due motivi principali:

1) viola il principio della singola responsabilità , poiché quegli attori presumibilmente avevano un altro lavoro da svolgere prima di inserire il codice di rendering in essi.

2) rende difficile l'estensione; se ogni tipo di attore implementa il proprio disegno e devi cambiare il modo in cui disegni in generale , potresti dover modificare molto codice. Evitare l'uso eccessivo dell'eredità può alleviarlo in una certa misura, ma non completamente.

È meglio che il tuo renderer gestisca il disegno. Dopotutto, ecco cosa significa essere un renderer. Il metodo di disegno del renderer dovrebbe prendere un oggetto "descrizione del rendering", che contiene tutto il necessario per renderizzare una cosa. Riferimenti a dati geometrici (probabilmente condivisi), trasformazioni specifiche dell'istanza o proprietà del materiale come colore, eccetera. Quindi lo disegna e non importa cosa dovrebbe "essere" quella descrizione di rendering.

I tuoi attori possono quindi mantenere una descrizione del rendering che creano da soli. Poiché gli attori sono in genere tipi di elaborazione logica, possono modificare le modifiche di stato nella descrizione del rendering in base alle esigenze, ad esempio quando un attore subisce un danno può impostare il colore della descrizione del rendering in rosso per indicare ciò.

Quindi puoi semplicemente iterare ogni attore visibile, aggiungere le loro descrizioni di rendering nel renderer e lasciarlo fare la sua cosa (in sostanza, potresti generalizzare ancora di più).


14
+1 per "codice di disegno del renderer", ma -1 per principio di responsabilità singola, che ha causato più danni che benefici ai programmatori . Ha l'idea giusta (separazione delle preoccupazioni) , ma il modo in cui è dichiarato ("ogni classe dovrebbe avere una sola responsabilità e / o solo una ragione per cambiare") è semplicemente sbagliato .
BlueRaja - Danny Pflughoeft il

2
-1 per 1), come ha sottolineato BlueRaja, questo principio è idealista, ma non pratico. -1 per 2) perché non rende l'estensione molto più difficile. Se devi cambiare l'intero motore di rendering, avrai molto più codice da modificare rispetto a quel poco che hai nel tuo codice attore. Preferirei actor.draw(renderer,x,y)di gran lunga renderer.draw(actor.getAttribs(),x,y).
corsiKa

1
Buona risposta! Bene, "Non devi violare il principio della responsabilità singola" non è nel mio decalogo, ma è comunque un buon principio da tenere in considerazione
FxIII

@glowcoder Non è questo il punto, è possibile mantenere l'attore.draw (), che è definito come {renderer.draw (mesh, attribs, transform); }. In OpenGL, mantengo struct RenderDetail { glVAO* vao; int shader; int texture; Matrix4f* transform; }più o meno per il mio renderer. In questo modo al renderer non importa cosa rappresentano i dati, ma li elabora. Sulla base di queste informazioni, posso anche decidere se ordinare l'ordine delle chiamate di disegno per ridurre le chiamate GPU complessive.
deceleratedcaviar

16

Il modello visitatore può essere utile qui.

Quello che puoi fare è avere un'interfaccia Renderer che sappia disegnare ogni oggetto e un metodo "draw te stesso" in ogni attore che determina quale metodo di rendering (specifico) chiamare, ad es.

interface Renderer {
    void drawPaddle(Player owner, Rectangle position);
    // Note: the Renderer chooses the Color based on which player the paddle belongs to

    // Also drawBackground, drawBall etc.
}

interface Actor {
    void draw(Renderer renderer);
}

class Paddle implements Actor {
    void draw(Renderer renderer) {
        renderer.drawPaddle(this.owner, this.getBounds());
    }
}

In questo modo è ancora facile effettuare il porting su un'altra libreria o framework grafico (una volta ho portato un gioco da Swing / Java2D a LWJGL ed ero molto contento di aver usato questo schema invece di passare un Graphics2Dgiro.)

C'è un altro vantaggio: il renderer può essere testato separatamente da qualsiasi codice attore


2

Nel tuo esempio vorresti che gli oggetti Pong implementassero draw () e si rendessero.

Anche se non noterai alcun guadagno significativo in un progetto di queste dimensioni, generalmente separare la logica di gioco e la loro rappresentazione visiva (rendering) è un'attività utile.

Con questo intendo dire che avresti i tuoi oggetti di gioco che possono essere update () 'd ma non hanno idea di come siano resi, si occupano solo della simulazione.

Quindi avresti una classe PongRenderer () che ha un riferimento a un oggetto Pong, e quindi si occupa del rendering della classe Pong (), questo potrebbe comportare il rendering dei Paddles o avere una classe PaddleRenderer per occuparsene.

La separazione delle preoccupazioni in questo caso è abbastanza naturale, il che significa che le tue classi possono essere meno gonfie, ed è più facile modificare il modo in cui le cose sono rese, non deve più seguire la gerarchia della tua simulazione.


-1

Se dovessi farlo con il Pongdisegno (), non dovrai duplicare il codice - semplicemente chiama la funzione Paddledel disegno () per ciascuno dei tuoi paddle. Quindi nel tuo Pongdisegno () avresti

humanPaddle.draw();
compPaddle.draw();

-1

Ogni oggetto dovrebbe contenere la propria funzione di disegno che dovrebbe essere chiamata dal codice di aggiornamento del gioco o dal codice di disegno. Piace

class X
{
  public void Draw( )
  {
     // Do something 
  } 
}

class Y
{
  public void Draw( )
   { // Do Something 
   } 
}

class Game
{
  X objX;
  Y objY;

  @Override
  public void OnDraw( )
  {
      objX.Draw( );
      objY.Draw( ); 
  } 
}

Questo non è un codice ma solo per mostrare come devono essere fatti gli oggetti di disegno.


2
Questo non è "Come si dovrebbe fare il disegno" ma un solo modo di farlo. Come menzionato nelle risposte precedenti, questo metodo presenta alcuni difetti e pochi benefici, mentre altri modelli hanno benefici e flessibilità significativi.
Troyseph,

Apprezzo, ma dato che doveva essere fornito un semplice esempio dei vari modi di disegnare attori nella scena, ho menzionato in questo modo.
user43609
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.