Partage
  • Partager sur Facebook
  • Partager sur Twitter

Problème de polymorphisme : appel de fonctions

Sujet résolu
    16 juillet 2019 à 1:44:37

    Bonjour,

    Je code un petit jeux assez rudimentaire pour le moment, j'ai créer une classe "Entity" qui me permettra par la suite d'utiliser le polymorphisme sur les classes hérité, à savoir "Player" pour l'instant. Mon problème (enfin j'en ai plusieurs :/) est que dans le main j'utilise une fonction de Entity qui devrait être différente selon les classes hérite (Player devrait renvoyé un enum ["SURVIVOR"], alors qu'une Entity vide un enum ["VOID"]). Sinon je ne comprend pas pourquoi je ne peux pas "additioner les fonctions" (ex : monde.getJoueur().getPv() ) ici, voici le code en question:

    Entity :

    enum e_id_Entity
    {
        VOID,
        SURVIVOR,
        MADMAN,
        ZOMBIE,
        SYRINGE,
    };
    class Entity
    {
    
    
    
    public :
    
        Entity();
        ~Entity();
        void beAttack(int damage);
        void beHeal(int heal);
        int getId();
    };
    
    #include "Entity.h"
    
    Entity::Entity()
    {
    
    }
    Entity::~Entity()
    {
    
    }
    int Entity::getId()
    {
        return VOID;
    }
    

    Player :

    #include "Entity.h"
    
    class Player : public Entity
    {
    
        int m_life;
        int m_energy;
        int m_energyMax;
    
    public:
    
        Player();
        ~Player();
        void beAttack(int damage);
        void beHeal(int heal);
        int getId();
    };
    #include <iostream>
    #include "Player.h"
    #include "Entity.h"
    
    Player::Player():Entity(),m_life(20),m_energy(5),m_energyMax(5)
    {
    
    }
    void Player::beHeal(int heal)
    {
        m_life+=heal;
    }
    void Player::beAttack(int damage)
    {
        m_life-=damage;
    }
    int Player::getId()
    {
        return SURVIVOR;
    }
    Player::~Player()
    {
    
    }

    L'extrait du main incriminé :

        for(int y=0;y<world.getSizeY();y++)             //Concretement, ces boucles parcour un tableau en 2 dimensions
        {
            for(int x=0;x<world.getSizeX();x++)
                {
                    if(world.getCase(x,y).beUse())      //Ici une Case est un conteneur qui contient une entity et d'autre valeurs ajouté, c'est ici que survient l'erreur du compilateur indiqué en PS
                    switch(caseBis.getEntity().getId())
                    {
                        case SURVIVOR:                  //Cette conditions n'est jamais remplis alors qu'il y a bien des Case contenant des SURVIVOR
                            sprPlayerSurvivor.setPosition(sizeWindowX/2-world.getSizeX()*32+x*64,sizeWindowY/2-world.getSizeY()*32+y*64);
                            rWindow.draw(sprPlayerSurvivor);
                            break;
    
                    }
                }
        }


    Pour ceux que ça intéresse la classe Case :

    enum e_id_Case
    {
        NEUTRAL,
        POISON_EFFECT,
        DEFENSE_EFFECT,
    };
    
    class Case
    {
        Entity *m_entity;
        int m_id;
        int m_valueEffect;
    
    public:
        Case();
        Case(int id,int valueEffect,Entity &entity);
        ~Case();
        Entity getEntity();
        int getIdEntity();
        bool beUse();
    };
    #include "Case.h"
    #include <iostream>
    
    Case::Case():m_entity(new Entity),m_id(NEUTRAL),m_valueEffect(0)
    {
    
    }
    Case::Case(int id,int valueEffect,Entity &entity):m_entity(&entity),m_id(NEUTRAL),m_valueEffect(0)
    {
    
    }
    Entity Case::getEntity()
    {
        return *m_entity;
    }
    
    bool Case::beUse()
    {
        return(m_entity!=0);
    }
    Case::~Case()
    {
        m_entity->~Entity();
    }

    PS :

    -Erreur du compilateur sur la 1ere/2eme conditions (if et switch):"error: passing 'const Case' as 'this' argument discards qualifiers [-fpermissive]|"

    J'ai conscience que je donne beaucoup de travail et m'en excuse vraiment ^^' !De même pour le grand nombre d'erreur qu'il risque d'y avoir. Mais là je suis un peu au bout du rouleau et n'y comprend plus rien du tout, merci énormément pour ceux qui pourront m'aider!:ange:

    • Partager sur Facebook
    • Partager sur Twitter
      16 juillet 2019 à 9:00:03

      Ca pique!

      rapidement:

      Sauf placement new, jamais d'appel exiplicite au destructeur. Dans ton cas il n'y a pas de placement new donc tu ne dois pas faire d'appel explicite au destructeur.

      Case::~Case()
      {
         delete m_entity;
      }

      voir même encore mieux, utiliser un std::unique_ptr<entity> au lieu d'un Entity *  pour m_entity, et là t'es plus emmerdé. 

      Case::Case():m_entity{std::make_unique<Entity>()},m_id{NEUTRAL},m_valueEffect{0}{}
      
      Case::~Case()
      { 
         // rien de spécial à faire
      }

      Ensuite, ton idée de dériver Player de Entity, ça sent le mauvais God Object en puissance et du même coup que les principes S.O.L.I.D et la loi de Demeter vont allègrement être ignorés. Quand une conception objet commence comme ça, elle finit généralement dans le mur, d'ailleurs tu es en train d'en faire l'expérience, puisque tu ne sais plus comment régler tes problèmes. La relation d'héritage est la plus forte (et donc la plus contraignante) qui soit, elle se base sur le principe de substitution de Liskov (le L de S.O.L.I.D) du nom de Barbara Liskov qui l'a ennonçé. Ce principe dit en substance que la substitution d'une classe dérivée par une autre ne doit pas avoir d'effet sur le comportement du programme. Par exemple une classe Ecrivain qui aura pour rôle d'écrire des données.

      class Writer
      {
      public:
      
          virtual write(std::string const & msg) = 0;
          virtual ~Writer() = default;
      };

      Je peux la dériver en une classe dédiée à l'écriture sur l'écran, une dédiée à l'écriture dans un fichier et une autre dédié à l'envoi de données vers un serveur par exemple:

      class ConsoleWriter : public Writer
      {
      public:
      
          virtual ~ConsoleWriter() = default;
      
          void write(std::string const & msg) final {std::cout << msg;}
      };
      class FileWriter : public Writer
      {
         std::ofstream target_;
      
      public:
      
          FileWriter(std::string const & fileName);
          virtual ~FileWriter() = default;
          void write(std::string const & msg) final;
      };
      class NetworkWriter : public Writer
      {
         SocketType socket_; 
          
      public:
      
         NetworkWriter(std::string const & address);
      
         virtual ~NetworkWriter() final {socket_.close();}
      
         void write(std::string const & msg) final;
          
      };

      Ici, le principe de Liskov est respecté, je peux opérer toutes les substitutions que je veux, je n'aurais pas d'effet sur le comportement du programme, les données que je passerai à mon Writer seront simplement écrite à des endroits différents.

      Un bon moyen de ne pas se planter quand on conçoit une classe est de penser en terme de service à rendre et surtout pas en terme d'encapsulation de données. Le premier objectif d'une classe est de servir à quelque chose, donc de rendre des services (idéalement un seul en vertu du principe de responsabilité unique (SRP, le S de S.O.L.I.D)). Si on se focalise sur cet objectif de service à rendre, la conception est plus facile, je peux identifier les services dont j'aurais besoin dans mon programme, je peux les spécifier précisément, c'est à dire faire une espèce de contrat qui contiendra toutes les garanties de services, conditions d'utilisation requise, invariants, effets de bords, données retournées, qu'est ce qui se passe en cas d'erreur (on ne vit pas dans le monde magique des bisounours)... A partir de là, je sais quelles classes je dois concevoir, je sais précisément quel rôle elles auront et cerise sur le gâteau, je sais aussi comment les tester car j'ai tous les éléments pour vérifier que la classe conçue est bien conforme à ce qui est attendu.






      • Partager sur Facebook
      • Partager sur Twitter
      Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug
        16 juillet 2019 à 13:07:01

        J'ai rapidement lue les pages Wikipédia des concepts de S.O.L.I.D et de la Loi de Demeter et je les trouve très intéressante et cohérente à ma vision de la POO (je suis encore débutant donc j'ai du mal a trouver plusieurs alternative à un problème et je ne peux donc que très peu les appliquer : j'essais de survivre en quelque sorte ^^), mais même en m'aidant de ton post (Merci d’ailleurs d'avoir répondu!) j'ai du mal a l'appliquer à mon code :/... La classe Entity représente juste les base de l'entité est de ce qu'elle peuvent faire, Player est simplement plus évolué mais aussi plus précise (Une seringue est considéré comme une Entité mais ne pourras jamais attaquer par elle même ^^').De plus l'idée de "service" me parait SUPER abstraite, la boucle envoyé doit pour l'instant juste s'occuper de l'affichage en fonction de l'ID de l'entité (Qu'on peut voir comme une fonctions qui renvoi un enum selon le type de l'objet). J'ai vraiment du mal à voir ce que tu appel un Service, et si tu ne veux pas t’embêter je n'arrive pas à appeler la bonne fonction à savoir "Entity::getId", la seul version appeler est celle de Entity et non de ses classes hérités.  

        • Partager sur Facebook
        • Partager sur Twitter
          16 juillet 2019 à 13:32:21

          Sans parler de la conception, voici quelques pb dans ton code:

          - Le polymorphisme sans fonction membre virtuelle, c'est pas top... Ta classe Entity ne déclare aucune fonction virtuelle. D'après ce que tu veux faire, elle devrait plutôt ressembler à ça:

          class Entity {
          public :
              virtual ~Entity() = default;
              virtual void beAttack(int damage);
              virtual void beHeal(int heal);
              virtual int getId();
          };

          Puis Player:

          class Player: public Entity{
              int m_life;
              int m_energy;
              int m_energyMax;
           
          public:
              Player();
              void beAttack(int damage) override;
              void beHeal(int heal) override;
              int getId() override;
          };

          - Dans la classe Case, la fonction membre getEntity renvoie Entity par valeur, ce qui signifie que tu vas perdre le type dynamique. Seuls les pointeurs et références permettent de faire du polymorphisme.

          • Partager sur Facebook
          • Partager sur Twitter
            16 juillet 2019 à 14:56:03

            Le concept de service te parais abstrait parce que tu n'as pas fais le travail d'analyse des besoins de ton programme. Imaginons que je veuille faire un jeu 3D par exemple (un  FPS pour fixer les idées). Une analyse très sommaire et non exhaustive va faire apparaître que par exemple je vais avoir besoin d'un module pour charger les textures, un autre pour charger les modèles 3d, un autre encore pour composer les scène, un autre pour faire le rendu graphique, un autre pour "gérer" l'IA des bots, un autre encore pour "gérer" le multi joueur, les bruitages, la musique... Au passage tu remarqueras que je n'ai pas évoqué la possibilité d'une classe Player, parce que justement, il n'y a pas de service qui correspond. Qu'est ce qui différencie un joueur, d'un PNJ, c'est le centre de décision, dans le cas d'un PNJ, c'est une IA, dans le cas du joueur, c'est les points d'entrées de l'interface homme machine (souris, clavier, manette, micro, écran tactile...) et dans un jeu multi-joueur, ce sera une connexion réseau.

            Concrètement ta classe Entity ne correspond à aucun service dont j'aurai besoin. Après je peux éventuellement partir sur une architecture de type ECS (Entity Component System) où j'aurai probablement une classe Entity qui ne sera qu'un agrégat de composants (probablement même seulement de références de composants et non les composants eux mêmes)  qui seront utilisés par des systèmes. Là j'ai un vrai service pour ma classe Entity qui est de fournir un regroupement cohérent de composants, mais une telle classe Entity n'aura probablement pas de classe dérivée et certainement pas une classe Player.

            • Partager sur Facebook
            • Partager sur Twitter
            Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug
              16 juillet 2019 à 18:43:02

              Très bien mais à ce moment là : à correspond concrètement le joueur? Comment faire pour stocker sa vie, ses armes, sa position où même ses actions? Une classe me paraissait être une bonne alternative, en la dérivant d'Entity j'avais un moyen d'avoir une collection hétérogène et de pouvoir les "refresh" à chaque frame...Mais là tout tombe à l'eau nan? Alors certe rien ne le différencie d'une IA, SAUF les entrées humaines que je voulais gérer à l’intérieur de cette classe. Puis je n'ai jamais utilisé d' "override", y a-t-il des choses à savoir sur ce mot-clef?
              • Partager sur Facebook
              • Partager sur Twitter
                16 juillet 2019 à 18:47:29

                ''Une seringue est considéré comme une Entité mais ne pourras jamais attaquer par elle même''

                Alors ta seringue peux se faire attaquer et se faire healer ? :-°
                • Partager sur Facebook
                • Partager sur Twitter
                  16 juillet 2019 à 19:03:22

                  Il est vrai que cela peut paraître assez bizarre, mais il s'agit d'utiliser une attaque sur une Case et non sur l'entité, donc la fonction "beAttack" qui sera override et redéfini dans la seringue n'aura juste aucun effet, elle pourra être attaquer mais sans effets. Juste une protection pour ne pas appeler une fonction qui n'existerais pas
                  • Partager sur Facebook
                  • Partager sur Twitter
                    16 juillet 2019 à 19:10:30

                    '' donc la fonction "beAttack" qui sera override et redéfini dans la seringue n'aura juste aucun effet ''

                    Tu te rend compte que tu defini un probleme de conception grave ?
                    Pourquoi ta seringue aurait-elle une fonction qui ne fait rien et qui nomenclaturement est impossible ( beHeal pour une seringue ? Elle a combien de vie ta seringue ? )

                    Je crois que tu devrais revoir ta conception.
                    Une approche ECS serait plus viable.
                    • Partager sur Facebook
                    • Partager sur Twitter
                      16 juillet 2019 à 19:21:03

                      Ecoutez...C'est vraiment un tout 1er jeux, j'expérimente juste quelques notions que j'ai appris sur ce siten je sais bien qu'il est loin d'être parfait. Mais juste je suis déjà perdu de A à Z et j'essais d'apprendre en testant des choses, mais plus je pose des questions, plus on m'énonce des concept/principes ou encore manière de coder, et plus je suis perdu.. J'ai l'impression de devoir tout recommencer alors que mon trucs me parait super simple pour l'instant : un damier, un joueur qui peut jouer et peut effectuer des actions sur d'autre case, et sur ces autres cases des Entités. C'est vraiment pas contre vous mais la j'ai beaucoup trop de choses à apprendre que ça en devient presque rebutant. Si vous pouviez me guider puisque plus j'avance, plus je rencontre des problèmes et plus je recul.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        16 juillet 2019 à 20:42:56

                        Ne te décourage pas .. dans ce domaine tu n'as pas fini d'apprendre et des concepte y'en a beaucoup ..
                        Ce qui faut comprendre seulement , c'est qu'a la base ta CONCEPTION ( et non ta facon de codé ) n'est pas bien pensée . Tu dois toujours te poser des question avant de coder :
                        - Est-ce logique qu'une seringue puisse etre healer et attaqué ? Non
                        - Est-ce logique qu'une seringue et un joueur hérite d'une classe entité ? Oui a un certain point.

                        Si tu veux garder cette conception , descend les fonction dans les classe fille qui auront besoin de ses fonctions
                        par exemple : tu peut avoir une classe mere entité afin de beneficier du polymorphisme ... mais une seringue ne devrait pas ertre healable ou attaquable. Ces fonctions devrait etre définies dans la classe 'player'
                        • Partager sur Facebook
                        • Partager sur Twitter
                          16 juillet 2019 à 22:54:11

                          C'est normal que tu fasses des erreurs au début, tu connais la citation latine "Errare humanum est" mais peut être pas la deuxième partie "Perseverare diabolicum est". L'erreur est humaine, la persévérance dans l'erreur est diabolique. Les idées que nous essayons de te faire passer viennent de notre expérience, les erreurs que tu fais, nous les avons faites avant toi (on ne naît pas programmeur, on le devient), nous avons compris ce qui ne fonctionnait pas et pourquoi ça ne marche pas, ce que nous essayons de faire, c'est te mettre sur un chemin qui ne débouche pas sur une impasse. 

                          Si tu y réfléchis à deux fois, il n'y a absolument aucune raison pour justifier le fait qu'une seringue est une entité, une seringue est un objet inerte jetable qui n'a manifestement pas le moindre point commun avec une entité, sauf peut être l'unicité, mais ça ne justifiera jamais que l'on puisse dire qu'une seringue est une entité au sens du principe de substitution de Liskov et à partir de là, si tu dérives Seringue de Entité tu es dans l'erreur de conception.

                          Pour une classe Player c'est plus subtil, ce qui va se passer le plus souvent, c'est que tu vas agréger des données et leur donner un sens qui en réalité n'existe pas. Prenons par exemple les points de vie de ton perso, ils seront impactés par le système de gestion de dommage du jeu, dès lors quel est l'intérêt de les stocker dans une classe personnage, si je veux être efficace, je vais mettre tous les "Points de vie" dans un tableau que je vais passer au système de résolution de dommages, que les points de vies soient ceux du joueur, d'un PNJ, d'un mob, ou de n'importe quoi d'autre, je m'en fous, ca sera géré pareil, là encore tu retrouves le service, le vrai service, c'est pas le perso, c'est le truc qui assure la gestion des dommages, c'est lui qui fait vraiment le boulot, et tu peux prendre n'importe quel aspect, l'affichage par exemple, ce n'est pas le perso qui va s'afficher sur l'écran, c'est le moteur graphique qui va faire le rendu du perso, là encore service rendu par ta classe Perso = 0, le service, c'est le moteur de rendu graphique qui le rend. Qu'est ce qui différencie le perso d'un PNJ? si tu y réfléchis un peu tu verras que la seule différence, c'est la source des ordres, le clavier, la souris, une manette pour le joueur local, une connexion réseau pour un joueur distant, et une IA pour un PNJ, tout le reste c'est pareil et si tu approfondis encore la réflexion, tu vas découvrir que tu peux gérer la destruction d'un mur exactement pareil que les dégât sur le joueur, le mur a des point de vie et quand il a encaissé plus de dégâts que ses points de vies, il meure (il s'effondre, c'est pareil, c'est juste l'animation qui va changer).

                          Lorsque je programme, j'ai toujours un bloc note, un crayon et une gomme, je noirci le bloc de schémas, de notes à moi même... Au bureau, j'ai un gigantesque tableau veleda avec plein de feutres, et c'est pareil, avant d'écrire la moindre ligne de code, je t'ai couvert le tableau de schémas, qui sont le résultat de la fixation de ma réflexion. C'est pour ça qu'on insiste autant sur la conception, avec l'expérience on a l'habitude, on sait que si on a bien bossé en amont, l'écriture du code sera facile, qu'il n'y aura pas de gros problèmes, par contre si on bâclé cette étape, Murphy veille au grain, sur le plan des emmerdes, c'est toujours 36 fois la mise! Lorsque je vais écrire le code, je n'ai quasiment pas à réfléchir, c'est presque comme si j'écrivais sous la dictée, j'ai toute la structure dans la tête, il ne reste plus qu'à la traduire dans le langage de programmation.

                          Le soucis des débutants, c'est qu'ils se jettent dans le code sans avoir fait cette démarche de réflexion et d'analyse préalable, et puis ils se noient dans leur code et ses contradictions...

                          -
                          Edité par int21h 17 juillet 2019 à 0:07:17

                          • Partager sur Facebook
                          • Partager sur Twitter
                          Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug
                            17 juillet 2019 à 1:38:21

                            Merci à vous, je vais essayer de revoir mes systèmes de gestions, c'est juste que la plus part du temps je considère mon code comme un ensemble d'objet (dans le sens très explicite du terme, plus comme des variables et pas assez comme des méthodes) plus que de service. Je vais revoir l'ensemble des interactions entre les classes et le faire sur papier. Dernière question avant de fermer ce post (promis ^^) : Est-ce mieux d'avoir plusieurs tableaux se rapportant chacun à un élément (un tableau "PV" puis un autre "Dégât par attaque") ou le numéro de chaque case de stockage définis de qui je parle (case 0 : Joueur, case 1 : Bot n°1,etc...)? Ainsi chacun des éléments peuvent être gérer sans trop s’embêter avec du polymorphisme ou fonctions dérivées. Ou bien la solution brut avec des Objets tous dérivés d'une classe et des fonctions qui effectuerais les bonnes actions sur ceux-ci pour les modifier?

                            PS : Encore merci! Juste que parfois j'ai vraiment du mal à m'accrocher, le C++ étant très difficile puis tout le monde qui nous impose de se tourner vers des moteurs de jeux/ Langage haut niveaux.

                            • Partager sur Facebook
                            • Partager sur Twitter
                              17 juillet 2019 à 13:02:21

                              Salut,

                              DeveCout a écrit:

                              Merci à vous, je vais essayer de revoir mes systèmes de gestions, c'est juste que la plus part du temps je considère mon code comme un ensemble d'objet (dans le sens très explicite du terme, plus comme des variables et pas assez comme des méthodes) plus que de service.

                              A ce moment là, dis toi que toute chose ne prend réellement du sens que parce qu'elle est utilisée: une donnée n'a du sens que... si elle est "manipulée" dans une (ou plusieurs) fonctions, et, le plus souvent, ce n'est pas la donnée qui nous intéresse, mais le résultat ... de la manipulation de la donnée.

                              Tu t'en fous pas mal, qu'un élément soit positionné à tel endroit plutôt qu'à un autre (*).  Ce qui t'intéresse, c'est que si tu décide de la déplacer, elle se trouve, après le déplacement, effectivement à une distance équivalente au déplacement par rapport à sa position d'origine

                              (*) Bien sur, le fait de pouvoir connaitre la position d'un élément fait souvent partie des services que l'élément en question doit être capable de fournir, parce que, dans certains cas (pour évaluer les différentes collisions qui surviennent entre différents éléments, principalement), cette information aura son importance.  Mais, l'un dans l'autre, un élément se déplacera sans doute beaucoup plus souvent, sans rentrer en collision avec quoi que ce soit, qu'il n'entrera en collision avec "quelque chose" ;)

                              DeveCout a écrit:

                              Est-ce mieux d'avoir plusieurs tableaux se rapportant chacun à un élément (un tableau "PV" puis un autre "Dégât par attaque") ou le numéro de chaque case de stockage définis de qui je parle (case 0 : Joueur, case 1 : Bot n°1,etc...)? Ainsi chacun des éléments peuvent être gérer sans trop s’embêter avec du polymorphisme ou fonctions dérivées. Ou bien la solution brut avec des Objets tous dérivés d'une classe et des fonctions qui effectuerais les bonnes actions sur ceux-ci pour les modifier?

                              Tout dépend de l'approche que tu décide de mettre en oeuvre : dans une approche de type ECS, tu auras un tableau par "type d'élément", et ce sera le plus facile.  C'est d'ailleurs la solution que te conseille (à très juste titre ) int21h.

                              Dans une approche orientée objets, tu  te retrouverais plutôt avec un tableau "d'entités" dérivées d'une classe de base, et tu ferais sans doute un usage intensif des fonctions virtuelles et du polymorphisme.

                              Le problème de cette approche, outre le fait des performances qui auront tendance à dégringoler, c'est que ton programme n'a absolument aucune chance d'être correct si ta conception n'est pas parfaite. Et, du coup, tu te heurte de plein fouet aux restrictions imposées par le LSP, qui dit, en substance que

                              Si S est un sous type (une classe dérivée) de T (une classe de base), toutes les propriétés valides (simplifions au maximum: les fonctions publiques) de T (la classe de base) doivent être valide pour S (la classe dérivée).

                              Autrement dit, on peut considérer qu'une voiture, une moto, un camion ou même un char à voile comme étant "des véhicules", car tous te présenteront une fonction "aller à (tel endroit)"; ce qui te permettrait de les placer dans un tableau (de pointeur, de préférences intelligents) de véhicules.

                              Mais, si tu veux ajouter la notion de mur ou de coffre, tu ne peux décemment pas considérer que ce sont des ... véhicules, car la fonction "aller à (tel endroit)" n'a absolument aucun sens pour un mur ou pour un coffre !

                              Tu pourrais donc envisager d'avoir une notion "d'objet" sans autre précision, qui pourrait regrouper les murs, les coffres et ... les véhicules.  Mais, pour respecter LSP, il faut trouver au minimum une propriété qui soit valide aussi bien pour le coffre que pour le mur ou pour les véhicules.  Et ca, ca posera pas mal de problèmes :'(

                              En outre, il faut te dire que tu voudras combiner plusieurs notions entre elles  dans le sens où tu finiras tôt ou tard par avoir:

                              • trois races (les elfes, les nains et les orcs)
                              • trois spécialisation (les éclaireurs, les guerriers et les magiciens)
                              • cinq type d'armes (les arcs, les épées, les haches, les marteaux et les bâton)
                              • deux notions de portée (corps à corps et "à distance")
                              • deux notions de zone (permettant d'attaquer une personne unique ou "toutes les peronnes qui se trouvent dans un rayon de X par rapport au point attaqué")
                              • deux type de joueur (joueur humains et PNJ)
                              • des armes magiques et d'autres qui ne le sont pas
                              • des sorts d'attaque et des sorts de défense
                              • j'en passe, et sans doute de meilleures

                              Cela signifie que tu pourras avoir des elfes qui sont des éclarireurs, d'autres qui sont des guerriers et d'autres encore qui sont des magiciens; que certains utiliseront des arcs, d'autres des épées, et d'autres encore utiliseront des bâtons; que les arcs sont d'office des armes à deux mains, mais que les épées, les haches et les bâtons peuvent être des armes à une main ou à deux mains, que les hache et les bâtons peuvent être des armes de lancé, et je pourrais continuer longtemps comme cela.

                              Autrement dit, tu vas assister à une véritable explosion de ton nombre de classes pour pouvoir prendre en compte les différentes combinaisons rendues possibles par ces différentes notions.  Et je ne te parle même pas de l'elfe qui est un magicien et un guerrier ou du chaman orc qui est un peu voleur :D

                              A termes, cela deviendrait totalement ingérable ;)

                              • Partager sur Facebook
                              • Partager sur Twitter
                              Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                17 juillet 2019 à 15:17:44

                                Pour te donner un exemple de systeme ECS , voici un court code qui reflete l'idée :

                                #include <iostream>
                                #include <vector>
                                #include <string>
                                
                                void Attack(size_t attacker, size_t attacked, std::map<size_t, int> & lives, std::map<size_t, int> const & attacks)
                                {
                                	std::map<size_t,int>::iterator attacked_life_it = lives.find(attacked);
                                	std::map<size_t,int>::const_iterator attacker_dmg_it = attacks.find(attacker);
                                	
                                	if(attacked_life_it != lives.end() && attacker_dmg_it != attacks.end())
                                	{
                                		attacked_life_it->second -= attacker_dmg_it->second;
                                	}
                                	
                                }
                                
                                bool isAlive(size_t entity, std::vector<size_t, int> const & lives)
                                {
                                	std::vector<size_t, int>::const_iterator it = lives.find(entity);
                                	
                                	if(it = lives.end())
                                		return false;
                                		
                                	return it->second > 0;
                                }
                                
                                void printAttack(size_t attacker, size_t attacked, std::vector<size_t, std::string> const & names)
                                {
                                	std::vector<size_t, std::string>::const_iterator attacker_name_it = names.find(entity_0);
                                	std::vector<size_t, std::string>::const_iterator attacked_name_it = names.find(entity_1);
                                	
                                	std::cout << attacked_name_it->second <<" has been attacked by " << attacker_name_it->second <<std::endl;
                                }
                                
                                int main(){
                                	size_t entity_0 = 0;
                                	size_t entity_1 = 1
                                	
                                	// map une entité a sa vie
                                	std::map<size_t, int> LifeList {};
                                	// map une entité a ses dommages
                                	std::map<size_t, int> DmgList {};
                                	// map une entité avec son nom
                                	std::map<size_t, std::string> NameList {};
                                	
                                	// creation de la premiere entité
                                	// cela se fait en ajoutant les information necessaires dans les list correspondantes
                                	NameList.insert ( std::pair<size_t,std::string>(entity_0, "Joueur"));
                                	LifeList.insert ( std::pair<size_t,int>(entity_0, 100) );
                                	DmgList.insert ( std::pair<size_t,int>(entity_0, 12) );
                                	
                                	// creation de la deuxieme entité
                                	// cela se fait en ajoutant les information necessaires dans les list correspondantes
                                	NameList.insert ( std::pair<size_t,std::string>(entity_1, "Ennemi"));
                                	LifeList.insert ( std::pair<size_t,int>(entity_1, 125) );
                                	DmgList.insert ( std::pair<size_t,int>(entity_1, 7) );
                                	
                                	size_t turn_counter = 0;
                                	size_t attacked = 0;
                                	size_t attacker = 0;
                                	
                                	while(isAlive(entity_0) && isAlive(entity_1))
                                	{
                                		if(turn_counter%2==0){
                                			attacker = entity_0;
                                			attacked = entity_1;
                                			Attack(entity_0, entity_1, LifeList, DmgList);
                                		}
                                		else{
                                			attacker = entity_1;
                                			attacked = entity_0;
                                			Attack(entity_1, entity_0, LifeList, DmgList);
                                		}
                                		
                                		printAttack(attacker, attacked, NameList);
                                turn_counter++; } }

                                Ce code n'a pas été compilé ni testé, mais il devrait etre fonctionnel.
                                Note : tout retour de find devrait etre verifié avec le conteneur.end() ( chose que je n'ai pas fait partout ).

                                Comme tu peut voir : une entité est défini selon les composants ( vie , nom , attack .. ) qu'elles ont.
                                Si tu veut que ton entity_0 soit un loup et se deplace en meute , alors tu pourrais créer un composant 'loup' pour lequel un system ( ici les fonction attack , isAlive et printAttack) deplacerait les positions de ceux qui ont un composant loup suivant un 'alpha' par exemple.

                                Ce systeme n'empeche donc pas de garder la vie et les dommages afin que ton loup soit attaquable et qu'il puisse attaqer.
                                Si tu veux que ton loup soit un magicien , tu créer un composant magicien et tu lui ajoute dans la liste correspondante.
                                Au moment venu , le systeme qui s'occupe des attaque pourrait recuperer les composant magique pour lancer une boule de feu par exemple ..
                                Tu veut que ton loup soit un piege , donc qu'il attaque sans pouvoir etre attaqué ? Tu lui enleve son composant de vie et voila ..
                                ( en réalité c'est légerement plus complexe , mais l'idée est la ! ).

                                Dans ce systeme , on evite la relation lourde de l'heritage et du polymorphisme.
                                Chaque systeme ( ici les fonctions ) recoit que les information dont elles ont besoin afin de travailler sur l'entité ( la fonction attack ne recoit pas la liste de nom car ce n'est pas son travail d'afficher quoi que ce soit ! ) , comme la fonctionn d'affichage ne recoit pas les vie ni les dommages !


                                -
                                Edité par CrevetteMagique 17 juillet 2019 à 15:19:52

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  17 juillet 2019 à 15:53:06

                                  Bon bah merci beaucoup! Je vais essayer de travailler en ECS pour ce projet, je m'efforce peut-être trop à vouloir créer des objets partout et sacraliser la POO au sens strict du terme. De plus il est vrai que ce sera beaucoup plus simple pour les modifications, il suffira de rajouter un id et une conditions et hop on aura une nouvelle sorte d'attaque, merci à vous!
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    17 juillet 2019 à 17:20:24

                                    DeveCout a écrit:

                                    je m'efforce peut-être trop à vouloir créer des objets partout et sacraliser la POO au sens strict du terme.

                                    Ca arrive très souvent ;)  Les gens ont tendance à oublier que la POO n'est j'amais qu'un outil, comme un marteau ou un tournevis, et que, en tant que tel, l'outil est adapté à certains besoins, et totalement inadapté à d'autres ;)

                                    Tu n'essayerais jamais d'enfoncer un clou avec un tournevis ou d'enfoncer une vis avec un marteau.  C'est pareil pour la POO.  Quand l'approche orientée objet est adaptée, les choses se font "naturellement" de cette manière.  Si tu te rend compte, sans doute après un certain temps, que l'approche orientée objet en arrive à poser plus de problèmes que les solutions qu'elle offre, c'est qu'il faut changer d'outil et donc l'approche du problème que tu essayes de résoudre ;)

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

                                    Problème de polymorphisme : appel de fonctions

                                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                    • Editeur
                                    • Markdown