Questions sur la conception d'un jeu

Certaines choses ne sont pas encore claires dans ma tête...

a marqué ce sujet comme résolu.

Lu'!

Je n'ai qu'un point majeur à redire sur la conception proposée : pourquoi la map est elle en charge de s'afficher ? Ou, pour être plus précis, et le MVC là dedans ? Quel est le symptôme dans la conception : Map se retrouve avec deux responsabilités : déterminer les directions possibles pour divers éléments et leur caractère bouffable ou non (ce n'est déjà peut être plus de son ressort pour ce dernier point d'ailleurs) et l'affichage d'elle même et des bestioles. Nous avons deux responsabilité : viol du SRP.

Ici, le MVC est parfaitement appliquable ;) . Le tout est d'augmenter encore la séparation entre les éléments. La map, les fantômes et le pacman sont des observables, la vue principale et les représentations graphiques des éléments, des observeurs.

Second point, Partie pourrait encore être découpé et/ou renommé. On a bien envie d'avoir un contrôleur qui se charge de : provoquer les appels nécessaires vers le modèle lorsque le joueur appuie sur un bouton, dire au modèle qu'il peut faire sa tâche idle. Concrètement du côté de cette classe, on aurait :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void run(){
  while(true){
    if(event.poll()){
      switch(event){
      case UP : model.playerMovement(UP);
      //...
      }
    }
    model.idle();
    view.refresh();
  }
}

Sachant que ce idle côté modèle serait juste les actions des fantômes, etc. Finalement, le dernier point est que la vue (qui aura été notifiée au fil du tour par les observables) doit mettre son affichage à jour.

Dernier point, mineur cette fois, la majorité des int proposés sont en fait des naturels (unsigned).

J'ai encore du mal à mettre en place une vraie architecture MVC dans mon projet. Si j'ai bien compris, les acteurs seront des membres de la classe modèle. Il seront mis à jour par la classe contrôleur, et affichés par la classe vue.

Mais du coup comment mettre à jour et afficher un membre de modèle ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Dans Partie::effectuerTour()
controller.update(model); // model est donc un objet de la classe modèle
view.draw(model);

// Dans Controller::update(Model &model)
// en gros, le code de Ksass`Peuk
model.getPacman().update();

// Dans View::draw(const Model& model)
spritePacMan.setPosition(model.getPacmanPosition());
window.draw(spritePacMan);

Serait-ce donc quelque chose comme ça ? Et est-ce logique de faire passer une référence sur modèle non-constant pour le contrôleur mais constant pour la vue ?

A titre d'info générale, le MVC est essentiellement un modèle applicatif/Web, et il n'est habituellement pas employé dans le jeu vidéo. Les concepts typiques de l'architecture d'un jeu vidéo, c'est :

  • la séparation entre la mise à jour de l'état du jeu (la logique) et l'affichage ;
  • le modèle entité + composant, dans lequel un objet du jeu est une entité constituée de différents composants (composants sprites, son, logique, etc).

Dans ce modèle, à chaque update, on met à jour successivement tous les composants, et à chaque draw on affiche tous les composants graphiques. C'est comme ça que fonctionnent la plupart des jeux vidéo.

Bien sûr ce n'est pas une structure incompatible avec le MVC, c'est même relativement proche. Mais il est important de faire tous les affichages en même temps, et toutes les mises à jour en même temps, sous peine d'avoir des incohérences entre l'état du jeu et ce que le joueur peut visualiser.

Stranger

Si l'on veut faire du vrai jeu vidéo, il faudrait s'orienté vers de l'orienté donnée, qui est très loin de l'orienté objet. Ici vu la complexité du jeu et puisqu'il est a but d'apprentissage de l'orienté objet le MVC aurait pu prendre place.

Je le trouve cependant un peu lourd pour débuter alors que Loris ce posait encore des questions de cohésion basique. Mais si il est motivé alors oui c'est une bonne idée de prendre directement les bonne habitude.

J'ai encore du mal à mettre en place une vraie architecture MVC dans mon projet. Si j'ai bien compris, les acteurs seront des membres de la classe modèle. Il seront mis à jour par la classe contrôleur, et affichés par la classe vue.

Mais du coup comment mettre à jour et afficher un membre de modèle ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Dans Partie::effectuerTour()
controller.update(model); // model est donc un objet de la classe modèle
view.draw(model);

// Dans Controller::update(Model &model)
// en gros, le code de Ksass`Peuk
model.getPacman().update();

// Dans View::draw(const Model& model)
spritePacMan.setPosition(model.getPacmanPosition());
window.draw(spritePacMan);

Serait-ce donc quelque chose comme ça ? Et est-ce logique de faire passer une référence sur modèle non-constant pour le contrôleur mais constant pour la vue ?

Loris

Ton controleur serait charger de mettre a jour ton modèle Ton modèle représente les données, l'objet de la modification (Pacman, Ghost, Map) Tes vues représente l'affichage de tes données.

