IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

[C++] Utilisation du pattern strategie (ou pont)


Sujet :

C++

  1. #1
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut [C++] Utilisation du pattern strategie (ou pont)
    Bonjour

    J'ai deux choses à demander ^^

    1ère chose :

    J'ai étudié le pattern Strategie grâce au livre "Design Pattern - Tete la Première". Cependant, quelque chose me chiffonne quand je parcours le Web et quand j'ai découvert le pattern Bridge (pont) qui ressemble beaucoup au pattern stratégie :
    Le pont est un pattern Strcucturel:
    - Pont: Sépare une abstraction de son implémentation de sorte que les deux puissent varier indépendamment.

    Le stratégie est un pattern comportemental:
    - Stratégie: Définit une famille d'algorithmes, encapsule chacun, et les rend interchangeables. La Stratégie permet à un algorithme de varier indépendamment des clients qui l'utilisent.
    Maintenant, dans le livre pour le pattern Stratégie, j'ai l'impression que c'est un mixte du pattern Pont et Strategie. Voilà le diagramme UML en pièce jointe.

    Voilà je voulais votre avis sur la question

    2ème chose :

    Je souhaiterais appliquer l'un des deux patterns (je pense le Strategie) à ma situation dans mon jeu 2D :
    Soit les interfaces IEntity, ISprite ainsi que des interfaces *Behaviours

    • La classe Player et Enemy dérivent de l'interface IEntity.
    • La classe AnimatedSprite et ClassicalSprite dérivent de l'interface ISprite.
    • IEntity est composé d'un ISprite (composition) et d'interfaces de Behaviours (non implémentées encore).
    • La classe World (qui gère le monde en entier) possède un ensemble d'entités (dans un vector pour l'instant).


    Donc grâce au polymorphisme je peux mettre à jour et dessiner toutes mes entites en parcourant le vector.

    Pas de problème jusqu'à la

    MAIS si je veux rajouter dans les classes Player et Enemy des attributs spécifiques voires des méthodes spécifiques, je ne pourrais jamais les invoquer à partir de World (car il ne manipule que des IEntity).

    De même, les Behaviours vont devoir manipuler des attributs des classes Player, Enemy par exemple quand un ennemie va perdre de la vie, il faudra bien baisser son attribut heath (qui sera privé dans la classe Enemy).

    Pour être plus clair, prenons un exemple de Behaviours : IColissionBehaviour (celui qui gère les collisions entre les entités). Imaginons une classe HitBehaviour qui implémente (dérive en C++) de IColissionBehaviour.
    La classe HitBehaviour à besoin de connaitre des infos sur toutes les Entity qui implémentent ce comportement !

    Et hop, je suis coincé !! Je ne vois pas comment faire...

    J'ai vraiment besoin d'aide, je n'arrive plus à avancer dans mon design

    Merci en tout cas, et bonne vacances
    Images attachées Images attachées  

  2. #2
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 493
    Points
    5 493
    Par défaut
    Bonjour à toi.

    Sur "bridge vs strategy" d'abord.
    * Première différence : leur but. L'un (bridge) est un pattern structurel et concerne donc la façon dont ton programme est écrit, tandis que l'autre (strategy) est un pattern comportemental qui concerne la façon dont ton programme s'exécute. Typiquement, la stratégie utilisée par un objet est appelée à varier au cours de l'exécution alors qu'on utilisera plutôt bridge pour une implémentation choisie au démarrage.
    * Seconde différence : typiquement, avec "bridge", l'abstraction est presque entièrement bâtie par-dessus cette implémentation et offre souvent une interface proche de cette dernière. A l'inverse, une stratégie est plutôt une partie seulement de son consommateur. Dans ton cas, tu as ainsi plusieurs familles de stratégie par client, et ce dernier assure aussi d'autres services qui ne dépendent d'aucune stratégie.
    * Troisième différence : le couplage est typiquement plus fort avec le pattern "strategy". Dans le cas de figure où "bridge" est utiliser pour s'interfacer avec différents SGBD, on peut écrire autant d'implémentations que l'on veut sans rien changer à l'abstraction. A l'inverse, si tu écris une stratégie pour une AI en mode berserk, tu as alors besoin de modifier ton client pour ajouter un code de sélection de l'AI berserk.



    Sur la 2nde partie.
    * Pourquoi World aurait-il besoin de manipuler les attributs spécifiques à certaines classes ? Si des traitements requièrent une connaissance détaillée de ces classes, ils doivent être effectués par ces classes en question et non pas par World. Cf. le Dependency Inversion Principle.

    * Sur le reste, je ne pourrais pas t'aider davantage sans savoir de quel type de jeu il s'agit mais, grosso modo, je dirais que tu dois définir des interfaces additionnelles (IHasHealth pour donner un probablement mauvais exemple) ainsi qu'une façon de savoir si un objet en question implémente telle ou telle interface. HitBehaviour n'aura ainsi besoin que de manipuler une certaine interface et n'aura pas besoin de connaître tous les détails de l'objet. Alternativement, ces interfaces pourraient être implémentées par les comportements eux-mêmes plutôt que par les entités.

  3. #3
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut
    Merci pour ta réponse, donc il s'agit bien d'une stratégie dans mon livre

    Donc en fait, je fais un remake de Zelda en 2D :
    http://www.developpez.net/forums/d98...ake-zelda-gbc/

    En fait, j'ai du mal à me dire si je dois séparer le Player (qui est une entité puisqu'il a un Sprite et des Behaviours) des autres entités car un Player est un peu spécial, contrairement aux autres entités (porte, trou, panneau, ennemies, murs, objets...) il se déplace grâce au joueur et il possède un inventaire qui lui même possède des armes.

    Donc, faut-il faire hérité Player de IEntity ou le gérer à part ? Ou mettre la classe Inventory et Item et comment les lier ?

  4. #4
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 493
    Points
    5 493
    Par défaut
    Voilà un projet bien sympathique.

    Commençons par le point le plus simple : je pense que l'inventaire devrait effectivement être totalement indépendant, je ne vois aucune raison pour en faire un sous-élément de Link. Qui plus est pas besoin d'une classe item : l'inventaire devra simplement avoir des membres "numBombes", "numFlèches", etc, ce sera plus facile à gérer. Le seul cas où un item devrait avoir une instance associée ce sera une entité dans le cas où il est par terre, attendant d'être ramassé.

    Maintenant, sur la question de savoir si Link doit être traité comme une entité comme les autres, ma réponse est positive quand je regarde ce qui les unit :
    * AI : le contrôle par le joueur n'est qu'une forme d'AI. Tous auront besoin de services communs comme "puis-je aller en (x, y) ?"
    * Collisions. Les réactions (comportements) seront différentes mais, fondamentalement, les mécanismes sont les mêmes. On a des collisions joueur-projectile, ennemi-projectile, joueur-mur, ennemi-mur, joueur-ennemi et même ennemi-ennemi (si on pense à ces statues amovibles : si on en déplace une elle peut pousser un monstre).
    * Comportements communs : par exemple une projection en arrière suite à un coup, durée pendant laquelle l'AI ou le joueur ne peuvent rien faire. Ou le gel des AI et de Link lors d'une transition d'écran.
    * Gestion de la vie et détection de la mort.

    A mes yeux ça fait beaucoup de choses en commun alors que les différences peuvent très bien être gérées par des comportements différents.

    A priori je partirais sur une hiérarchie de base de la sorte :
    Entity (avec entre autres propriétés IsMovable, IsItem, IsSpiky, IsPlayer, IsCreature)
    * Movable (statues déplaçables : comportements identiques, seul le graphisme change)
    * Item (bombes, fées, etc : service d'instanciation rapide, comportements identiques)
    * Spiky (contient notamment un flag de faction IsLinkSided et des dommages au contact)
    ** Projectile (flèches, boules de feu, rochers qui tombent, etc)
    ** Killable (contient notamment des PV)
    *** Player
    *** Creature

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 630
    Points : 30 699
    Points
    30 699
    Par défaut
    Salut,

    Il t'est tout à fait possible de considérer ton joueur comme étant une entité pour tout ce qui a trait à la gestion "classique" de ton joueur en tant que tel (tout ce qui a trait à l'affichage, va-t-on dire )

    Par contre, tu devras, de toutes manières, gérer ton (tes) joueur(s) comme... des joueurs pour toute une série de situations (tout ce qui a trait à son (leur) déplacement, ses (leurs) actions, sa (leur) "vie dans le monde", ... )

    Il faudra donc, quitte à "enregistrer" ton joueur comme étant une entité à afficher, prévoir toute un système de gestion du joueur en tant que tel!

    Ce système de gestion du joueur s'occupera, entre autres, de la gestion des événements de l'utilisateur, et les différents héritages n'auront sans doute meme pas à intervenir à ce niveau

    Pour ce qui est de l'inventaire, un raisonnement similaire peut parfaitement etre suivi:

    Rien ne t'empêche de considérer ton inventaire comme étant une entité affichable et de le gérer en tant que tel pour tout ce qui a trait à l'affichage, mais, d'un autre coté, il faudra le gérer en tant qu'inventaire pour toute une série de situation.

    La responsabilité de l'inventaire sera, d'ailleurs, la gestion des objets qu'il contient (il agira comme une collections d'objets )

    Ce qui tombe bien, c'est que l'on peut estimer que, chaque joueur ayant son propre inventaire, on peut estimer que c'est un candidat idéal pour lui déléger la gestion de celui-ci

    En réalité, une bonne partie de la gestion de l'inventaire en tant que tel sera sans doute déléguée au système d'événements de celui-ci, étant donné que beaucoup d'actions effectuées par le joueurs auront trait à la gestion de l'inventaire

  6. #6
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut
    D'accord, j'ai implémenté le Player comme étant une entité tout comme un ennemie (donc Player et Enemy héritent de IEntity).

    Voici un diagramme fait à la main (très moche désolé) :
    http://www.tutoworld.com/temp/uml_zelda.jpg

    Citation Envoyé par koala01 Voir le message
    Par contre, tu devras, de toutes manières, gérer ton (tes) joueur(s) comme... des joueurs pour toute une série de situations (tout ce qui a trait à son (leur) déplacement, ses (leurs) actions, sa (leur) "vie dans le monde", ... )

    Il faudra donc, quitte à "enregistrer" ton joueur comme étant une entité à afficher, prévoir toute un système de gestion du joueur en tant que tel!
    Justement en faisant hériter Player de IEntity, vu que le World ne manipule que des IEntity, je ne pourrais jamais appeler des méthodes ou avoir accès à des attributs propre à la classe Player, d'où mon problème.

    Voilà les classes (simplifiées) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    class World
    {
        private:
            std::vector<IEntity*> entities; // ensemble des entités de la zone de jeu
            Core::XmlManager* pXmlManager; // gestion du fichier xml de config du jeu
     
            IVect2D pos; // position de la zone dans la map courante
            int idCurrent; // id de la zone courante dans la map courante
            int idCurrentMap; // id de la map courante (une map contient plusieurs zones)
            int idCurrentMask; // id du masque correspondant à la map courante
     
        public:
            World();
            ~World();
     
            bool load();
            bool draw(const Core::IRenderer& r);
            bool update(int direction, int buttons);
    };
     
    // implémentation de la classe World :
    World::World()
    {
    .....
    }
     
    World::~World()
    {
    ....
    }
     
    bool World::load()
    {
        int idTemp = LOAD_FAILED;
     
    // des chargements
    ....
     
        idCurrentMap = IM->loadImage("data/map/overworld/1.bmp", false);
        idCurrentMask = IM->loadImage("data/map/overworld/1m.bmp", false);
     
        // chargement du sprite via le ImageManager
        idTemp = IM->loadImage("data/link_mouvement.bmp");
     
        // création d'un sprite
        if (idTemp != LOAD_FAILED)
        {
            AnimatedSprite* aS = new AnimatedSprite(
                IM->getImageSizeById(idTemp),
                IVect2D(100,10), idTemp, USize2D(16*3,16*3), true, 10);
     
            Player* p = new Player(aS);
     
            idTemp = IM->loadImage("data/1.bmp", true);
     
            AnimatedSprite* cS = new AnimatedSprite(
                IM->getImageSizeById(idTemp),
                IVect2D(100,10), idTemp, USize2D(16,16), false, 10);
     
            Enemy* e = new Enemy(cS);
     
            entities.push_back(e);
            entities.push_back(p);
     
            return true;
        }
        else return false;
    }
     
    bool World::draw(const Core::IRenderer& r)
    {
        bool bOk = true;
     
        // dessine le terrain
        //r.drawSurface(idCurrentMap, IVect2D(0,16), Rect(pos, USize2D(320, 256)));
     
        std::vector<IEntity*>::const_iterator it;
        for (it = entities.begin(); it != entities.end(); ++it)
        {
            bOk &= (*it)->draw(r);
        }
     
        return bOk;
    }
     
    bool World::update(int direction, int buttons)
    {
        bool bOk = true;
     
        std::vector<IEntity*>::const_iterator it;
        for (it = entities.begin(); it != entities.end(); ++it)
        {
            bOk &= (*it)->move(direction);
            bOk &= (*it)->update(direction, buttons);
        }
     
        return bOk;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class ISprite
        {
            protected:
                USize2D size; // sa taille
                IVect2D pos; // sa position
                int idImage; // son image
                int currentDir; // direction courante (haut, bas, gauche ou droite) du sprite
     
            public:
                ISprite(USize2D _size, IVect2D _pos, int _idImage) : size(_size), pos(_pos), idImage(_idImage), currentDir(0) {};
                virtual ~ISprite() {};
     
                virtual bool update(int direction, int buttons) = 0;
                virtual bool draw(const IRenderer& r) = 0;
     
                IVect2D getPos() const { return pos; }
                USize2D getSize() const { return size; }
                int getIdImage() const { return idImage; }
     
                void setPos(int x, int y) { pos = IVect2D(x,y); }
                void setDir(int dir) { currentDir = dir; }
        };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    31
    class IEntity
    {
        protected:
            Core::ISprite* pSprite; // pointeur sur son sprite
            IMoveBehaviour* pMoveBehaviour; // pointeur sur son type de déplacement
            bool bVisible;
     
        public:
            IEntity(Core::ISprite* _pSprite)
                : pSprite(_pSprite), bVisible(false)
            {
            };
     
            virtual ~IEntity() { delete pSprite; delete pMoveBehaviour; };
     
            virtual bool update(int directions, int buttons)
            {
                pSprite->update(directions, buttons);
                return true;
            }
     
            bool move(int directions)
            {
                return pMoveBehaviour->onMove(*pSprite, directions);
            }
     
            bool draw(const Core::IRenderer& r)
            {
                return pSprite->draw(r);
            }
    };
    1ère question : Est ce que cela choque (ou brise un des principes OO) de passer une référence du Sprite dans le "MoveBehaviour" ? car pour effectuer un déplacement, j'ai besoin d'avoir accès à des attributs du sprite...

    Interface IMoveBehaviour et ses 3 implémentations :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    // Interface IMoveBehaviour
    class IMoveBehaviour
    {
        private:
     
        public:
            IMoveBehaviour() {};
            virtual ~IMoveBehaviour() {};
     
            virtual bool onMove(Core::ISprite& pSprite, int directions) = 0;
     
    };
     
    // pas de déplacement : entité immobile
    class NoMoveBehaviour
        : public IMoveBehaviour
    {
        private:
     
        public:
            NoMoveBehaviour() {};
            ~NoMoveBehaviour() {};
     
            bool onMove(Core::ISprite& pSprite, int directions)
            {
                (void) pSprite;
                (void) directions;
     
                return true;
            }
    };
     
    // déplacement manuel : le Player avec son clavier ou son joystick
    class ManualMoveBehaviour
        : public IMoveBehaviour
    {
        private:
     
        public:
            ManualMoveBehaviour() {};
            ~ManualMoveBehaviour() {};
     
            bool onMove(Core::ISprite& pSprite, int directions)
            {
                int tempX = pSprite.getPos().x;
                int tempY = pSprite.getPos().y;
                int dirTmp = 0;
     
                // si on appuie sur une direction
                if (directions != Core::InputManager::D_NONE)
                {
                    if (Core::InputManager::isPressed(directions, Core::InputManager::D_RIGHT))
                    {
                        tempX++;
                        dirTmp= 2;
                    }
                    if (Core::InputManager::isPressed(directions, Core::InputManager::D_LEFT))
                    {
                        tempX--;
                        dirTmp = 0;
                    }
                    if (Core::InputManager::isPressed(directions, Core::InputManager::D_UP))
                    {
                        tempY--;
                        dirTmp = 3;
                    }
                    if (Core::InputManager::isPressed(directions, Core::InputManager::D_DOWN))
                    {
                        tempY++;
                        dirTmp = 1;
                    }
     
                    // controles des bords simples pour l'instant
                    if (tempX >= 0 && tempY >= 0 && tempX < SCREEN_W && tempY < SCREEN_H)
                    {
                        // on set la nouvelle position
                        pSprite.setPos(tempX, tempY);
                    }
     
                    pSprite.setDir(dirTmp);
                }
     
                return true;
            }
    };
     
    // déplacement automatique : IA pour les ennemies par exemple
    class AutoMoveBehaviour
        : public IMoveBehaviour
    {
        private:
            int tempAlea;
            int dirTmp;
     
        public:
            AutoMoveBehaviour() {};
            ~AutoMoveBehaviour() {};
     
            bool onMove(Core::ISprite& pSprite, int directions)
            {
                (void)directions;
     
                int tempX = pSprite.getPos().x;
                int tempY = pSprite.getPos().y;
     
                // sélection de la direction aléatoirement
                // si il a choisie une direction,
                // il doit avancer de 2 carrés au moins soit 2*16 pixels
                if (tempAlea >= 2*16)
                {
                    tempAlea = 0;
     
                    // choisie une nouvelle direction
                    dirTmp = rand()%4;
                }
     
                if (dirTmp == 0) tempX--;
                if (dirTmp == 1) tempY++;
                if (dirTmp == 2) tempX++;
                if (dirTmp == 3) tempY--;
     
                // controles des bords simples pour l'instant
                if (tempX >= 0 && tempY >= 0 && tempX < SCREEN_W && tempY < SCREEN_H)
                {
                    pSprite.setPos(tempX, tempY);
                    pSprite.setDir(dirTmp);
                    tempAlea++;
                }
                else // de même si il sort de l'écran
                {
                    tempAlea = 2*16;
                }
     
                return true;
            }
    };
    2ème question : Etant donné que j'utilise une map "masque" pour savoir si une entité peut se déplacer ou pas (exemple : couleur blanc = sol, couleur rouge = murs, bleu = trou...), vais-je être obliger passer l'identifiant de cette map en paramètre depuis IEntity jusqu'à *MoveBehaviour ?

    3ème question : Maintenant, le problème que j'ai c'est pour gérer les collisions, étant donné que j'ai besoin de savoir pour lé déplacement des entités si j'ai des collisions ou pas (avec un murs, une autre entité, un trou ou autre), comment vais-je faire ?

    J'aurais vraiment besoin d'un coup de pouce pour me débloquer

    Merci

  7. #7
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 493
    Points
    5 493
    Par défaut
    Petite remarque préliminaire : justement, ce n'est pas à World de déplacer le joueur.
    Pour ma part je te recommande de créer un PlayerBehaviour qui se substituera à l'IA pour la classe Player. D'autre part, ta fenêtre devrait mettre à jour à chaque frame une classe "Controller" dans laquelle seront stockés les états des différents boutons (A, B, haut, bas, etc... Vrai si pressé durant la frame, faux sinon). PlayerBehaviour n'interagira qu'avec Controller et aura la responsabilité de déplacer Link en fonction des boutons pressés. Controller est un singleton avec un membre statique exposant l'unique instance.
    Note : si le fps est faible, mieux vaut envisager une variante qui prendrait en compte la durée pendant laquelle le bouton a été pressé, la pompe à messages étant alors dans un thread de plus haute priorité.

    1./ Ce qui serait préférable à mon avis c'est qu'Entity ait lui-même une position et qu'il mette à jour la position de Sprite au début de draw (le système de coordonnées des deux positions peut être différent d'ailleurs). Il n'y aurait aucun problème à passer ton entité en argument à tes comportements (un comportement n'est couplé qu'aux classes entités de base, sauf peut-être les comportements très spécifiques liés à une classe-entité spécifique).

    2./ Ta carte devrait selon moi être encapsulée dans un singleton Map (ou un singleton ScreenMap pour une carte bornée à l'écran visible) afin d'éviter d'avoir à la passer sans cesse.

    3./ Si tu fais un déplacement au pixel près, Entity doit typiquement exposer un getter pour un bounding rectangle. Si tu te contentes d'un déplacement par case (je ne parle pas de l'animation qui sera toujours au pixel près, je parle du fait de savoir si une pression infime sur la touche "haut" fait déplacer Link d'un pixel ou amorce un glissement d'une case), la position suffit.
    Note1 : en réalité, je viens de réaliser que le sprite de Link, par exemple, grandit quand il frappe. Donc il faut dans tous les cas exposer un bounding rectangle.
    Note2 : une entité peut être composée de plusieurs sprites, attention.
    Note3 : retour sur la note 1, un contact avec l'épée n'est pas identique à un contact avec le corps. Du coup, une entité doit exposer plusieurs rectangles de collisions avec chacun un comportement différent.

    Maintenant, pour le détail, chaque entité va d'abord scanner les autres entités pour voir si l'une d'elle est en collision. Vu le peu d'entités, la force brute conviendra très bien. Quant aux collisions avec les murs, il faut pour ça interroger la map et cela peut être fait à plusieurs moments et de plusieurs façons (soit on vérifie à chaque déplacement envisagé s'il est licite, soit on stocke les déplacements envisagés sans vérifier et, juste avant le dessin, on réalise le déplacement avec les éventuelles corrections nécessaires : annulation, rebond, changement d'orientation, etc).
    Note : un cas intéressant à considérer est celui d'un serpent composé de plusieurs sprites qui bute contre un mur et rebrousse chemin.

    Autre chose à trancher : les collisions sont-elles détectées deux fois (A détecte sa collision avec B et ce dernier en fait de même de son côté), ou bien une seule fois, ce qui implique dans ce cas une forme de négociation entre A et B pour savoir lequel des deux effectue le traitement (typiquement on commence avec l'un des deux, qui passe ou non le relais à l'autre) ?

    EDIT : Ajout de notes importantes.

  8. #8
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Petite remarque préliminaire : justement, ce n'est pas à World de déplacer le joueur.
    C'est à qui alors ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    bool World::update(int direction, int buttons)
    {
        bool bOk = true;
    
        std::vector<IEntity*>::const_iterator it;
        for (it = entities.begin(); it != entities.end(); ++it)
        {
            bOk &= (*it)->move(direction);
            bOk &= (*it)->update(direction, buttons);
        }
    
        return bOk;
    }
    Avec le polymorphisme, je ne vois que cette classe capable de déplacer toutes les entités.

    Citation Envoyé par DonQuiche Voir le message
    Pour ma part je te recommande de créer un PlayerBehaviour qui se substituera à l'IA pour la classe Player. D'autre part, ta fenêtre devrait mettre à jour à chaque frame une classe "Controller" dans laquelle seront stockés les états des différents boutons (A, B, haut, bas, etc... Vrai si pressé durant la frame, faux sinon). PlayerBehaviour n'interagira qu'avec Controller et aura la responsabilité de déplacer Link en fonction des boutons pressés. Controller est un singleton avec un membre statique exposant l'unique instance.
    Note : si le fps est faible, mieux vaut envisager une variante qui prendrait en compte la durée pendant laquelle le bouton a été pressé, la pompe à messages étant alors dans un thread de plus haute priorité.
    En fait, j'ai déjà un manager des Inputs (je n'ai pas fourni le code car y'en avait déjà assez) et l'état des touches sont stockées dans les paramètres "directions" pour les flèches de direction et "buttons" pour les boutons. L'état des Inputs sont passés aux méthodes Update() de chaque classe. Sinon je n'ai pas compris, l'histoire de ton PlayerBehaviour (un petit exemple ^^) ?
    Citation Envoyé par DonQuiche Voir le message
    1./ Ce qui serait préférable à mon avis c'est qu'Entity ait lui-même une position et qu'il mette à jour la position de Sprite au début de draw (le système de coordonnées des deux positions peut être différent d'ailleurs). Il n'y aurait aucun problème à passer ton entité en argument à tes comportements (un comportement n'est couplé qu'aux classes entités de base, sauf peut-être les comportements très spécifiques liés à une classe-entité spécifique).
    Alors la position est stockée dans l'interface ISprite (attribut "pos") car pour moi une Entity n'a qu'a seul sprite donc sa position est données par celle de son sprite.

    Sinon même faire un :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual bool onMove(Entity& entity, int directions) = 0;
    puis un :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    bool move(int directions)
    {
        return pMoveBehaviour->onMove(*this, directions);
    }
    n'est pas choquant ? Je ne sais pas mais le "this" ne m'inspire pas confiance ^^
    Citation Envoyé par DonQuiche Voir le message
    2./ Ta carte devrait selon moi être encapsulée dans un singleton Map (ou un singleton ScreenMap pour une carte bornée à l'écran visible) afin d'éviter d'avoir à la passer sans cesse.
    Alors dans un Zelda, c'est un peu plus compliqué, on a une grande MAP et on scrolle de zone en zone. Donc, j'ai décidé de créer une MAP de type Plaine (par exemple) qui contient 6 zones (2 lignes et 3 colonnes). Si la zone visible (l'écran) fait 100*100 pixels pour simplifier alors la MAP PLAINE fera 3*100 = 300 par 2*100 = 200 pixels.

    Justement je me demandais comment j'allais gérer le scroll donc si je créé un singleton MapLoader(), je ne vois pas trop quoi mettre comme méthodes pour gérer le scroll (sachant que pour scroller donc changer de zone, il faut connaitre le sprite de la zone actuelle et celui de la nouvelle zone !)
    Citation Envoyé par DonQuiche Voir le message
    3./ Si tu fais un déplacement au pixel près, Entity doit typiquement exposer un getter pour un bounding rectangle. Si tu te contentes d'un déplacement par case (je ne parle pas de l'animation qui sera toujours au pixel près, je parle du fait de savoir si une pression infime sur la touche "haut" fait déplacer Link d'un pixel ou amorce un glissement d'une case), la position suffit.
    Note1 : en réalité, je viens de réaliser que le sprite de Link, par exemple, grandit quand il frappe. Donc il faut dans tous les cas exposer un bounding rectangle.
    Note2 : une entité peut être composée de plusieurs sprites, attention.
    Note3 : retour sur la note 1, un contact avec l'épée n'est pas identique à un contact avec le corps. Du coup, une entité doit exposer plusieurs rectangles de collisions avec chacun un comportement différent.
    Alors de toute manière, je fonctionne avec des masques donc les collisions se font au pixels près, de même pour les déplacements. D'où la nécessité d'avoir accès au masque de la zone courante dans mon ManualMoveBehaviours et AutoMoveBahaviour.

    Je n'ai pas compris aussi l'histoire de l'épée de Link, pour moi l'épée sera une autre entité qui sera créé pour quelques frames dans le World et une fois le coup fini elle disparaitra.
    Citation Envoyé par DonQuiche Voir le message
    Maintenant, pour le détail, chaque entité va d'abord scanner les autres entités pour voir si l'une d'elle est en collision. Vu le peu d'entités, la force brute conviendra très bien.
    Je suis d'accord mais comment le faire justement, c'est là ou je bloque, je suis sur que c'est débile (un parcours de vector et un appel au ICollisionBehaviours des entités mais je bloque vraiment). Je dois être trop fatigué...
    Je ne sais pas même quelles implémentations de l'interface ColissionBehaviours créer pour mon Zelda, ca craint
    Citation Envoyé par DonQuiche Voir le message
    Quant aux collisions avec les murs, il faut pour ça interroger la map et cela peut être fait à plusieurs moments et de plusieurs façons (soit on vérifie à chaque déplacement envisagé s'il est licite, soit on stocke les déplacements envisagés sans vérifier et, juste avant le dessin, on réalise le déplacement avec les éventuelles corrections nécessaires : annulation, rebond, changement d'orientation, etc).
    J'opte plus pour la méthode 1, c'est plus facile de vérifier directement avec le masque si le déplacement est licite plutot que de le stocker et vérifier plus tard.
    Citation Envoyé par DonQuiche Voir le message
    Note : un cas intéressant à considérer est celui d'un serpent composé de plusieurs sprites qui bute contre un mur et rebrousse chemin.
    Encore une fois, pour moi le serpent est un seul Sprite car même si il est composé de plusieurs petits sprites, on peut très bien décider d'en faire une seule image et donc une Entity = un Sprite = une position unique et une taille unique.
    Après peut être que j'ai complètement tord, je ne dis pas le contraire
    Citation Envoyé par DonQuiche Voir le message
    Autre chose à trancher : les collisions sont-elles détectées deux fois (A détecte sa collision avec B et ce dernier en fait de même de son côté), ou bien une seule fois, ce qui implique dans ce cas une forme de négociation entre A et B pour savoir lequel des deux effectue le traitement (typiquement on commence avec l'un des deux, qui passe ou non le relais à l'autre) ?
    Bonne question, je dirais qu'il est important de faire les collisions dans les deux sens. A entre en collision avec B donc A effectue le traitement mais B doit surement effectuer un autre traitement. Exemple : Link avec Ennemi => Link doit perdre de la vie et reculer, l'Ennemie quand à lui ne dois rien faire (peut être arrêter d'avancer pendant quelques frames).
    Autre exemple : Epée de Link avec Ennemie, cette fois c'est l'Ennemie qui pert de la vie et recule et l'épée continue sa vie (et disparaitra à la fin de son animation).
    Donc faire les tests de collision dans les deux sens me semble approprié ^^
    Encore une fois, je suis naze en conception, c'est pour ca que j'ai besoin de votre aide justement

    C'est bien beau tout cas, si seulement j'avais une once d'idée d'implémentation

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 630
    Points : 30 699
    Points
    30 699
    Par défaut
    En fait, il y a un problème de conception dés le départ...

    Pour bien pouvoir réfléchir en terme de conception OO, tu dois te demander, pour chaque classe, les services que tu es en droit d'en attendre.

    Cela implique un prérequis très important : savoir quelle responsabilité tu va donner à tes classe, dans le respect absolut d'un principe simple : celui de la responsabilité unique.

    En gros, on s'attendrait presque que la plupart de tes classes soient capables de faire le café ou de commander le souper au chinois du coin tellement elles ont de responsabilités, et ca, c'est le meilleur moyen de foncer dans le mur

    Il faut vraiment te dire que si une classe a plus d'une responsabilité, c'est qu'elle en a (beaucoup) trop

    Tu te dis que ton monde est peuplé d'entités soit...

    Mais pose toi la question de savoir quels services tu attends de ton monde et de tes entités (pour ne parler que de ces deux classes pour commencer )

    On pourrait dire que le propre d'une entité est d'être affichée (par exemple)

    Elle n'aurait que faire de sa position réelle, elle s'en fout tout à fait... Le système de tracé se positionne à un endroit donné (celui auquel se trouve l'entité à tracer) et demande à l'entité de se tracer, point barre.

    Mais, sémantiquement parlant, on ne s'attend pas à ce qu'un monde se charge de tracer quoi que ce soit: un monde contient un certain nombre d'entités et permet de déterminer la position de chacune d'elles par rapport aux autres et par rapport à un référentiel donné, c'est tout

    Etant donné que tu subdivise le monde en six zones distinctes, si l'on considère qu'il connait la position de l'ensemble des entités qui se trouvent dans les six zones, on s'attend en priorité à ce qu'il puisse nous donner:
    • L'ensemble des entités se trouvant dans une zone donnée
    • La position d'une entité données par rapport à un référentiel représentant la coordonnée 0,0 de la zone dans laquelle se trouve cette entité
    Ces deux type d'informations seront, par exemple, utilsés d'un coté par le système de tracé ou le système de détection des collisions pour leur travail respectif

    A partir de là, on peut, effectivement, se dire que toute chose devant être affichée peut etre considérée comme une entité, et que nous avons donc des entités particulières telles que sprites, ennemis, joueur, objet d'inventaires ou autres

    Chacun de ses type particulier d'entité aurait une responsabilité propre, et sera, par conséquent, utilisé (en dehors de tout aspect rattaché à l'affichage ou à la gestion des collisions) de manière différente.

    Mais, cela sous entend que, bien que toute entité se retrouve, au final, dans le monde, il y a, pour chaque type particulier d'entité, un système qui prend en charge l'utilisation qui leur est propre.

    Tu aurais, par exemple, tout un système (c'est à dire un ensemble de classes ) qui se chargerait de la création, de la vie (intelligence artificielle ) et de la mort des ennemis, un autre système qui se chargerait de la génération, du déplacement et de la suppression des sprites, ou encore un système prenant en charge le joueur.

    Chaque fois qu'un de ces systèmes de gestion d'un type particulier d'entité en crée une, il l'enregistre, à la position qui est sienne, auprès du monde, de manière à ce que les systèmes d'affichage et de détection de collisions puissent les prendre en compte, chaque fois qu'un de ces système détruit ou déplace une entité, il en tient effectivement le monde informé, mais la décision de créer, de déplacer ou de détruire cette entité va dépendre exclusivement... du système concerné.

    Et, évidemment, le système concerné ne considérera pas les entités dont il a la charge comme des entités, mais bien... comme des entités du type particulier dont il a la charge

    Je veux dire par là que le système (l'ensemble de classes) qui prend en charge la gestion des ennemis, par exemple, considérera l'ensemble des éléments qu'il crée, déplace ou détruit comme étant... des ennemis

    Après, les choses peuvent sans doute nécessiter des intermédiaires!!!

    Par exemple, la création d'un sprite peut etre demandée par deux éléments distincts: l'intelligence artificielle (des ennemis) d'un coté, un événement utilisateur de l'autre.

    Ces deux éléments feront appel au système de gestion de sprite pour lui demander de créer un sprite de tel type, partant de telle position et se déplaçant à telle vitesse dans telle direction.

    le système de sprite s'occupera de créer le sprite en fonction de ce qui lui est demandé, de l'enregistrer auprès du monde (pour qu'il soit affiché), de le déplacer à la vitesse demandée dans la direction demandée et, quand le système de détection des collisions détectera que le sprite est entré en collision avec "quelque chose", le système de gestion des sprite le "désenregistrera" auprès du monde et le détruira.

    De son coté, le système de détection de collision se chargera de mettre en relation le sprite avec l'objet avec lequel il est entré en collision de manière à ce que les deux puissent réagir à cette collision

    Evidemment, quand je parle ici de "systèmes" je veux en réalité parler d'ensembles de classes travaillant de concert pour gérer un type particulier d'entité.

    Nous y trouverons sans doute chaque fois une fabrique (permettant de créer les entités attendues selon certains critères à définir), un "gestionnaire de", et une série de visiteurs susceptibles de manipuler l'ensemble des entités d'un type particulier en fonction de leur évolution "spatio temporelle"

    Je ne vais pas commencer, vu l'heure, à te faire une analyse complete de ce qu'il te faut mettre en oeuvre, mais je crois que je t'ai donné suffisamment de pistes à suivre pour te permettre de reprendre ta conception de manière correcte

    Si tu as un doute quand à la manière de mettre l'une ou l'autre chose en oeuvre, n'hésite pas à demander, que ce soit du point de vue conceptuel ou du point de vue de l'implémentation

  10. #10
    screetch
    Invité(e)
    Par défaut
    bon je prends en cours de route donc je ne peux pas forcément être exhaustif
    déjà je dirais de se méfier des avis des gens et de les prendre avec du recul.
    c'est pas que les gens sont incompétents, c'est que c'est de l'architecture informatique. Et lorsqu'on demande a 1000 informaticiens de résoudre un problème on obtient 1000 réponses différentes et pas toutes sont fausses.


    je ne vodurais donc pas forcément t'envoyer sur une nouvelle voie qui contredit les avis des 43 posteurs précédents, ca serait contre-productif.

    je dirai simplement de manière générale:
    * ce que j'ai lu ici était souvent très compliqué. La raison, c'est un manque de généralisation du souvent au fait que tu (et d'autres gens qui ont posté aussi) restes très très près d'un design "naturel" ou Link est une classe, un Enemy est une classe, un Mur est une classe, etc


    un peu de maths: si tu as n classes, tu as n(n-1) interactions possibles entre classe. La complexité augmente donc au carré, et c'est ainsi que la plupart des logiciels deviennent des plats de spaghettis.

    Pour se débarasser de ce problème la seule solution est d'avoir le moins de classes possibles, au lieu de faire des classes spéciales pour tous les machins qui existent.


    par exemple, si Link est une classe spéciale, tu dois donc résoudre deux problèmes: que se passe t'il lorsque Link touche un mur? et que se passe t'il lorsque un Enemy touche un mur? et quand un Mur touche un Mur?
    si tu n'as qu'une seule classe "Entity" ou "Sprite" ou je ne sais pas, le problème devient: que se passe t'il lorsqu'une Entity touche une Entity?
    bien sûr la réponse est: ca dépend des propriétés de l'Entity en question.
    Mais tu as donc une interaction unique Entity->Entity dont le résultat va dépendre des propriétés (et non plus du TYPE) de l'entité. A toi de voir les propriétés qui auront un sens.

    Link, le mur et l'ennemi deviennent donc des objets très similaires mais aux propriétés différentes.



    Il y a ensuite les Controllers, qui peuvent se spécialiser en AIController pour controller certaines entités et en PlayerController pour controller d'autres entités. Ainsi, l'IA pourrait controller Link, même si ce n'est pas très utile mais donc tu as alors moins d'interaction.







    Donc en gros tu essayes trop de modéliser la "vie réelle" avec tes classes, tu donnes des rôles particuliers a certains objets. Si certains objets sont particuliers, ce n'est cependant pas une raison pour leur donner une classe particulière






    La deuxième erreur générale c'est exactement l'inverse; certains concepts abstraits n'ont rien a faire dans cette discussion, notemment les Design Patterns. J'avoue que j'en ai ma claque de lire Singleton dans un message sur deux; que ce soit un Singleton ou pas, ca ne devrait pas changer fondamentalement le résultat.
    Le plus dur c'est d'ailleurs de faire en sorte d'avoir le moins de liens possibles entre les classes (CF le n au carré plus haut) et en ajoutant Singleton dans un message tu commences par te tirer une balle dans le pieds (ou pire, a tirer une balle dans le pieds d'Aspic)



    de 1, tout est faisable sans singleton et c'est même pas plus dur
    de 2, c'est un détail d'implémentation qui ne devrait pas apparaître sur ce post a ce point, comme un DP Visiteur ou autre.

  11. #11
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut
    C'est vrai qu'au final chacun ayant sa vision de la chose, ca m'embrouille plus qu'autre chose

    Citation Envoyé par koala01 Voir le message
    En fait, il y a un problème de conception dés le départ...
    Ca je veux bien le croire

    Le principe de responsabilité unique ne peut pas toujours être appliqué. Par exemple un moteur de jeu à une méthode de Render() pour dessiner et une méthode de Update() pour mettre à jour les éléments du jeu => on a donc deux responsabilités et pourtant c'est pas mal si ?

    Alors dans mon cas, le moteur de jeu délégue le dessin à une classe Renderer() dont sont UNIQUE but est de dessiner tout ce que je lui demande ^^

    Pour la fonction Update(), on appelle donc la fonction Update() de la classe World mais c'est c'est impossible de définir une responsabilité unique. Le World est responsable de la gestion des entités qui le compose pour moi, donc il est responsable de gérer les collisions, les déplacer, les supprimer quand elles meurent, en ajouter d'autres... (Ouch j'explose le SRP).

    Moi ce que je voulais faire, c'était délégué le déplacement à une interface IMoveBehaviour, les collisions à un ICollisionBehaviour... en utilisant le pattern stratégie.

    Citation Envoyé par koala01 Voir le message
    Evidemment, quand je parle ici de "systèmes" je veux en réalité parler d'ensembles de classes travaillant de concert pour gérer un type particulier d'entité.

    Nous y trouverons sans doute chaque fois une fabrique (permettant de créer les entités attendues selon certains critères à définir), un "gestionnaire de", et une série de visiteurs susceptibles de manipuler l'ensemble des entités d'un type particulier en fonction de leur évolution "spatio temporelle"
    Voilà au final je suis toujours coincé, car les concepts abordés sont trop compliqué pour moi

  12. #12
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 630
    Points : 30 699
    Points
    30 699
    Par défaut
    Citation Envoyé par Aspic Voir le message
    Le principe de responsabilité unique ne peut pas toujours être appliqué. Par exemple un moteur de jeu à une méthode de Render() pour dessiner et une méthode de Update() pour mettre à jour les éléments du jeu => on a donc deux responsabilités et pourtant c'est pas mal si ?
    Là, tu parles de fonction de ta classe...

    Ce sont donc des services que ta classe va rendre dans le cadre de la responsabilité qu'elle a

    Il se peut que la responsabilité d'une classe soit complexe: un moteur de jeu a pour but de... faire fonctionner le jeu, bien évidemment

    A partir de là, il est "logique" que l'on s'attende à un ensemble de services divers et variés, tels que la possibilité de charger une carte ou une partie, d'afficher cette carte, de générer des "mouvements" et de mettre les informations à jour, de jouer des sons, de recevoir des événements d'inputs ou que sais-je

    Mais tous ces services rentrent malgré tout dans le cadre de la responsabilité unique de ton moteur de jeu.

    A partir de là, chaque service sera délégué à une classe particulière dont la responsabilité unique sera... "particulière" au service attendu:

    Ainsi, la classe monde va sans doute s'occuper, comme je le disais plus haut de gérer les positions de l'ensemble des entités que l'on trouve dans le monde, et de faire "le tri" de celles qui doivent être considérées par rapport à la partie "visible" (à un instant T) du monde.

    A coté de la classe monde, nous aurons une classe renderer qui s'occupera de l'affichage (basé sur les entités que le monde renvoie par rapport à la portion visible (à un instant T) du monde, ou une classe "SoundPlayer" qui s'occupera de jouer des sons en fonctions de certains événements qui seront émis, pour leur part, par le système d'intelligence artificielle pour les ennemis et d'autre part par le système de gestion d'input pour le joueur.

    Le système d'intelligence artificielle ne prendra lui-même en compte que les ennemis (considérés comme tels ), gérés par un classe dont c'est l'unique responsabilité (le "gestionnaire d'ennemis") dont le monde (qui les considère comme des entités ) indique qu'ils sont visibles (comprend: dans la portion de monde à afficher à l'instant T particulier ).

    L'avantage de travailler de la sorte, c'est que tu n'as meme plus besoin de jouer avec le pattern singleton (qui est un anti pattern ), car le moteur de jeu dispose d'une (et une seule) instance de monde, de SoundPlayer ou de EnnemyManager, et, comme tu n'auras qu'une seule instance de moteur de jeu (créée dans main() ) tu es sur que tu n'as qu'une seule instance des différents membres qui lui permettent de rendre les services attendus

    Comme le moteur de jeu est, en quelques sortes, le "chef d'orchestre" de tout le brol, c'est lui qui se chargera de fournir en tant qu'arguments les outils nécessaires au services rendus par les différents membres pour leur permettre de travailler correctement


    Alors dans mon cas, le moteur de jeu délégue le dessin à une classe Renderer() dont sont UNIQUE but est de dessiner tout ce que je lui demande ^^
    Exactement
    Pour la fonction Update(), on appelle donc la fonction Update() de la classe World mais c'est c'est impossible de définir une responsabilité unique. Le World est responsable de la gestion des entités qui le compose pour moi, donc il est responsable de gérer les collisions, les déplacer, les supprimer quand elles meurent, en ajouter d'autres... (Ouch j'explose le SRP).
    Justement, non...

    Le monde en lui-même n'est qu'un réceptacle pour tout ce qui peut etre considéré comme une entité à un instant T.

    Il ne sert qu'à permettre la création d'une "photo" instantanée du monde

    Le cycle de vie de toutes les entités qu'il reçoit est géré par "quelque chose d'autre" dont ce sera l'unique responsabilité

    Et quand je parle "d'autre chose", cela peut très bien être un ensemble de classes (un système indépendant de classes) composé, entre autre, d'un "gestionnaire" (de sprites, d'ennemis, d'objets d'inventaire, ou que sais-je) qui se charge de les enregistrer auprès du monde lors de la création et de les en "désenregistrer" lors de leur destruction.

    Mais ce "gestionnaire" travaille en relation avec un ensemble d'autres classes dont les responsabilités respectives sont de gérer les différentes étapes du cycle de vie des objets et des événements qui s'y rapportent

    Si je parle de système de classes indépendantes, c'est parce que chaque système de classes peut exister sans avoir la moindre connaissance des autres systèmes de classes

    Par exemple, tu pourrais très bien commencer par ne créer qu'un système de classes dont la responsabilité globale est la gestion des sprites, avec, "en son centre", un "SpriteManager".

    Avant que tu ne mettes au point les autres systèmes de classes (celui dont la responsabilité globale est la gestion des objets d'inventaires, ou d'ennemis ou de je ne sais quoi), tu pourrais très bien faire en sorte que ton moteur de jeu se contente d'émettre, de manière plus ou moins aléatoire des événements qui provoqueront la création de sprites et tout ce que tu verrais du monde (vu que tout le reste n'existerait pas encore) lorsque tu lances ton jeu, ce serait des sprites qui partent à gauche et à droite, selon différents scénarios

    Juste après, tu pourrait mettre au point le système de classe dont la responsabilité est la gestion des objets d'inventaires, qui doit aussi pouvoir travailler seul...

    Tu pourrais donc faire en sorte que ton moteur de jeu n'émette plus d'évènements spécifiques à la gestion de sprites, mais qu'il émette plutot des événement spécifique à la gestion d'objets d'inventaire, et tout ce que tu verrais alors, ce sont des objets d'inventaire qui apparaissent et qui disparaissent de la carte

    Juste après, tu pourrais faire pareil pour le système de gestion des éléments du décors, puis avec le système de gestion des ennemis et, en activant ou désactivant certaines parties, tu dois pouvoir voir (ou non), le décors, les ennemis, les sprites, les déplacements dans le monde (scroll ) etc

    Au final, lorsque tu réactive l'ensemble, tu auras tous les éléments constitutifs du monde qui apparaissent et qui agissent "de concert" (grace au moteur de jeu )
    Moi ce que je voulais faire, c'était délégué le déplacement à une interface IMoveBehaviour, les collisions à un ICollisionBehaviour... en utilisant le pattern stratégie.
    Tu devras sans doute recourir à un DP stratégie, mais pas au niveau de monde

    C'est au niveau de tous les ensembles de classes (tous les "systèmes de classes") indépendants les uns des autres qui considèrent chaque fois les entités (qui interviennent dans l'instantané renvoyé par monde) comme étant du type particulier "réel" que ces stratégies seront mise en oeuvre et utilisée.
    Voilà au final je suis toujours coincé, car les concepts abordés sont trop compliqué pour moi
    C'est justement pour cela que tu dois impérativement déléguer les responsabilités, de manière à réfléchir, point par point, à tout ce qui permet de gérer le "cycle de vie" d'un type particulier d'entité, sans t'inquiéter des autres types particuliers d'entités.

    Il n'y aura que deux systèmes de classes (hormis le monde) qui considéreront les différents types d'entités en tant qu'entité "générique" (ne serait ce que provisoirement ) c'est le système dont la responsabilité globale est la gestion de l'affichage et celui dont la responsabilité globale est la gestion des collisions

    Tous les autres systèmes de classes travailleront sur avec des types de classes particuliers et l'ensemble aura sans doute comme "fil d'Ariane" un système de "gestion d'événements" permettant aux différents systèmes de communiquer entre eux (car tout réagis en fonction des événements provoqués par son entourage )

  13. #13
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut
    Citation Envoyé par koala01 Voir le message
    A partir de là, chaque service sera délégué à une classe particulière dont la responsabilité unique sera... "particulière" au service attendu:
    Donc selon toi, ca serait à la classe GameEngine de posséder un pointeur sur chaque "service délégué" ? c'est à dire un pointeur sur SpriteLoader dont l'unique but est de charger des sprites, un autre sur EnemyManager dont l'unique but est de gérer les ennemies, SoundLoader...

    Car pour moi ca serait plutot à World d'avoir tout ca...
    Voilà la code du moteur de jeu pour les fontions Update() et render() :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    bool Core::GameEngine::update()
    {
        pInputManager->update();
     
        // MAJ des controlleurs (clavier, joystick)
        int directions = pInputManager->getDirectionsPressed();
        int buttons = pInputManager->getButtonsPressed();
     
        // MAJ du jeu
        return pWorld->update(directions, buttons);
    }
     
    bool Core::GameEngine::render()
    {
        bool bOk = true;
     
        // efface l'écran
        pRenderer->clearScreen();
     
        // dessine dans le double buffer les entités du jeu
        bOk &= pWorld->draw(*pRenderer);
     
        // affiche les mises à jour sur l'écran
        pRenderer->updateWindow();
     
        if (!bOk)
           LError << "Fail to render the game.";
     
        return bOk;
    }
    On voit bien que le moteur de jeu délègue à World le travail de gérer les entités, les collisions, les sons, les evements... enfin tout ce qui fais un bon

    Alors que la fonction render() reset l'écran puis délègue à World l'affichage des entités puis blit le résultat sur l'écran.
    Citation Envoyé par koala01 Voir le message
    Ainsi, la classe monde va sans doute s'occuper, comme je le disais plus haut de gérer les positions de l'ensemble des entités que l'on trouve dans le monde, et de faire "le tri" de celles qui doivent être considérées par rapport à la partie "visible" (à un instant T) du monde.
    Alors je ne l'ai pas dit (désolé) mais le monde ne connait pas tous les ennemies de la carte totale. En fait, le chargement des entités (ennemies et autres) se fait au moment su scroll (changement de zone). De ce fait, la classe World a déjà l'ensemble des Entity visible à l'écran
    Citation Envoyé par koala01 Voir le message
    Le monde en lui-même n'est qu'un réceptacle pour tout ce qui peut etre considéré comme une entité à un instant T.

    Il ne sert qu'à permettre la création d'une "photo" instantanée du monde

    Le cycle de vie de toutes les entités qu'il reçoit est géré par "quelque chose d'autre" dont ce sera l'unique responsabilité
    Actuellement, c'est le monde lui même qui créé les entités, il ne les recoit pas de quelqu'un d'autre.

    En fait, si tu avais un exemple ou un diagramme de classe de ce que tu essayes de m'expliquer j'y verrais plus claire car là sincèrement, je ne suis plus du tout...

  14. #14
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 630
    Points : 30 699
    Points
    30 699
    Par défaut
    Citation Envoyé par Aspic Voir le message
    Donc selon toi, ca serait à la classe GameEngine de posséder un pointeur sur chaque "service délégué" ? c'est à dire un pointeur sur SpriteLoader dont l'unique but est de charger des sprites, un autre sur EnemyManager dont l'unique but est de gérer les ennemies, SoundLoader...
    Presque...

    GameEngine ne devrait avoir qu'un (meme pas pointeur) membre SpriteManager, et meme pas de SpriteLoader

    Le SpriteManager utilise un "SpriteFactory" (qu'il connait en tant que membre et c'est SpriteFactory qui fera appel à SpriteLoader, uniquement lors de l'initialisation, pour charger le "prototype" des sprites qu'elle devra générer
    Car pour moi ca serait plutot à World d'avoir tout ca...
    Voilà la code du moteur de jeu pour les fontions Update() et render() :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    bool Core::GameEngine::update()
    {
        pInputManager->update();
     
        // MAJ des controlleurs (clavier, joystick)
        int directions = pInputManager->getDirectionsPressed();
        int buttons = pInputManager->getButtonsPressed();
     
        // MAJ du jeu
        return pWorld->update(directions, buttons);
    }
     
    bool Core::GameEngine::render()
    {
        bool bOk = true;
     
        // efface l'écran
        pRenderer->clearScreen();
     
        // dessine dans le double buffer les entités du jeu
        bOk &= pWorld->draw(*pRenderer);
     
        // affiche les mises à jour sur l'écran
        pRenderer->updateWindow();
     
        if (!bOk)
           LError << "Fail to render the game.";
     
        return bOk;
    }
    On voit bien que le moteur de jeu délègue à World le travail de gérer les entités, les collisions, les sons, les evements... enfin tout ce qui fais un bon

    Alors que la fonction render() reset l'écran puis délègue à World l'affichage des entités puis blit le résultat sur l'écran.
    Cela montre bien que tu veut donner beaucoup trop de responsabilités à ton monde : ce que tu explique là est bien plus du ressort du gameEngine (qui devrait d'ailleurs le déléguer à d'autres classes )que du monde, que diable !!!!
    Alors je ne l'ai pas dit (désolé) mais le monde ne connait pas tous les ennemies de la carte totale. En fait, le chargement des entités (ennemies et autres) se fait au moment su scroll (changement de zone). De ce fait, la classe World a déjà l'ensemble des Entity visible à l'écran

    Actuellement, c'est le monde lui même qui créé les entités, il ne les recoit pas de quelqu'un d'autre.
    Ca, ce n'est qu'un détail mais tu comprendras assez rapidement
    En fait, si tu avais un exemple ou un diagramme de classe de ce que tu essayes de m'expliquer j'y verrais plus claire car là sincèrement, je ne suis plus du tout...
    j'étais, justement, occupé à préparer une réponse avec des morceaux de code...

    Elle est assez longue à écrire, mais, si tu patiente un tout petit peu, tu verras, tout deviendra limpide

  15. #15
    Expert confirmé
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Points : 4 388
    Points
    4 388
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Elle est assez longue à écrire, mais, si tu patiente un tout petit peu, tu verras, tout deviendra limpide
    Ouais un exemple ^^

    Si seulement ca pouvait être vrai Il est vrai que je comprends 20 fois plus vite avec un exemple plutôt qu'un long discours, et j'espère que ca sera le cas donc je vais être patient

    PS : j'ai déjà un moteur de jeu qui fonctionne bien avec la gestion du fps via des timers, un manager des Input, etc.
    Je mets la procédure de démarrage du moteur de jeu, sait-on jamais ca peut servir :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    void Core::GameEngine::run()
    {
        if (!bRunning)
        {
            bool ok = true;
            int frames_done = 0;
            int old_time = 0;
     
            // lancement des timers du moteur de jeu
           .......
     
            // boucle du jeu principale
            bRunning = true;
            while (bRunning && !pInputManager->getEscape() && !GameEngine::closeWindows && ok)
            {
                // pour éviter que le CPU soit à 100%
                while(ticks == 0)
                {
                    rest(100 / fps);
                }
     
                while(ticks > 0)
                {
                    int old_ticks = ticks;
     
                    // MAJ des entites du moteur de jeu
                    ok = this->update();
     
                    ticks--;
                    if(old_ticks <= ticks)
                    {
                        LWarning << "\tABORD run()";
                        break;
                    }
                }
     
                if(gameTime - old_time >= 10)//i.e. a second has passed since we last measured the frame rate
                {
                    fps = frames_done;
                    //fps now holds the the number of frames done in the last second
                    //you can now output it using textout_ex et al.
                    LWarning << "fps : " << fps;
                    //reset for the next second
                    frames_done = 0;
                    //gameTime = 0;
                    old_time = gameTime;
                }
     
                // AFFICHAGE DU RESULTAT
                ok = this->render();
     
                frames_done++;//we drew a frame!
            }
     
            // suppression des timers
           ......
        }
    }
    Merci beaucoup !

  16. #16
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 493
    Points
    5 493
    Par défaut
    @Screetch
    Je tiens à dire que je n'ai mentionné le pattern Singleton que parce qu'Aspic demandait s'il allait devoir passer ces données en argument à chaque fois. Je ne voulais que mettre en évidence, en utilisant le pattern le mieux connu pour ça, le fait qu'utiliser des membres statiques était plus adapté. Après, chacun fait ce qu'il veut et pour ma part je n'utiliserais pas un singleton.

    Ensuite, oui, je rentre parfois trop dans les détails. Parfois à tort, notamment dans mon dernier message posté assez tardivement, mais parfois parce qu'Aspic est demandeur.


    @Aspic
    Avant toute chose, nous semblons avoir un problème de sémantique, tu comprends de travers la notion de détention des responsabilités dans le contexte de conception objet. Ainsi, quand tu nous as montré ce code où World parcourt les entités en appelant pour chacune d'elles "update", tu nous as dit que c'était World qui avait donc la responsabilité de mettre à jour la position des entités. C'est faux : World demande aux entités de se mettre à jour mais ce sont bien elles-mêmes qui décident quoi changer et que faire des informations fournies (ou peut-être délèguent-elles à une autre classe). Dans ton exemple, c'est donc bien Entity qui possède cette responsabilité et non World. World a au mieux la responsabilité de requérir des entités qu'elles se mettent à jour. Ce qui n'a rien d'anormal, tout ça est correct. Le rôle de chef d'orchestre est de toute façon inévitable puisqu'il faut bien que la pile des appels ait une racine, le tout est que cela soit fait avec un faible couplage et ça ne veut pas dire pour autant que le chef d'orchestre a toutes les responsabilités du monde.

    De même, tu nous as dit que Entity avait pour responsabilité le rendu. C'est faux là aussi, Entity ne fait que spécifier la position de chaque sprite. En somme, Entity ne fait que spécifier ce qui sera rendu et où mais les détails du rendu (batching, appel des API graphiques, etc) sont gérés par autre chose, peut-être sprite ou peut-être une autre classe.



    Maintenant, tu me disais plus haut que, quand Link donne un coup d'épée, ton moteur créera une autre entité "épée". Là il me semble que tu as un problème de découpage et d'assignation des responsabilités. En effet, imagine que durant son coup d'épée, Link soit touché. L'animation doit alors être interrompue et Link projeté en arrière. On voit bien que le comportement de Link et de l'épée, même s'ils sont représentés par deux sprites distincts, sont liés. En clair, il faut un chef d'orchestre pour le personnage de Link (et, plus généralement, pour toute créature, ou tout ensemble de créatures si tu suis la piste de Koala01). De même tu peux aussi avoir un ennemi composé de zones vulnérables ou non qui sont rattachés au même pool de PV.

    Il me semble donc qu'une entité peut être composée de N sprites et de M zones de collision ayant des conséquences différentes. Chercher à faire cette économie en forçant un modèle "une entité = un sprite = une zone de collision", c'est à mon avis se créer des problèmes et vouloir réunir de force des responsabilités distinctes.

    Une entité (ou un gestionnaire d'entités multiples mais similaires comme le propose Koala01) devrait a priori avoir les responsabilités suivantes (dont certaines sont éventuellement déléguées à des comportements) :
    * Gestion de l'IA (ou gestion par le joueur)
    * États internes (vie, position, etc)
    * Réactions aux collisions (voir déléguer ça aux zones de collision)
    * Maintien/passage des sprites
    * Maintien/passage des zones de collision

    En vrac :
    * Passer "this" en argument n'a rien de choquant
    * Le PlayerBehaviour que j'avais mentionné venait du fait que je croyais que tu comptais déléguer à des comportements la gestion de l'IA (et, dans le cas du joueur, la réaction à l'état du contrôleur). Accessoirement cela me semble une bonne idée dans la mesure où l'IA va varier au cours du temps (un bond en arrière suite à un coup suspend le reste, un ennemi peut avoir deux comportements selon qu'il a ou non repéré Link, etc)
    * Pour les collisions, deux bêtes boucles for imbriquées feront l'affaire.

  17. #17
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Décembre 2010
    Messages
    734
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Décembre 2010
    Messages : 734
    Points : 1 475
    Points
    1 475
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    @Screetch
    Je tiens à dire que je n'ai mentionné le pattern Singleton que parce qu'Aspic demandait s'il allait devoir passer ces données en argument à chaque fois. Je ne voulais que mettre en évidence, en utilisant le pattern le mieux connu pour ça, le fait qu'utiliser des membres statiques était plus adapté.
    Je moinssoie violamment. Il est possible de procéder différement, notamment en transmettant la référence du moteur de jeu aux gestionnaires à l'instanciation pour qu'ils puissent lui relayer les évènements.
    C'est à mon sens mieux car du coup le lien est:
    1) plus facile à repérer
    2) plus facile à changer au besoin (ex: tests)

  18. #18
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Décembre 2010
    Messages
    734
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Décembre 2010
    Messages : 734
    Points : 1 475
    Points
    1 475
    Par défaut
    Ne voulant pas abuser des édits après coup, cette fois-ci je précise par un second message: pour qu'on puisse changer, il est bon que la référence au moteur de jeu soit typée d'une interface (style gestionnaire d'évènements) dont on puisse fournir plusieurs implémentations comme:
    1) le moteur de jeu
    2) diverses classes de test permettant de tester les sous-systèmes séparément, je trouve que ça aide quand on développe un ensemble complexe.

  19. #19
    screetch
    Invité(e)
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    @Screetch
    Je tiens à dire que je n'ai mentionné le pattern Singleton que parce qu'Aspic demandait s'il allait devoir passer ces données en argument à chaque fois. Je ne voulais que mettre en évidence, en utilisant le pattern le mieux connu pour ça, le fait qu'utiliser des membres statiques était plus adapté. Après, chacun fait ce qu'il veut et pour ma part je n'utiliserais pas un singleton.

    Ensuite, oui, je rentre parfois trop dans les détails. Parfois à tort, notamment dans mon dernier message posté assez tardivement, mais parfois parce qu'Aspic est demandeur.


    @Aspic
    Avant toute chose, nous semblons avoir un problème de sémantique, tu comprends de travers la notion de détention des responsabilités dans le contexte de conception objet. Ainsi, quand tu nous as montré ce code où World parcourt les entités en appelant pour chacune d'elles "update", tu nous as dit que c'était World qui avait donc la responsabilité de mettre à jour la position des entités. C'est faux : World demande aux entités de se mettre à jour mais ce sont bien elles-mêmes qui décident quoi changer et que faire des informations fournies (ou peut-être délèguent-elles à une autre classe). Dans ton exemple, c'est donc bien Entity qui possède cette responsabilité et non World. World a au mieux la responsabilité de requérir des entités qu'elles se mettent à jour. Ce qui n'a rien d'anormal, tout ça est correct. Le rôle de chef d'orchestre est de toute façon inévitable puisqu'il faut bien que la pile des appels ait une racine, le tout est que cela soit fait avec un faible couplage et ça ne veut pas dire pour autant que le chef d'orchestre a toutes les responsabilités du monde.

    De même, tu nous as dit que Entity avait pour responsabilité le rendu. C'est faux là aussi, Entity ne fait que spécifier la position de chaque sprite. En somme, Entity ne fait que spécifier ce qui sera rendu et où mais les détails du rendu (batching, appel des API graphiques, etc) sont gérés par autre chose, peut-être sprite ou peut-être une autre classe.



    Maintenant, tu me disais plus haut que, quand Link donne un coup d'épée, ton moteur créera une autre entité "épée". Là il me semble que tu as un problème de découpage et d'assignation des responsabilités. En effet, imagine que durant son coup d'épée, Link soit touché. L'animation doit alors être interrompue et Link projeté en arrière. On voit bien que le comportement de Link et de l'épée, même s'ils sont représentés par deux sprites distincts, sont liés. En clair, il faut un chef d'orchestre pour le personnage de Link (et, plus généralement, pour toute créature, ou tout ensemble de créatures si tu suis la piste de Koala01). De même tu peux aussi avoir un ennemi composé de zones vulnérables ou non qui sont rattachés au même pool de PV.

    Il me semble donc qu'une entité peut être composée de N sprites et de M zones de collision ayant des conséquences différentes. Chercher à faire cette économie en forçant un modèle "une entité = un sprite = une zone de collision", c'est à mon avis se créer des problèmes et vouloir réunir de force des responsabilités distinctes.

    Une entité (ou un gestionnaire d'entités multiples mais similaires comme le propose Koala01) devrait a priori avoir les responsabilités suivantes (dont certaines sont éventuellement déléguées à des comportements) :
    * Gestion de l'IA (ou gestion par le joueur)
    * États internes (vie, position, etc)
    * Réactions aux collisions (voir déléguer ça aux zones de collision)
    * Maintien/passage des sprites
    * Maintien/passage des zones de collision

    En vrac :
    * Passer "this" en argument n'a rien de choquant
    * Le PlayerBehaviour que j'avais mentionné venait du fait que je croyais que tu comptais déléguer à des comportements la gestion de l'IA (et, dans le cas du joueur, la réaction à l'état du contrôleur). Accessoirement cela me semble une bonne idée dans la mesure où l'IA va varier au cours du temps (un bond en arrière suite à un coup suspend le reste, un ennemi peut avoir deux comportements selon qu'il a ou non repéré Link, etc)
    * Pour les collisions, deux bêtes boucles for imbriquées feront l'affaire.
    ne prend pas mal ce que j'ai dit, je crois que c'est surtout Aspic qui rentre trop tôt dans les details.

  20. #20
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 630
    Points : 30 699
    Points
    30 699
    Par défaut
    Allez, pour t'aider un peu à comprendre ce que je veux expliquer, je vais sortir du coté abstrait de tout ce que je t'explique pour te donner une idée de l'implémentation telle que je la conçois

    Une petite précision: j'ai considéré que l'on pouvait charger l'ensemble du monde (et je ne veux pas reprendre l'ensemble de la réponse que j'ai déjà écrite pour changer )

    Nous sommes partis sur l'idée que tout ce qui constitue le monde est "Entity", et que la responsabilité d'une Entity est de se positionner dans le monde.

    Pour se positionner dans le monde, elle a besoin de connaitre les coordonnées où se placer, et de pouvoir etre tracée.

    Seulement, elle ne peut etre tracée que si elle est visible ( si elle se trouve dans la portion du monde que l'on aperçoit à l'écran).

    Nous avons donc besoin d'une classe (qui a sémantique de valeur "Coordinate" qui pourrait ressembler à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Coordinate
    {
        public:
            Coordinate(int x, int y):x_(x),y_(y){}
            int x() const{return x_;}
            int y() const{return y_;}
    };
    Et la classe Entity pourrait ressembler à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    class Entity
    {
        public:
            Entity(int xpos, int ypos, bool visible =false):pos_(x,y),visible_(visible){}
             virtual ~Entity(){}
            // permet de savoir si l'entité est visible 
            bool isVisible() const{return visible_;}
            // permet de rendre l'entité visible ou invisible
            void setVisibility(bool visibility){visible_ = visibility;}
            /* permet de connaitre la coordonnée sur l'axe des x
             * par rapport au "coin supérieur" du monde
             */
            int xpos() const{return pos_.x();}
            /* permet de connaitre la coordonnée sur l'axe des y
             * par rapport au "coin supérieur" du monde
             */
            int ypos() const{return pos_.y();}
            /* je vais considérer qu'il est possible de bouger n'importe quelle
             * entité jusqu'à ce qu'on ait la preuve qu'on ne peut pas le faire
             * cela limitera les redéfinitions à la classe dérivée de Entity
             * qui servira de base aux objet non mobiles (éléments de décors
             * et / ou objets d'inventaire
             */
            virtual void moveTo(Coordinate const & c){pos_=c;}
            /* et j'ai la possibilité de la dessiner... j'utilise l'idiome NVI
             * car je veux que le tracé de l'objet ne se fasse que s'il est visible ;)
             */
             void draw() const 
             {
                 if(isVisible())
                     drawMe();
             }
        private:
            Coordinate pos_; // pour avoir la position de l'entité
            bool visible_; // pour savoir s'il est visible
            /* la fonction qui devra être redéfinie quand on saura comment 
             * l'afficher :D (dans les classes dérivées)
             */
            virtual void drawMe() const = 0;
    };
    Il existe deux grandes catégories d'entités: celles qui peuvent bouger (on y retrouve le joueur, les ennemis, les sprites, ... et, pourquoi pas,quelques éléments de décors sur lesquels on ne peut pas agir (un cerf passant au loin ) ) et celles qui ne peuvent pas bouger (éléments de décors, objets d'inventaire, ...)

    Autant faire la distinction tout de suite, de manière à pouvoir gérer tous les objets qui peuvent bouger d'un coté et tous ceux qui ne peuvent pas bouger de l'autre

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class MovableEntity : public Entity
    {
        public:
            MovableEntity (int xpos, int ypos, bool visible =false):Entity(xpos, ypos){}
    };
    class NonMovableEntity : public Entity
    {
        public:
            NonMovableEntity (int xpos, int ypos, bool visible =false):Entity(xpos, ypos){}
            /* si on invoque la fonction move sur quelque chose qui ne peut pas
             * bouger, il ne se passe tout simplement rien :D
             */
            virtual void moveTo(Coordinate const & c){}
    }
    Le monde se contente de connaitre toutes les entités qui le composent (quelles qu'elles soient) et de les marquer visibles ou non en fonction de leur position par rapport à la portion visible

    On doit donc pouvoir y rajouter ou en retirer des entités "à la demande"
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
     
    class World
    {
        /* il faut etre clair : le monde n'a aucune responsabilité quant à la
         * gestion de la durée de vie des entités qu'il connait : il ne fait que
         * les utiliser :D
         */
        void registerEntity(Entity* entity)
        {
             entities_.push_back(entity);
             needSorting_ = true;
        }
        void unregisterEntity(Entity * entity)
        {
            if(needSorting_)
                sort();
            std::vector<Entity*>::iterator it = std::find( entities_.begin(), entities_.end(), entity);
            if(it!=entities_.end())
            {
                entities_.erase(it);
                needSorting_= true;
             }
        }
        /* met à jour la visibilié de toutes les entités qu'il connait
         * sur base de la position du coin supérieur gauche de la partie
         * à afficher 
         */
        void updateVisibility(Coordinate const & topLeft)
        {
            /* je vais considérer que la portion affichée est de 100 * 200...
             * à toi d'adapter ;)
             */
            Coordiante bottomRight(topLeft.x()+100, topLeft.y()+200);
            for(std::vector<Entity*>::iterator it= entities_.begin();it!=entities_.end(); ++ it)
            {
                bool temp= ( (*it)->xpos >=topLeft ().x()||
                             (*it)->xpos <= bottomRight.x()) &&
                           ( (*it)->ypos >=topLeft ().y()||
                             (*it)->xpos <= bottomRight.y());
                 (*it)->setVisibility(temp);
     
            }
        }
        std::vector<Entity*> visibleEntities() const
        {
            std::vector<Entity*> visibles;
            for(std::vector<Entity*>::iterator it= entities_.begin();it!=entities_.end(); ++ it)
           {
                if((*it)->isVisible())
                    visibles.push_back(*it);
           }
           return visibles;
        }
        private:
            std::vector<Entity*> entities_;
            bool needSorting_;
            void sort()
            {
                std::sort(entities_.begin(), entities_.end();
            }
    }
    Et c'est tout

    La plupart des gestionnaires (de sprites, d'ennemis, d'objet d'inventaire et autres) agiraient en réalité comme sur base de gestionnaires d'événement, et pourraient très bien être considérés comme des "visiteurs" d'événement

    Nous avons donc une hiérarchie d'événements (qui seraient les objets visités) et une hiérarchie de gestionnaires d'événements

    La classe de base de l'événement ressemblerait à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class EventManager;
    class Event
    {
        public:
            virtual ~Event(){}
    };
    et serait, dérivée en plusieurs grandes catégories d'événement, comme, par exemple, les événements propres à des sprites:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class SpriteEvent : public Event
    {
        public:
           /* on sait, quand on travaille sur un événement propre aux sprites
            * que c'est le SpriteManager qui devra gérer l'événement :D
            * seulement, on ne sait pas encore comment il va le gérer ;)
            */
            virutal void accept(SpriteManager &) = 0;
    };
    et cette classe sera, par exemple, elle-même dérivée en SpriteCreationEvent (l'événement qui provoquera la création du sprite ) et SpriteDestructionEvent (l'événement qui en provoquera la destruction )

    Cela prendra la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    class SpriteCreationEvent : public SpriteEvent
    {
        public:
            virtual void accept(SpriteManager & sm)
            {
                sm.visit(*this);
            }
            /* les informations requises pour la création du sprite ;) */
    };
    class SpriteDestructionEvent : public SpriteEvent
    {
        public:
            virtual void accept(SpriteManager & sm)
            {
                sm.visit(*this);
            }
            const Sprite * toDestruct() const {return sprite_;}
        private:
            Sprite * sprite_;
    };
    Quant au SpriteManager, il va réagir à ces événements, et gérer le sprite dont la construction est demandée par l'un et la destruction demandée par l'autre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
     
    class SpriteManager
    {
        public:
            /* pour la facilité, on peut décider que le spriteManager connait le 
             * monde, mais l'idéal serait quand meme de faire sans :D
             */
            SpriteManger(World &w):world_(w){}
            void visit(SpriteCreationEvent const & sce)
            {
                /* on récupère ici les informations nécessaires à la création
                 * du sprite
                 */
                /* puis on en demande la création à la fabrique de sprites */
                Sprite* toadd = facotry_.create(/* paramètres utiles */);
                registerSprite(toadd);
            }
            void visit(SpriteDestructionEvent const & sde)
            {
                const Sprite* todell = sde.toDestruct();
                unregisterSprite(todell);
            }
            void update()
            {
                 /* on calcule le temps, qui servira de base pour la mise à jour
                  * de tous les sprites ;)
                  */
                 int time = /* ... */
                /* puis on demande à tous les sprites de se mettre à jour en
                 * fonction de ce temps ;)
                 */
                for(std::vector<Sprite*>:iterator it=sprites_.begin(); it!=sprites_.end(); ++it)
                    (*it)->updatePosition(time);
            }
        private:
            World & world_;
            SpriteFactory factory_;
            std::vector<Sprite*> sprites_;
            bool needSorting_;
            /* l'enregistrement d'un sprite se fait en deux temps :
             * auprès du monde pour qu'il puisse le gérer en tant qu'entité
             * et auprès du gestionnaire lui meme, pour qu'il puisse le gérer en
             * tant que sprite ;)
             */
             void registerSprite(Sprite * toadd)
             {
                 world_.registerEntity(toadd);
                 sprites_.push_back(toadd);
             }
            /* le "désenregistrement" d'un sprite se fait de la meme manière ;)
             */
             void registerSprite(Sprite * torem)
             {
                 world_.unregisterEntity(torem);
                 if( needSorting_)
                     sort();
     
                std::vector<Sprite*>::iterator it = std::find( sprites_.begin(), sprites_.end(), torem);
                if(it!=sprites_.end())
                {
                    sprites_.erase(it);
                    needSorting_= true;
                 }
                 delete  torem;
             }
            void sort()
            {
                std::sort(entities_.begin(), entities_.end();
                 needSorting_ = false;
            }
    };
    et tu aurais, bien évidemment, quelque chose de très similaire pour ce qui est de la gestion des ennemis, des éléments de décors (mobiles, s'il y en a et non mobiles), des objets d'inventaire ou de toute grande catégorie d'Entity que tu pourrais généraliser

    Le render pourrait alors prendre une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Render
    {
        public:
            void draw(World const &w)
            {
                std::vector<Entity*>  todraw= w.visibleEntities();
                for(std::vector<Entity*>::iterator it= todraw_.begin(); it!=todraw_.end(); ++ it)
               {
                    (*it)->draw();
               }
            }
            void clear()
            {
                /* ce qu'il faut pour faire le ménage */
            }
            /* le reste qui peut l'aider dans sa tache :D */
    };
    Le tout serait amoureusement mis en oeuvre et utilisé par... ton GameEngine, sous une forme sans doute proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    class GameEngine
    {
        public:
            GameEngine():spriteManager_(world_),
                          ennemiManager_(world_),
                          itemsManager_(world_),
                          setsManager_(world_){}
             void run()
             {
                  setManager_load("filename");
                  while(!stop)
                  {
                     /* en fait, il faudrait une machine à état, car on devra
                      * sans doute gérer le menu principal et / ou la gestion
                      * des options de jeu, mais pour ce qui en est du jeu,
                      * cela pourrait se limiter à 
                      */
                      computeChanges();
                      update();
                      soundPlayer_.play();
                      draw();
                  }
              }
        private:
            World world_;
            Coordinate visiblePositionStart_;
            SpriteManager spriteManager_;
            EnnemyManager ennemiManager_;
            ItemsManager itemsManager_;
            /* tout ce qu'il faut pour que le gameEngine fonctionne :D */
            void comupteChanges()
            {
                /* gestion du temps, des événements, de la popote interne ;)
                 */
            }
            void update()
            {
                /* toutes les mises à jour utiles, dont */
                world.updateVisibility(visiblePositionStart_);
            }
             void draw()
             {
                  render_.clear();
                  render_.draw(world_);
    };
    J'ai fait tout cela en vitesse, et je n'ai pas testé le code, bien entendu...

    Il y a donc très certainement des choses à rajouter, à modifier ou à adapter, mais tu devrais maintenant avoir tout ce qu'il te faut pour y arriver

Discussions similaires

  1. [DESIGN J2ME] Utilisation du pattern STATE ?
    Par BerBiX dans le forum Java ME
    Réponses: 0
    Dernier message: 04/12/2008, 16h55
  2. [Stratégie] Pattern Strategie avec parametres variables
    Par new.proger dans le forum Design Patterns
    Réponses: 3
    Dernier message: 10/06/2007, 21h48
  3. BDS - utilisation des Patterns
    Par RamDevTeam dans le forum Delphi .NET
    Réponses: 1
    Dernier message: 05/11/2006, 18h35
  4. Réponses: 4
    Dernier message: 22/12/2004, 15h28

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo