Les acteurs d'un jeu doivent-ils être responsables de se dessiner?

41

Je suis très nouveau dans le développement de jeux, mais pas dans la programmation.

Je joue (encore) avec un jeu de type Pong en utilisant l' canvasélément JavaScript .

J'ai créé un Paddleobjet qui a les propriétés suivantes ...

  • width
  • height
  • x
  • y
  • colour

J'ai aussi un Pongobjet qui a des propriétés telles que ...

  • width
  • height
  • backgroundColour
  • draw().

La draw()méthode consiste actuellement à réinitialiser le canvaset c’est là qu’une question a été soulevée.

Si l' Paddleobjet a une draw()méthode responsable de son dessin, ou si l' objet draw()de l' Pongobjet est responsable de dessiner ses acteurs (je suppose que c'est le terme correct, corrigez-moi si je me trompe).

Je pensais qu'il serait avantageux pour le Paddlede se dessiner lui-même, comme j'instancie deux objets, Playeret Enemy. S'il n'y avait pas dans les Pong« s draw(), je dois écrire un code similaire deux fois.

Quelle est la meilleure pratique ici?

Merci.

alex
la source
Question similaire: gamedev.stackexchange.com/questions/13492/…
MichaelHouse

Réponses:

49

Faire dessiner des acteurs eux-mêmes n'est pas une bonne conception, pour deux raisons principales:

1) cela viole le principe de responsabilité unique , car ces acteurs avaient vraisemblablement un autre travail à faire avant d’y avoir inséré le code.

2) cela rend l'extension difficile; si tous les types acteur met en œuvre son propre dessin, et vous devez changer la façon dont vous dessinez en général , vous pouvez avoir à modifier beaucoup de code. Éviter le recours excessif à l'héritage peut atténuer ce problème dans une certaine mesure, mais pas complètement.

Il est préférable que votre moteur de rendu gère le dessin. Après tout, c’est ce que cela signifie d’être un moteur de rendu. La méthode draw du rendu doit prendre un objet "render description", qui contient tout ce dont vous avez besoin pour rendre une chose. Références à des données géométriques (probablement partagées), à des transformations spécifiques à une instance ou à des propriétés matérielles telles que la couleur, etc. Il dessine ensuite cela, et se moque de ce que la description de rendu est supposée "être".

Vos acteurs peuvent alors conserver une description de rendu qu'ils créent eux-mêmes. Étant donné que les acteurs sont généralement des types de traitement logique, ils peuvent appliquer des modifications d'état à la description de rendu, si nécessaire. Par exemple, lorsqu'un acteur subit des dommages, il peut définir la couleur de sa description de rendu en rouge pour l'indiquer.

Ensuite, vous pouvez simplement itérer chaque acteur visible, mettre en file d'attente leurs descriptions de rendu dans le rendu, et le laisser faire son travail (vous pouvez, en gros, généraliser davantage).

Josh
la source
14
+1 pour "le code de dessin de rendu", mais -1 pour le principe de responsabilité unique, ce qui a causé plus de tort que de bien aux programmeurs . Elle a la bonne idée (séparation des préoccupations) , mais la formulation ("chaque classe ne devrait avoir qu'une responsabilité et / ou une seule raison de changer") est tout simplement fausse .
BlueRaja - Danny Pflughoeft
2
-1 pour 1), comme l'a souligné BlueRaja, ce principe est idéaliste, mais pas pratique. -1 pour 2) car cela ne rend pas l'extension beaucoup plus difficile. Si vous devez modifier l'intégralité de votre moteur de rendu, vous aurez beaucoup plus de code à modifier que le peu que vous avez dans votre code d'acteur. Je préférerais de beaucoup actor.draw(renderer,x,y)que renderer.draw(actor.getAttribs(),x,y).
CorsiKa
1
Bonne réponse! Eh bien "Vous ne devez pas violer le principe de responsabilité unique" ne fait pas partie de mon décalogue, mais reste un bon principe à prendre en compte
FxIII
@glowcoder Ce n'est pas l'essentiel, vous pouvez maintenir actor.draw (), qui est défini comme {renderer.draw (mesh, attribs, transform)); }. En OpenGL, je maintiens un struct RenderDetail { glVAO* vao; int shader; int texture; Matrix4f* transform; }plus ou moins pour mon rendu. De cette façon, le rendu ne se soucie pas de ce que les données représentent, il les traite simplement. Sur la base de ces informations, je peux alors également décider de trier l'ordre des appels de tirage afin de réduire le nombre total d'appels GPU.
deceleratedcaviar
16

Le modèle de visiteur peut être utile ici.

Ce que vous pouvez faire est d'avoir une interface de rendu qui sache dessiner chaque objet et une méthode "dessine-toi" dans chaque acteur qui détermine quelle méthode de rendu (spécifique) à appeler, par exemple:

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());
    }
}

De cette façon, il est toujours facile de porter sur une autre bibliothèque graphique ou un autre framework (j’avais jadis porté un jeu de Swing / Java2D vers LWJGL et j’étais très heureux d’avoir utilisé ce modèle au lieu de passer à un autre Graphics2D.)

Il y a un autre avantage: le moteur de rendu peut être testé séparément de tout code d'acteur.

finnw
la source
2

Dans votre exemple, vous voudriez que les objets Pong implémentent draw () et se rendent eux-mêmes.

Bien que vous ne remarquiez aucun gain significatif dans un projet de cette taille, la séparation de la logique de jeu et de leur représentation visuelle (rendu) est une activité utile.

J'entends par là que vos objets de jeu pourraient être update (), mais ils n'ont aucune idée de la façon dont ils sont rendus, ils ne sont concernés que par la simulation.

Ensuite, vous auriez une classe PongRenderer () avec une référence à un objet Pong, puis elle se chargerait du rendu de la classe Pong (). Cela pourrait impliquer le rendu des Paddles ou la création d’une classe PaddleRenderer.

La séparation des problèmes dans ce cas est assez naturelle, ce qui signifie que vos classes peuvent être moins lourdes et qu'il est plus facile de modifier le rendu des choses, il n'est plus nécessaire de suivre la hiérarchie de votre simulation.

Michael
la source
-1

Si vous deviez le faire dans le Pong'draw (), vous n'auriez pas besoin de dupliquer le code - appelez simplement la fonction Paddle' draw () pour chaque pagaie. Donc dans ton Pongtirage () tu aurais

humanPaddle.draw();
compPaddle.draw();
Benixo
la source
-1

Chaque objet doit contenir sa propre fonction de dessin qui doit être appelée par le code de mise à jour du jeu ou le code de tirage. Comme

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( ); 
  } 
}

Ce n'est pas un code mais juste pour montrer comment dessiner des objets.

utilisateur43609
la source
2
Ce n'est pas "comment dessiner devrait être fait" mais une manière simple de le faire. Comme mentionné dans les réponses précédentes, cette méthode présente des défauts et peu d'avantages, tandis que d'autres modèles ont des avantages et une flexibilité importants.
Troyseph
J'apprécie mais étant donné qu'un exemple simple devait être donné des différentes manières de dessiner des acteurs dans la scène, j'ai mentionné cette façon.
user43609