Ici partie serait controleur, Pacman, Ghost, Map des modèles et ta vue serait ta scène. Le tient se tiendrais a jour grâce au patron obeservateur

+0 -0

Je le trouve cependant un peu lourd pour débuter alors que Loris ce posait encore des questions de cohésion basique. Mais si il est motivé alors oui c'est une bonne idée de prendre directement les bonne habitude.

Sanoc

En fait le jeu fonctionne actuellement bien et il est (presque) terminé. Je suis venu ici car justement je n'étais pas fier de mon code, et oui je suis motivé à prendre directement de bonnes habitudes. ^^

Donc voilà ce que je retiens : il faut que les entités calculent toutes les informations nécessaires afin d'être dessiner, mais avec des valeurs relatives, afin de respecter le SRP. La part contrôleur va mettre à jour chaque élément. Et c'est à la part vue de calculer la position/taille absolue ces éléments et de tout dessiner.

Voici comment je peux résumer l'organisation actuelle qui s'approche d'un MVC :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Partie :

    membres modèles : pacMan, ghosts, map, scoreDisplay, etc...
    membres vue : window, texture
    membre contrôleur : event

    constructeur :
        création de la fenêtre
        chargement des textures
        création des modèles

    méthode de mise à jour (effectuerTour) :
        // contrôleur
        switch(event)
            si touche direction appuyée > pacMan.setDirection(...)

        mise à jour de tous les modèles
        différent testes (pacMan mange un dot, niveau terminé, collisions pacMan-ghost...)

        // vue
        on dessine tout (ici et seulement ici on utilisera tileSize, sceneWidth etc...)

Est-ce que ça vous paraît acceptable comme ça ? Et serait-ce nécessaire d'ajouter des classes pour séparer vue et contrôleur ?

Oui c'est plus ou moins le principe. Le tout est de bien respecter le domaine de compétence de chacun des membre de MVC, et de bien appliqué tes patrons observateurs.

Si tu veut directement prendre en main les bonne habitudes:

  • Essaye de comprendre le principe des patrons
    • 'commandes' que tu utilisera pour tes actions clavier
    • 'singleton' que tu appliquera sur certaines de tes classes.
  • Fait nous des diagramme UML

Sans diagramme UML on va avoir un mal de fou a t'aider a partir de maintenant. Car les listes c'est bien joli mais ca représente rien de concret.

+0 -0

Le singleton est de plus en plus présenté, à raison, comme un anti-pattern. Un (vieil) article, qui parle de ces bestioles : http://blog.emmanueldeloget.com/index.php?post/2006/10/26/25-etes-vous-atteint-de-singletonite .

Un autre point est que les créateurs et les utilisateurs de ce type de classe ont souvent la fâcheuse tendance à oublier qu'un Singleton doit être thread-safe et réentrant.

Le singleton est de plus en plus présenté, à raison, comme un anti-pattern. Un (vieil) article, qui parle de ces bestioles : http://blog.emmanueldeloget.com/index.php?post/2006/10/26/25-etes-vous-atteint-de-singletonite .

Un autre point est que les créateurs et les utilisateurs de ce type de classe ont souvent la fâcheuse tendance à oublier qu'un Singleton doit être thread-safe et réentrant.

Ksass`Peuk

Je ne connaissais pas cette opinion. Cependant, cet article le présente comme un patron abusé et non pas comme un mauvais patron. Il tend a informer les gens sur leurs utilisation abusive du patron et a leurs remettre le questionnement ENTIER nécessitant son utilisation.

Quant au thread-safe je ne sais pas comment cela ce passe en c++ mais en Java ça relève a 2-3 lignes de plus dans notre getInstance().

Merci tout de même. Je pense que j'aurais besoin de le relire pour bien le cerner et en éviter quelque abus.

+0 -0

Je ne connaissais pas cette opinion. Cependant, cet article le présente comme un patron abusé et non pas comme un mauvais patron. Il tend a informer les gens sur leurs utilisation abusive du patron et a leurs remettre le questionnement ENTIER nécessitant son utilisation.

Sanoc

J'ai pris volontairement un avis modéré. Il y a des avis plus virulents et aussi étayés sur le net ;) . Pour ma part, je ne crois pas que le singleton soit complètement à bannir mais je rencontre très rarement le cas où il est justifié.

Quant au thread-safe je ne sais pas comment cela ce passe en c++ mais en Java ça relève a 2-3 lignes de plus dans notre getInstance().

Sanoc

Hum à voir le code mais blinder le getInstance ne doit régler que la thread-safety à la création. Pour le reste, il faudra :

  • soit jouer de synchronized, si Java reste basique, ça se réglera à coups de mutexes auto-généré, les performances vont sûrement en pâtir, s'il va trop loin il peut tout casser (c'est rare, mais une erreur a encore été trouvée récemment dans le JMM),
  • soit faire sa synchro à la main comme un grand.

J'ai en fait un problème avec la mise à jour des acteurs. Dans Pac-Man, le système de jeu veut que lorsqu'un acteur atteint la limite de la scène, il se "téléporte" de l'autre côté de la scène et continue à avancer à l'infini.

Sauf que pour cela, les acteurs ont besoin de connaître la taille de la scène. Mais comment faire s'ils doivent être totalement indépendant de sceneWidth et sceneHeight ?

En ce qui me concerne, je verrais plutôt les choses comme : je donne un ordre au modèle, par exemple "déplace pacman vers la droite", le modèle regarde où se trouve pacman et ce qui se trouve à droite, s'il va vers un mur, il ne fait rien (il peut éventuellement signaler qu'un mouvement illégal a été fait pour faire un "bip" dégueux). Sinon, il dit à Pacman de se déplacer à la case voulue, et comme il connaît la carte, il peut faire le modulo.

En fait, je m'y prenais mal depuis le début. Jusqu'à là, la scène (le modèle) de mon jeu demandaient aux acteurs de se déplacer en leur disant 'acteur.update()'. Ainsi tous les acteurs se déplacent "automatiquement", comme s'ils connaissait parfaitement le monde dans lequel ils se trouvent.

En réalité, il faudrait que ces acteurs soient inconscients du monde qui les entoure : c'est à la scène de leur dire "va là, sauf s'il y a un mur ; et s'il y a une pièce, ajouter un point". La fonction update() des acteurs ne devrait pas gérer les collisions, juste un déplacement de l'acteur sans conditions.

Cela, me paraît un peu perturbant, car ce serait à la scène de gérer tous les comportements des acteurs avec le reste ?

Ah donc chaque entité est un modèle, j'avais compris que le modèle était l'ensemble des entités. Mais oui en fait, Scène en l’occurrence est plutôt un contrôleur (je commence à m'y perdre un peu).

Bref, je vais essayer de réécrire mon code de grâce à vos conseils. ;) Je passe pas encore en résolu car mon incompétence dans ce domaine me fait penser que j'aurais encore des choses à demander ici.

Tu peut implémenter ton code et peut être même qu'il marchera, car une mauvaise conception peut très bien être implémenter et être opérationnel.

Cependant, si tu souhaite réellement avancer dans tes compétences en conception je ne te conseillerai jamais assez de prendre quelques heures (c'est de l'ordre de 3-4 tout au plus) pour apprendre a faire un diagramme de classe de base. Ainsi la communauté pourra visualiser ton projet et te dire directement ou ce trouve le problème et comment le rectifier.

De plus un diagramme uml est toujours plus clair même pour soi, on vois clairement tout notre travail et toute notre réflexion coucher sur un papier et réduit en un simple schéma. On vois alors tout de suite les idiotie flagrante.

+1 -0

Rebonjour à vous,

J'ai donc essayé de réfléchir sur ce système MVC. Sachant que j'ai un objet window qui gère à la fois les événements et le rendu, j'ai pensé organiser le système d'une manière où c'est le contrôleur qui "englobe" le tout. Voilà à quoi ressemblerait la classe contrôleur (sans aucun lien avec le jeu en question) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Controller
{
    // membres
    private :
        Window window;
        Model model;
        View view;

    // fonction effectuée à chaque tour
    public :
        void update()
        {
            // on récupère l'évennement
            window.pollEvent(event);

            // on met à jour le modèle
            switch(event)
            {
                case foo: model.moveTruc(); break;
                case bar: model.setChose(); break;
            }
            model.moveMachin();

            // on demande à la vue d'afficher le modèle dans la fenêtre
            view.render(model, window);
        }
}

Avant de commencer à créer un diagramme de classes, j'aimerais savoir si vous, expérimentés que vous êtes, pensez que ce système est conceptuellement acceptable. :)

Je prendrais plutôt les éléments interne par référence à l'entrée du constructeur. La relation de composition est trop forte à mon avis. Une agrégation sans élément optionnel me semble plus valide.

Autre point dont je n'ai pas parlé. Pour le contrôleur, un système de signal/slot est encore plus flexible et encore plus adapté. Tu devrais jeter un oeil à ce genre de techniques.

D'accord merci. J'aurais donc ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Controller
{
    private :
        Model model;
        View view;

    // constructeur
    public :
        void Controller() :
            view(model)
        {
        }

    // fonction effectuée à chaque tour
        void update()
        {
            view.pollEvent(event);
            switch(event)
            {
                case foo: model.moveTruc(); break;
                case bar: model.setChose(); break;
            }
            model.moveMachin();

            view.render();
        }
}

J'ai finalement pensé que la vue puisse hériter de Window pour rendre la chose plus simple. Je ne me suis pas encore trop attardé sur les signaux/slots (de toute manière pour ce jeu, je n'ai que la direction de Pac-Man à écouter et à transmettre au modèle).

Est-ce que cette fois-ci, c'est (suffisemment) optimale ? ^^

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte