Je te rassure, ce n'est pas le cas, ta critique était légitime et j'ai simplement tenu à m'expliquer sur au moins un des points.
Version imprimable
Tu as peut être raison :)
En fait je crois que je commence à comprendre cette histoire de sémantique de responsabilités :
Quand je fais ca dans la classe World, pour moi c'est World qui avait la responsabilité de déplacer et de mettre à jour les animations. Apparemment c'est faux :DCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 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; }
Donc je dirais que en fait vu que la méthode move de Entity appelle la méthode onMove du IMoveBehaviour:
Code:
1
2
3
4
5
6
7
8 .... IMoveBehaviour* pMoveBehaviour; // pointeur sur son type de déplacement .... bool Entity::move(int directions) { return pMoveBehaviour->onMove(*pSprite, directions); }
Je suppose que c'est donc IMoveBehaviour qui a la responsabilité de déplacer les Entités ? Est ce correct ou pas ?Code:
1
2
3
4
5 class IMoveBehaviour { public: virtual bool onMove(Core::ISprite& sprite, int directions) = 0; };
De même pour le dessin dans la classe World :
On fait appel à la méthode draw() de Entity :Code:
1
2
3
4
5
6
7
8
9
10
11
12 bool World::draw(const Core::IRenderer& r) { bool bOk = true; std::vector<IEntity*>::const_iterator it; for (it = entities.begin(); it != entities.end(); ++it) { bOk &= (*it)->draw(r); } return bOk; }
Et cette méthode fait aussi appelle à la méthode draw() du sprite qui fait elle même appelle à la méthode drawSurface() du Renderer.Code:
1
2
3
4 bool Entity::draw(const Core::IRenderer& r) { return pSprite->draw(r); }
Donc au final, c'est bien Renderer qui est responsable de l'affichaga des Entity ?
Yop,
En ayant juste lu rapidement de fil de discussion, tu te prend la tête pour des truc simple.
Tu as une classe World, qui contient toutes tes entités (d'une façon ou d'un autre)
Tes entités peuvent être de plusieurs types, mais ils ont tous un point commun, ils peuvent etre mis a jour et affiché
Donc, ton "world", lui se contente de parcourir tes entités, et de les mètre a jour l'un après l'autre avec "update", puis les affiché avec "draw"
Pour les déplacements, gère ça directement dans ta classe,
imaginons que player hérite de unit qui herite de entity, tu surcharge "update" dans player et tu code ton comportement "si flèche gauche et pas collision alors je me déplace a gauche"
pour Enemy, tu surcharge "update" et tu code ton comportement "si cible a droite et pas collision alors je me déplace a droite"
Pour le dessin, pareil, tu gères ça dans ta classe
un "enemy" a un nom affiché au dessus de sa tete ? bah tu surcharge "draw" pour afficher le nom en plus de ce que fait déjà de "draw" de "entity"
un "enemy_quifaittresmal" qui hérite de "enemy" bah lui c'est pareil sauf que en plus de dessiner le nom, tu lui dessine une tête de mort a coté du nom
Après a toi de décider ce qui fait d'une classe qu'elle mérite d’être une classe (player est controllé par le clavier) et ce qui peut être un simple paramètre (unité se déplace a une vitesse x, pour player x=10, pour enemy x=8)
Salut,
Le problème n'est pas là... ca j'ai bien compris comment utiliser un simple polymorphisme :P
Par contre, j'aimerais bien savoir si j'ai bien compris cette histoire de responsabilité (cf mon post précédent) :)
En attendant, je vais continuer à étudier le code de Koala01 ;)
Bonjour Aspic.
Sur le début, tu as tout à fait raison, c'est bien MoveBehaviour qui détient dans ce cas la responsabilité du déplacement. Sur le draw, c'est un peu moins net et on pourrait ergoter mais cela reviendrait à débattre du nombre d'anges sur une tête d'épingle. Pour ma part je m'y serais pris un peu différemment mais peu importe.
Par conte, sur le movebehaviour, il y a quand même une chose assez laide : tu passe à toutes tes entités l'état du contrôleur alors que seul le joueur va avoir besoin de ces informations, c'est assez tordu. Les diverses propositions qui ont été faîtes pour éviter cela (utiliser des membres statiques pour exposer certaines informations ou passer une instance d'une classe contenant des références vers tous les services importants) valent à mon avis un coup d'oeil.
Utiliser des membres statiques c'est comme des variables globales et pour moi ce n'est pas mieux du tout ;)
Utiliser une instance d'une classe contenant tout ce qu'il faut revient à faire une God Class et la passer partout dans les fonctions, c'est pas top non plus :(
Mais effectivement, tu as raison la direction n'est utilisée que par Le PlayerBehaviour et pas les autres, comment résoudre ce problème ? Je n'en ai aucune idée :aie:
Autre chose, je dois gérer le Scroll (changement d'écran comme dans un zelda classique), pendez vous qu'il faut faire une classe à part et y mettre une référence dans ma classe World ? Ou alors directement gérer dans World (mais ce n'est pas son rôle je pense...) ...
Au fait, concernant le MoveBehaviour...
Certains types d'entités (en fait, celles qui sont déplaçables dans mon exemple) ont effectivement un comportement de déplacement...
Mais il faut être clair : non seulement, c'est une relation AS UN (donc, nous sommes plus proche de la composition ou de l'aggrégation que de l'héritage, mais, en plus, ce n'est pas le cas de n'importe quelle entité...
Il n'y a en effet aucune raison pour qu'un élément (immobile) du décors n'aie besoin d'un quelconque comportement de déplacement ;)
Par contre, on peut parfaitement envisager le fait qu'il puisse exister différents type de comportement de déplacement !
L'exemple simple est qu'un ennemi va se déplacer sur base des décisions de l'IA (par exemple, en utilisant l'algorithme A* ;) ), alors que le joueur va se déplacer sur base des boutons ou touches enfoncées par celui qui est derrière son écran ou qu'un sprite se déplacera "en droite ligne" selon une vitesse et une direction donnée ;)
Nous pourrions donc parfaitement avoir une hiérarchie de comportement de mouvement proche de
La classe MovableEntity sera donc sans doute intelligemment modifiée enCode:
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 class MoveBehaviour /* c'est une classe de base qui n'hérite de rien d'autre ;) */ { public: virtual ~MoveBehaviour(){} /* on sait que l'on doit permettre de calculer la prochaine position */ virtual computeNextPosition(Coordinate const & ) = 0; /* et qu'on doit pouvoir appliquer le déplacement à l'objet */ void updatePosition(MovableEntity & entity) { entity.moveTo(nexPos_); } protected: void setNextPos(Coordinate const & toset){nextPos_=toset;} private: Coordinate nextPos_; }; /* par exemple, pour le mouvement des spries */ class SpriteMovingBehaviour : public MoveBehaviour { public: SpriteMovingBehaviour(int time, int speed, double angle): time_(time), speed_(speed), angle_(angle){} virtual void computeNextCoordinate(Coordinate const &ActualPosion) { Coordinate temp = /* calcule de la position suivante par rapport à AcutalPosition, * en fonction de la vitesse et de l'angle indiquant la direction ;) */ setNextPosition(temp); } }; /* d'autres classes héritées de MoveBehaviour, adaptées pour le joueur, * les ennemis et les éléments de décors mobiles ;) */
et ce sont les classes qui dérivent de MovableEntity qui fournissent le pointeur vers le comportement adapté:Code:
1
2
3
4
5
6
7
8
9
10
11
12 class MovableEntity { public: MovableEntity(MoveBehaviour * behav,int xpos, int ypos, bool visible =false):Entity(xpos, ypos, visible), move_(behav){} void computeNextPosition() { move_->computeNextPosition(Coordinate(xpos(),ypos()); } /* comme précédemment :D */ private: MoveBehaviour * move_; };
Il semble en effet "logique" de se dire que l'utilisation du comportement qui consiste à se déplacer soit effectuée par... l'entité qui est susceptible de se déplacer, et ce meme si le comportement de déplacement vient à être adapté par rapport au type réel de l'entité qui doit en profiter ;)Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 class Sprite : public MovableEntity { public: Sprite(int xpos, int ypos, bool visible =false):MovableEntity(new SpriteMovingBehaviour,xpos, ypos, visible){} }; class Ennemy : public MovableEntity { public: Ennemy(int xpos, int ypos, bool visible =false):MovableEntity(new EnnemyMovingBehaviour,xpos, ypos, visible){} }; class Gamer : public MovableEntity { public: Gamer(int xpos, int ypos, bool visible =false):MovableEntity(new GamerMovingBehaviour,xpos, ypos, visible){} };
Si besoin est, tu peux en outre parfaitement regrouper toutes les entités susceptibles de se déplacer dans une seule collection de manière à pouvoir provoquer le calcul de la prochaine position depuis un endroit unique, ce qui participera en outre au respect du sacrosaint principe de la responsabilité unique ;) :D
Maintenant, il y a sans doute d'autre comportements, et l'on peut parfaitement les envisager de manière similaires (bien qu'ils n'héritent très certainement pas de MoveBehaviour ;) )
As-tu des arguments à avancer qui s'appliquent au cas présent ou s'agit-il d'une doctrine religieuse ? :mrgreen:
Pardon, je te taquine mais ma question reste plus que pertinente.
Au passage, variable globale != membre statique. Pas de pollution d'espace des noms, initialisation différente, etc.
Si la classe en question est extrêmement simple et se contente d'exposer les services, je ne crois vraiment pas que ce soit un problème. D'autant que ton alternative actuelle entraîne un couplage plus fort : si l'on décide de changer la gestion des états du contrôleur, il faut modifier toutes les entités et comportements. Dans les solutions qui t'ont été proposée tu as au pire deux ou trois classes à modifier.Citation:
Utiliser une instance d'une classe contenant tout ce qu'il faut revient à faire une God Class et la passer partout dans les fonctions, c'est pas top non plus :(
Qui a la responsabilité de décider quelles entités doivent être créées ou détruites ? Si tu réponds à cela, tu réponds à ta question.Citation:
Autre chose, je dois gérer le Scroll (changement d'écran comme dans un zelda classique), pendez vous qu'il faut faire une classe à part et y mettre une référence dans ma classe World ? Ou alors directement gérer dans World (mais ce n'est pas son rôle je pense...) ...
Note qu'il te faudra aussi assigner à une classe la responsabilité de lire la map pour en déduire quelles entités s'y trouvent. Au vu du scrolling, ce seront deux responsabilités distinctes.
@koaka01 :
Je suis d'accord avec toi, pas de raison qu'une entité qui ne se déplace pas ait un comportement de déplacement. Le problème est qu'avec ta solution, tu as énormément de classes rien que pour gérer le déplacement avec 3 types de déplacements possibles (Gamer, Enemy et Sprite).
Imagine pour la gestion des collisions, comment vas tu gérer les collisions entre tous les types d'entités d'un jeu par exemple ?
On a plus ou moins la même vision de la chose, la seule différence est que je considère que toutes les entités peuvent se déplacer. De ce fait pas d'héritage du tout, que de la composition. J'ai modifié un peu mon code, maintenant j'utilise une structure pour définir une entité :
Et donc pour une entité qui n'a pas de déplacement, il suffit de lui coller un NoMoveBehaviour qui ne fera rien, certes je suis d'accord c'est pas très logique ^^ mais au moins pour ajouter un nouveau type de déplacement, il suffit de créer une classe qui dérive de l'interface IMoveBehaviour et de l'implémenter. Pour modifier un déplacement, il suffit de modifier la classe en question.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 struct Entity { Core::ISprite* pSprite; // pointeur sur son sprite IVect2D pos; // position courante USize2D size; // taille Type type; // type d'entité bool bVisible; // visibilite bool bAlive; // vivante ou pas // behaviours IMoveBehaviour* pMoveBehaviour; // pointeur sur son type de déplacement IHealthBehaviour* pHealthBehaviour; ICollisionBehaviour* pCollisionBehaviour; };
Donc je trouve que c'est facilement maintenable non ?
@DonQuiche :
Même si les membres statiques sont mieux que des variables globales, pour y accéder on a pas besoin d'objet donc elles sont accessibles partout ;)
Alors peut être que tu as raison, mettre les touches du clavier/joystick en static est peut être une solution, au moins je n'aurais pas à le trimbaler en arguments partout ^^ mais pour moi, je ne trouve pas que ca respecte les principes OO.
La je ne vois pas ce que tu veux dire, as tu un exemple d'une telle classe ?Citation:
Si la classe en question est extrêmement simple et se contente d'exposer les services, je ne crois vraiment pas que ce soit un problème. D'autant que ton alternative actuelle entraîne un couplage plus fort : si l'on décide de changer la gestion des états du contrôleur, il faut modifier toutes les entités et comportements. Dans les solutions qui t'ont été proposée tu as au pire deux ou trois classes à modifier.
Enfin, pour la responsabilité de Scroll, pour moi c'est le World qui est en charge de loader/detruire les entités de la map et de les "gérer" donc le Scroll devrait se trouver dans la classe World. :P
@Aspic
Plutôt que de directement mettre les états du contrôleur (clavier ou pad) en membre statique, tu peux aussi envisager d'exposer l'instance représentant les états du contrôleur via un membre statique. Par exemple tu pourrais avoir une classe de ce genre, avec des membres statiques, ou des membres d'instance si tu souhaites la passer en argument à chaque update() :
CStatesAndServices
* getPlayerEntity() // Nécessaire pour les ennemis qui veulent suivre Link
* getControlerState() // Nécessaire pour la mise à jour de Link
* getSoundMixer() // Nécessaire pour jouer un son en réaction à un coup d'épée
* getInventory() // Nécessaire si Link ramasse un coeur (collisions)
// J'ai utilisé des méthodes plutôt que des champs mais il y a peu de chances que l'on en ait vraiment besoin.
Tu pourrais aussi la découper en plusieurs morceaux si tu restes sur l'idée d'une instance à passer à chaque appel de update() : avoir un UpdateArgs, CollisionArgs, etc... Ou mixer le tout pour avoir les services exposés par des membres statiques et les états par des *Args.
Maintenant, si tu préfères la méthode statique, tu peux aussi fourrer tout ça dans World (ou Game) en sachant que ça n'accroît le couplage que sur le papier puisque cette partie-ci de World changerait rarement. Oui, ça va à l'encontre du SRP mais le SRP est une aide à la conduite, pas un dogme religieux. Parfois on peut s'en affranchir si ça ne crée pas de problème. Et si tu es certain que World (ou Game) existera toujours et que ce sera toujours lui qui contiendra ces services et états, ça ne crée pas de problème.
Tout ceci étant dit, deux remarques :
* Si tu souhaites mettre en place des tests unitaires, la version à base de UpdateArgs et CollisionArgs est nettement plus sympathique puisqu'on peut l'adapter aisément pour se donner la possibilité de créer des mocks. Mais la même chose peut être faîte avec les membres statiques en prévoyant des setters.
* Inversement, l'avantage des membres statiques est que, si jamais tu as besoin à un moment d'un état que jusqu'à présent tu ne relayais pas jusqu'à un niveau si profond dans la pile des appels, tu vas devoir modifier tous tes callers pour ce faire. Mais les *Args compensent cette faiblesse, rendant les modifications à faire moins nombreuses.
Au final, c'est aussi une question d'esthétisme. Tu sembles par exemple affecter un certain purisme, peut-être parce que tu as ainsi l'impression de ne pas pouvoir faire d'erreurs. Dans tous les cas, toutes ces solutions sont meilleures que ta solution actuelle, laide et peu maintenable.
Et alors :question: le mouvement et la manière dont il est géré n'a, tout simplement rien à voir avec le reste :D
Tu peux très bien considérer le fait que seules les entités qui bougent (MovableEntity dans mon exemple) sont susceptibles d'avoir un déplacement, dont le mode est adapé à leur type réel (ennemy, sprite, gamer, que sais-je :question:)
"Tout ce qu'il faut", c'est, effectivement, que le comportement de déplacement soit délégué à une classe particulière ;)
Bien que tu aies deux type d'entités différents (celles qui bougent et celles qui ne bougent pas), tu n'as que deux cas de collisions : une entité qui bouge avec une entité qui ne bouge pas, et une entité qui bouge avec une entité qui bouge (tu ne verra jamais deux entités qui ne bougent pas entrer en collision :D )Citation:
Imagine pour la gestion des collisions, comment vas tu gérer les collisions entre tous les types d'entités d'un jeu par exemple ?
Par contre, tu devras de toutes manière mettre en place une certaine "priorité de réaction" en cas de collision, dépendant peut etre du type réel de l'entité, voir de l'entité qui est "active" par rapport à celle qui est "passive" dans la collision ;)
Tu donnes, de nouveau, trop de responsabilité à une entité, lorsque tu ne sais pas exactement de quel type elle est !!!Citation:
On a plus ou moins la même vision de la chose, la seule différence est que je considère que toutes les entités peuvent se déplacer. De ce fait pas d'héritage du tout, que de la composition. J'ai modifié un peu mon code, maintenant j'utilise une structure pour définir une entité :
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 struct Entity { Core::ISprite* pSprite; // pointeur sur son sprite IVect2D pos; // position courante USize2D size; // taille Type type; // type d'entité bool bVisible; // visibilite bool bAlive; // vivante ou pas // behaviours IMoveBehaviour* pMoveBehaviour; // pointeur sur son type de déplacement IHealthBehaviour* pHealthBehaviour; ICollisionBehaviour* pCollisionBehaviour; };
Tant que tu ne parles que d'une entité, sans précision, elle ne doit avoir qu'une seule responsabilité (par exemple, celle de fournir les informations permettant de la placer sur ta carte, et/ou celle de s'afficher ;) )
Ce n'est qu'une fois que tu spécialise ta classe (en MovableEntity ou NonMovableEntity) que tu peux déterminer que l'une est susceptible de se déplacer et l'autre pas...
Il ne servira donc à rien d'avoir un "NoMoveBehaviour" ;)
Ton "IMoveBehaviour" sera, effectivement, spécialisé en fonction du type réel de l'entité (qui peut se déplacer), mais c'est ce que nous faisons tous les deux, si ce n'est que je sais d'office qu'il y a tout un pan de la hiérarchie "Entity" qui n'en aura pas besoin ;)
Comme tu le dis, ce n'est pas logique :DCitation:
Et donc pour une entité qui n'a pas de déplacement, il suffit de lui coller un NoMoveBehaviour qui ne fera rien, certes je suis d'accord c'est pas très logique ^^
Dés lors, pourquoi le faire :question:
Si tu fais la distinction entre les entités qui peuvent bouger et celles qui ne peuvent pas bouger, tu as la certitude que seules les première devront disposer d'un comportement de déplacement adapté :D
Mais, tu remarqueras que c'est ce que je fais :DCitation:
mais au moins pour ajouter un nouveau type de déplacement, il suffit de créer une classe qui dérive de l'interface IMoveBehaviour et de l'implémenter. Pour modifier un déplacement, il suffit de modifier la classe en question.
La seule différence, c'est que je m'assure qu'un entité ne devant pas se déplacer n'aura en aucun cas besoin d'un comportement de déplacement ;)
Mais, réfléchis un peu!!!!Citation:
@DonQuiche :
Même si les membres statiques sont mieux que des variables globales, pour y accéder on a pas besoin d'objet donc elles sont accessibles partout ;)
Alors peut être que tu as raison, mettre les touches du clavier/joystick en static est peut être une solution, au moins je n'aurais pas à le trimbaler en arguments partout ^^ mais pour moi, je ne trouve pas que ca respecte les principes OO.
Où vas tu avoir besoin du clavier et du joystick :question:
Exclusivement au niveau du gestionnaire d'événement du joueur ;)
De là à dire qu'il peut, d'une manière ou d'une autre, n'exister qu'au travers de l'existence du joueur (ou de son système de gestion d'événements), il n'y a qu'un pas allègrement franchi :D
C'est d'autant plus vrai que tu n'en as besoin qu'au moment où tu vas créer l'événement (l'appui sur une touche ou le basculement du joystick dans une direction va provoquer la création d'un événement qui sera "propagé" là où nécessaire ;) )
Des arguments, il y en a à la pelle :D
Problème de réentrance, de gestion de thread, de "pureté" des fonctions...
Tu devrais t'intéresser à cette discussion, essentiellement à partir de l'intervention numéro 5, pour éviter d'avoir à tout réécrire ;)
Une "GodClass" ne sera jamais simple!!!Citation:
Si la classe en question est extrêmement simple et se contente d'exposer les services, je ne crois vraiment pas que ce soit un problème. D'autant que ton alternative actuelle entraîne un couplage plus fort : si l'on décide de changer la gestion des états du contrôleur, il faut modifier toutes les entités et comportements. Dans les solutions qui t'ont été proposée tu as au pire deux ou trois classes à modifier.
Il est bien plus facile et maintenable de garder des petites classes ayant des responsabilités limitées qu'une seule classes dont on s'attendrait presque à ce qu'elle nous fasse le café, tant elle a de responsabilités ;)
Ça commence mal...:roll:
* Sur les états du contrôleur :
Tu pars du principe qu'il va utiliser ton système à base d'événements, ce qui n'est pas dit. Depuis le début je m'efforce de rester à un niveau général quand toi tu développes *ton* implémentation.
* Sur les variables statiques
** Si une instance mutable est partagée par plusieurs threads, elle ne pose ni plus ni moins de problème que des membres statiques.
** Code réentrant : voir ci-dessus.
** Pureté des fonctions : voir ci-dessus.
Maintenant si tu prends la peine de regarder ce qui serait partagé, que vois-tu ? Soit des données modifiées une fois et lues par le reste du code (état du contrôleur), soit des instances mutables qui seront partagées (entité représentant Link, service sonore, etc).
* Sur la GodClass jamais simple :
Nous ne parlions pas d'une classe regroupant des pelletées de responsabilités. Nous parlions d'une classe ne fournissant aucune service et se contentant d'exposer quelques singletons (pas le pattern, l'unicité) en lecture seule (initialisés au démarrage).
Exact vu comme ca ;)
Je ne comprends pas très bien.
Dans le cas de ton implémentation, comment vas tu gérer les collisions de tes deux types d'entités (Movable et NonMovable) ?
Si tu créés un ICollisionBehaviour et que tu l'associes aux classes MoveEntity et NoMoveEntity, comment vas tu gérer les collisions au cas par cas, c'est à dire :
- Enemy (Movable) VS Player (Movable) => faudra déclencher un "Hit" sur le Player
- Enemy (Movable) VS Hole (NoMovable) => Enemy va changer de direction pour éviter le trou (d'ailleurs le déplacement des ennemies est aléatoire dans Zelda :D pas d'algo de type A* ou autre)
- Player (Movable) VS Door (NoMovable) => Déclencher une téléportation vers une autre zone de la map
En fait ton implémentation me plait bien :ccool:, j'ai juste peur d'être coincé au niveau des collisions entre toutes les entités :(
Dernière chose, tu as des entités qui ont un Sprite qui s'anime quand il se déplace (Enemy et Player), d'autres entités qui s'animent indéfiniment (des bougies, des éléments du décor pour rendre la map "vivante"), et d'autre qui sont fixes (une pancarte, une maison...) et d'autres qui s'animent lors d'un événement (ouverture d'une porte par exemple, apparition d'un objet...).
Comment vas tu gérer ca ? Tu ne vas quand même pas créer des nouveaux types d'entités qui dérivent de Entity ? :D
PS : Je crois que le "Ah mais réfléchie un peu !!!" était destiné à moi même :mrgreen:
Merci :)
EDIT :
En essayant d'implémenter la solution de koala01, je me suis rendu compte qu'il y a un petit problème :
La fonction suivante dans la classe MovableEntity ne pourra jamais être appelée car le World manipule un vector de Entity, Or cette fonction est spécialisée à un MovableEntity donc je ne pourrais pas l'appeler à partir de World... (ou alors comme d'hab, j'ai pas compris :aie:)Code:
1
2
3
4 void computeNextPosition() { move_->computeNextPosition(Coordinate(xpos(),ypos()); }
En clair:
De même dans ce design, qui est en charge de vérifier si le mouvement est valide ou pas ?Code:
1
2
3
4
5
6
7
8 // je crée un player en position (100,100) Entity* player = new Gamer(100, 100); //Je l'ajoute aux entités du monde world->addEntity(player); // et maintenant comment le World va t-il appeler la fonction spécialisée // computeNextPosition() de MovableEntity ?
Le mais réfléchis un peu était adressé à aspic, en fait :aie:
Je pars surtout du principe qu'il faut absolument faire en sorte de déléguer les responsabilités...Citation:
* Sur les états du contrôleur :
Tu pars du principe qu'il va utiliser ton système à base d'événements, ce qui n'est pas dit. Depuis le début je m'efforce de rester à un niveau général quand toi tu développes *ton* implémentation.
Il faut, bien sur, prendre les inputs de l'utilisateur en compte, et, le fait que l'utilisateur décide d'appuyer sur la barre d'espace, sur o ou sur la fleche montante est, quoi que tu en dise, un événement ;)
La seule inconnue, c'est la réaction que le système devra avoir face à cet événement ;)
Comme on travaille sur une application qui suit une "ligne de temps" ponctuée par des événements, il est logique que l'on se base sur ces événements pour mettre un système de réaction au point ;)
Maintenant, la hiérarchie "événement" peut clairement prendre un grand nombre de cas en compte tels que inputs utilisateur, collision entre entités, arrivée à un point de scrolling, décision "hors du jeu" de la part de l'utilisateur, timer écoulé ou que sais-je ;)
Chaque événement contient son propre contexte et seules les classes susceptibles à un type d'événement particulier en tiennent compte ;)
Ce n'est pas *mon* implémentation (je n'ai d'ailleurs quasiment pas donné de code en ce qui concerne les événement :D ), c'est juste le moyen le moins mauvais de faire en sorte que tout le monde communique de manière correcte et coordonnée ;)
Hé bien, il y a cependant une différence flagrante :Citation:
* Sur les variables statiques
** Si une instance mutable est partagée par plusieurs threads, elle ne pose ni plus ni moins de problème que des membres statiques.
** Code réentrant : voir ci-dessus.
** Pureté des fonctions : voir ci-dessus.
Un membre (ou une instance) mutable ne va agir que sur elle-même ou sur l'objet dont il est membre, alors qu'une variable statique (ou une variable globale) va agir sur toutes les instances qui la manipulent:
si tu as une classe Machin (disons : Ennemy pour rester dans le cadre de la discussion :D ) qui dispose d'un membre statique brol, et que tu as deux deux instances de Ennemy (et tu risques vraiment d'en avoir encore plus :D ), mettons E1 et E2, la modification que tu pourras effectuer sur E1.brol se répercutera immanquablement sur E2.brol, alors que cela ne doit, a priori, pas arriver ;)
Si brol est, non plus statique, mais mutable, et que tu as deux threads qui utilsent E1, (et deux autres qui utilisent E2), "tout ce qu'il convient de faire", c'est de s'assurer qu'un des thread (manipulant E1 ou E2) prendra le pas sur l'autre, mais les modifications apportées à E1.brol n'influeront que... E1 et "foutra la paix" à E2 ;)
Ce que tu sembles oublier, c'est que ton service sonore peut parfaitement être totalement indépendant de tout le reste!!!Citation:
Maintenant si tu prends la peine de regarder ce qui serait partagé, que vois-tu ? Soit des données modifiées une fois et lues par le reste du code (état du contrôleur), soit des instances mutables qui seront partagées (entité représentant Link, service sonore, etc).
Tout ce qu'il faut, c'est un moyen de lui faire savoir quels sons jouer et dans quel ordre...
Encore une fois, un système d'événements semble être la meilleure des solutions pour y parvenir: il reçoit d'office tous les événements et, en fonction de ceux-ci, détermine quel son doit etre joué.
Il place alors les sons dans une queue d'attente, et joue les sons "un à un" ;)
Tout ce qu'il faut, encore une fois, c'est que chaque événement se fasse connaitre du service sonore au moment de sa création ;) (et que le service sonore soit en mesure de mettre les événements en attente de gestion dans une file d'attente ;))
Et tu crois que le fait de fournir une pelleté d'instances uniques ne va pas occasionner une pelletée de responsabilité, sans doute :question:Citation:
* Sur la GodClass jamais simple :
Nous ne parlions pas d'une classe regroupant des pelletées de responsabilités. Nous parlions d'une classe ne fournissant aucune service et se contentant d'exposer quelques singletons (pas le pattern, l'unicité) en lecture seule (initialisés au démarrage).
Si tu délègue correctement les responsabilités, il est tout à fait possible que chaque "module" du programme puisse fonctionner de manière indépendante en n'étant, en gros, piloté qu'en fonction des événements qui surviennent...
Il n'y a aucune raison que le système de sons soit connu (ou connaisse) le système de détection de collisions, le système d'affichage ou le système de gestion des ennemis.
Le seul système plus ou moins central qu'il faut avoir est... le système de génération d'événements, et encore, la génération d'événement peut parfaitement etre décentralisée en fonction du type d'événement à générer, car, de toutes façons, l'événement est destiné à être récupéré par... le moteur de jeu de manière à etre transmis à "qui de droit" ;)
Le cas de la collision entre une entité Mobile et une entité immobile est le cas le plus simple à vrai dire : l'entité active (celle qui entre en collision avec l'autre :D ) est l'entité mobile, alors que l'entité passive (celle qui subit la collision) est l'entité immobile :D
Les choses deviennent certainement plus compliquées au niveau des collisions entre entités mobile :D
Le fait est qu'une collision provoque un système de réaction de la part des deux intervenants...Citation:
Si tu créés un ICollisionBehaviour et que tu l'associes aux classes MoveEntity et NoMoveEntity, comment vas tu gérer les collisions au cas par cas, c'est à dire :
- Enemy (Movable) VS Player (Movable) => faudra déclencher un "Hit" sur le Player
- Enemy (Movable) VS Hole (NoMovable) => Enemy va changer de direction pour éviter le trou (d'ailleurs le déplacement des ennemies est aléatoire dans Zelda :D pas d'algo de type A* ou autre)
- Player (Movable) VS Door (NoMovable) => Déclencher une téléportation vers une autre zone de la map
En fait ton implémentation me plait bien :ccool:, j'ai juste peur d'être coincé au niveau des collisions entre toutes les entités :(
L'entité qui entre en collision avec une autre va réagir à sa manière d'un coté et celle qui subit la collision va réagir à sa manière de l'autre ;)
Le tout est de savoir qui va prendre la priorité dans l'ordre de la réaction ;)
Mais, tu prend l'exemple d'une porte, sa réaction en cas de collision sera de forcer l'entité qui est entrée en collision avec elle (si elle existe encore après la collision :D ) à se déplacer instantanément vers une autre position ;)
En fait, je crois que l'on ne se comprend pas bien sur ce qui pourrait etre un sprite :aie:Citation:
Dernière chose, tu as des entités qui ont un Sprite qui s'anime quand il se déplace (Enemy et Player), d'autres entités qui s'animent indéfiniment (des bougies, des éléments du décor pour rendre la map "vivante"), et d'autre qui sont fixes (une pancarte, une maison...) et d'autres qui s'animent lors d'un événement (ouverture d'une porte par exemple, apparition d'un objet...).
Comment vas tu gérer ca ? Tu ne vas quand même pas créer des nouveaux types d'entités qui dérivent de Entity ? :D
Pour moi, un sprite est, simplement, une entité qui a sa vie propre mais qui n'est ni un élément du décors, ni un objet d'inventaire, ni un ennemi, ni le joueur...
Un sort lancé par un ennemi ou un joueur ou un objet n'attendant qu'à etre ramassé pourraient, par exemple, etre des sprite ;)
Le fait qu'une bougie, par exemple, puisse utiliser plusieurs images pour illustrer le mouvement de sa flamme, je veut pas forcément dire que la flamme doive être un sprite : la bougie est, simplement, composées de plusieurs images utilisées dans un ordre donné ;)
Le fait qu'une porte change d'aspect lorsqu'on l'ouvre n'implique absolument pas que l'on ait besoin d'un sprite pour y arriver : on a simplement deux états (ouverte et fermée) qui correspondent à deux représentations graphiques différentes ;)
Tu auras d'ailleurs remarqué que la classe Sprite que je fournis hérite, tout simplement, de MovableEntity, et dispose d'informations susceptible de lui permettre d'avoir sa vie propre (vitesse et direction ;) )
Ce qu'il peut manquer, éventuellement, c'est une information sur le son à jouer à sa création, à sa destruction et durant sa durée de vie ;)
Il ne s'agirait, en effet, pas de créer une classe sprite par image que tu veux afficher pour celui-ci, il "suffirait" que le sprite dispose de l'ensemble des images qu'il doit faire afficher les unes après les autres ;)
Par contre, si cela a une utilité quelconque, nous pourrions envisager de spécialiser la classe Sprite en différentes catégories telles que "armes", "aliments" (ou nous trouverions des pommes, des potions ou des bout de gigot :D ) et "sorts" ;)
Je ne dis pas que cela doit se faire, hein, je dis juste que, si cela présente un intérêt quelconque, il peut etre utile de l'envisager ;)
@koala01
Le fait que la remarque était adressée à Aspic ne la rend pas moins grossière.
*Sur les états du contrôleur:
Laisse-moi résumer : dans ton premier post tu me faisais une remarque qui ne s'appliquait qu'au cas où l'état du joueur serait mis à jour par les événements du contrôleur. Après que j'ai pointé cette restrictions, tu m'expliques donc que, oui, mais bon, de toute façon il faut faire comme ça et que c'est beaucoup plus naturel ainsi. D'abord, ça me fait un peu penser à l'expression "noyer le poisson".
Maintenant, je reviens tout de même un instant sur cette conception. Quand vas-tu devoir changer l'état de Link ? A chaque collision. A chaque pression des touches du contrôleur. A chaque fois qu'une animation bloquante se termine. Etc, etc, etc... Et à chaque fois tu vas devoir modifier plusieurs états en conséquence : l'animation, le mode d'interaction avec le monde (pendant l'anim qui suit un coup le perso devient invincible), avec le contrôleur (certaines animations ne peuvent pas être interrompues), etc. Qu'est-ce qui te semble le plus simple à écrire, lire et maintenir ? D'exploser cette gestion d'états conflictuels sur cinq ou six événements avec du code redondant (vérifier que les états de plus hautes priorités autorisent l'exécution) et dont on ne sait pas dans quel ordre ils s'exécutent, ou bien d'écrire une procédure unifiée appelé une seule fois par frame ?
Toujours sur cette conception, il ne faudrait pas confondre les choses : on peut très bien avoir un scheduler basé sur des événements pour ordonner par exemple "exécute tel code dans x millisecondes", ou un système événementiel à un plus haut niveau (UI). Mais il n'y a pas besoin pour autant que le coeur soit basé sur des événements. Personnellement, de mon expérience, ce n'est pas l'architecture la plus aisée pour le coeur du jeu et il est plus simple de mettre chaque entité à jour à chaque frame.
*Sur les membres statiques:
Le fait que E1 et E2 soient tous deux affectés par le changement de la variable statique n'est pas un problème, c'est le but recherché par la déclaration en statique (du moins dans le problème que tu exposais, pas dans ce que je décrivais à Aspic : il n'y a dans ce cas aucun problème de ce genre).
J'insiste : il n'y a aucune différence entre une variable statique et une instance mutable partagée du point de vue de tous les problèmes que tu as énoncés et il serait honnête de le reconnaître. Les variables statiques ne sont pas à prohiber comme tu le pensais, voilà tout.
Concernant le système sonore, il est bien mutable, point barre. Accessoirement, non, tu vas pas mettre en file les sons. Un son peut soit en écraser un autre, soit se superposer à ceux en cours mais il ne se met pas en file.
*Sur le regroupement des services:
Non, qu'une classe contienne une référence vers une instance ne lui donne pas les responsabilités de cette instance. Et il n'a jamais été question que ce système soit fait pour permettre au gestionnaire de collisions d'accéder au système sonore ou quoi que ce soit de ce genre. Je t'invite à relire les échanges successifs.
Elle était peut etre un peu rude, mais n'était pas grossière, ou, du moins, ne voulait pas l'être ;)
Il n'en demeure pas moins qu'il est beaucoup plus cohérent de faire en sorte que tout ce qui correspond à un input issu du joueur soit "centralisé" en un point (ou dans un module particulier), que ce module se charge de traiter l'information ainsi reçue et de transmettre l'information partout, sans qu'il soit besoin, hors du module concerné, de de voir s'intéresser à la touche qui a effectivement été enfoncée.
Tu n'as donc absolument aucun besoin d'avoir un accès global aux inputs issus du joueur car à la réception de chaque input ne doit correspondre qu'un "signal" donné, qui pourra etre compris comme tel par l'ensemble des "modules" susceptibles d'y réagir...
Cela permettra en outre, par exemple, au joueur de modifier la touche qui a pour effet de te faire avancer ou d'aller à droite: il n'y a qu'une table de correspondance à mettre à jour entre la touche enfoncée et le "signal" envoyé, et il n'y a meme plus besoin d'avoir accès à la configuration actuelle pour déterminer si le joueur voulait avancer, reculer, sauter ou aller à droite ;)
Tu as très mal interprété ce que j'ai dit...Citation:
*Sur les états du contrôleur:
Laisse-moi résumer : dans ton premier post tu me faisais une remarque qui ne s'appliquait qu'au cas où l'état du joueur serait mis à jour par les événements du contrôleur. Après que j'ai pointé cette restrictions, tu m'expliques donc que, oui, mais bon, de toute façon il faut faire comme ça et que c'est beaucoup plus naturel ainsi. D'abord, ça me fait un peu penser à l'expression "noyer le poisson".
Je te fais un copier coller de ma première intervention pour ce qui concerne le joueur:Autrement dit :Citation:
Envoyé par moi meme
Par la suite, j'ai effectivement introduit la notion de système d'événements, parce que la grosse majorité des réactions se fera sur un dénominateur commun qui est : la survenue d'un événement.
- Par moment, on pourra parfaitement se contenter de considérer le joueur comme étant une entité "quelconque"
- A d'autre moments, il faudra considérer le joueur comme étant du type d'entité particulier :joueur, et le fait que joueur hérite (de manière indirecte) de Entité n'interviendra sans doute pas dans la gestion qui en sera faite, car nous ne nous intéresseront exclusivement à ce qui fait que notre entité est un joueur
- Parmi tout ce dont le "module" qui s'occupe de la gestion du joueur aura à s'occuper, on trouvera, entre autres, une partie qui s'occupera de gérer les inputs issus du joueurs et de générer les événements qui vont bien en fonction de ces inputs.
Je ne parle pas ici uniquement des événements issus de l'appui sur une touche par l'utilisateur!!!
Je dis que, lorsque deux entités entre en collision, qu' un ennemi décide de frapper quelque chose ou de lancer un sort, qu'un sort touche quelque chose, ou que sais-je, nous observons la survenue d'un événement, et que c'est ce qui jalonne littéralement la ligne de temps de l'application.
Je dis, enfin, que, dans la liste de tous les événements susceptibles de survenir, il y aura, entre autres provoqués par l'appuis d'une touche de la part de l'utilisateur ;)
Tu aurais donc une classe de base Event, qui correspond à un événement dont "on ne sait rien", qui sera spécialisé en "CollisionEvent", en "NpcEvent", en "GamerEvent" ou en que sais-je, pour pouvoir déterminer l'origine des événements et qui seront eux meme spécialisées en d'autres événements plus spécifiques de manière à provoquer la réaction appropriées dans les autres modules.
Le tout en sachant qu'il est tout à fait possible de faire en sorte qu'un module particulier ignore carrément un type particulier d'événement, parce que sans objet pour le module en question (par exemple, le système qui s'occupe de gérer le joueur n'aura sans doute que faire des événements provoqués par les ennemis, mais devra gérer un grand nombre des événements susceptibles de survenir en cas de collision)
Je suis désolé si cela contredit ton approche, mais il me semble que cela correspond à l'approche "logique" dés le moment où l'on envisage de séparer clairement les responsabilités en créant, au final une série de modules indépendants les un des autres : chaque module se charge de créer les événements qui lui sont propres pour qu'ils soient récupérés par le moteur de jeu et "dispatchés" vers les modules susceptibles d'y réagir ;)
Tu as bien utilisé le terme : état...Citation:
Maintenant, je reviens tout de même un instant sur cette conception. Quand vas-tu devoir changer l'état de Link ? A chaque collision. A chaque pression des touches du contrôleur. A chaque fois qu'une animation bloquante se termine. Etc, etc, etc... Et à chaque fois tu vas devoir modifier plusieurs états en conséquence : l'animation, le mode d'interaction avec le monde (pendant l'anim qui suit un coup le perso devient invincible), avec le contrôleur (certaines animations ne peuvent pas être interrompues), etc.
Beaucoup de choses agiront au final comme des "mini machines à états"...
Pour chaque état donné, il y aura une série d'événement autorisés ou non...
En Créant une hiérarchie d'événement cohérente et correcte, il te sera parfaitement possible de faire en sorte que la grosse majorité des événements refusés par un état donné soit purement et simplement envoyés vers une fonction "qui ne fait rien" pour n'avoir, au final que quelques événements susceptibles de provoquer une transition vers un nombre limité d'états suivants
en gérant correctement tes états et tes événements, tu n'auras meme pas à t'en inquiéter : chaque état ne pouvant, de toutes manières, permettre la transition vers un nombre limité d'états suivants, cela se fera de manière systématique, et sans qu'il ne soit besoin pour la cause d'avoir du code redondant ;)Citation:
Qu'est-ce qui te semble le plus simple à écrire, lire et maintenir ? D'exploser cette gestion d'états conflictuels sur cinq ou six événements avec du code redondant (vérifier que les états de plus hautes priorités autorisent l'exécution) et dont on ne sait pas dans quel ordre ils s'exécutent,
Elle ne sera peut etre pas appelée une seule fois par frame, mais la procédure restera unifiée malgré tout...Citation:
ou bien d'écrire une procédure unifiée appelé une seule fois par frame ?
Si tu te place au niveau du game engine(qui, rappelons le, récupère tous les événements générés par les différents modules :D ) cela revient à faire
avec la fonction transmitEvent qui ressemble àCode:
1
2
3 transmitEvent( event) update() draww()
la fonction update() qui ressemble àCode:
1
2
3
4
5
6
7
8 void GameEngine::transmitEvent(Event * event) { ennemiesManager_.setNextEvent(event); gamerManager_.setNextEvent(event); entitiesManager_.setNextEvent(event); /* ... * tout vers quoi il faut envoyer l'événement ;) */ }
et la fonction draw() qui ressemble àCode:
1
2
3
4
5
6
7
8 void GameEngine::update() { ennemiesManager_.update(); gamerManager_.update(); entitiesManager_.update(); /* ... * tout ce qui doit etre mis à jour ;) */ }
Code:
1
2
3
4 void GameEngine::draw() { entitiesManager_.draw(); }
Pourquoi pas, étant donné que tout est susceptible de provoquer survenue ou de réagir à la survenue d'un événement, et que c'est quand meme ce qui jalonne la ligne de temps de ton application :question:Citation:
Toujours sur cette conception, il ne faudrait pas confondre les choses : on peut très bien avoir un scheduler basé sur des événements pour ordonner par exemple "exécute tel code dans x millisecondes", ou un système événementiel à un plus haut niveau (UI). Mais il n'y a pas besoin pour autant que le coeur soit basé sur des événements.
Et tu mets ton entité à jour de quelle manière :question: sur base de l'air du temps :question: sur base d'un comportement qui n'a rien à faire de ce qui a pu se passer à coté d'elle :question: ou sur base des événements qui sont survenus entre la frame précédente et celle que l'on va devoir afficher (et qui ont eu un impact sur elle ou sur sa réaction ) :question:Citation:
Personnellement, de mon expérience, ce n'est pas l'architecture la plus aisée pour le coeur du jeu et il est plus simple de mettre chaque entité à jour à chaque frame.
C'est, justement, le problème...Citation:
*Sur les membres statiques:
Le fait que E1 et E2 soient tous deux affectés par le changement de la variable statique n'est pas un problème, c'est le but recherché par la déclaration en statique (du moins dans le problème que tu exposais, pas dans ce que je décrivais à Aspic : il n'y a dans ce cas aucun problème de ce genre).
Tu ne peux pas garantir que, dans un contexte identique, l'appel d'une fonction en passant en paramètre E1 ou E2 aura un comportement identique si un membre est partagé par les deux objets (et risque d'être modifié "par ailleurs"!!!
Que tu déclares une constante statique passe encore, vu que, par définition, sa valeur ne changera pas!!!
Mais si tu utilises une variable statique, tu n'a aucun contrôle sur la valeur qu'elle pourra avoir à un instant T pour un objet X, vu que tous les objets de même types sont susceptibles d'y avoir chipoté.
Les constantes statiques ne sont pas à prohiber!!!Citation:
J'insiste : il n'y a aucune différence entre une variable statique et une instance mutable partagée du point de vue de tous les problèmes que tu as énoncés et il serait honnête de le reconnaître. Les variables statiques ne sont pas à prohiber comme tu le pensais, voilà tout.
Les variables statiques sont le meilleur moyen de "foutre le bordel" en te retrouvant avec un objet dont le comportement est incohérent du seul fait que tu perds tout contrôle de la valeur de la variable!!!
Il n'a meme pas à être mutable : il a, purement et simplement, à etre indépendant et à être "piloté" (par envois d'événement) par le Game Engine;)Citation:
Concernant le système sonore, il est bien mutable, point barre.
Tu n'as aucun besoin d'y faire appel depuis ailleurs que... depuis le game engine ;)
ca, je suis d'accord, j'aurais pu etre plus explicte quant à mon explication ;)Citation:
Accessoirement, non, tu vas pas mettre en file les sons. Un son peut soit en écraser un autre, soit se superposer à ceux en cours mais il ne se met pas en file.
Le problème est que, si cette instance se balade partout, elle prend, fatalement, la responsabilité de fournir l'accès aux différents gestionnaires, et que, de ce seul fait, elle fournira n'importe où l'accès à n'importe quel gestionnaire!!!Citation:
*Sur le regroupement des services:
Non, qu'une classe contienne une référence vers une instance ne lui donne pas les responsabilités de cette instance. Et il n'a jamais été question que ce système soit fait pour permettre au gestionnaire de collisions d'accéder au système sonore ou quoi que ce soit de ce genre. Je t'invite à relire les échanges successifs.
De là à se retrouver dans une situation dans laquelle le module qui s'occupe de gérer les collisions fasse directement appel au système de gestion des sons, il n'y a qu'un pas qui sera bien trop allègrement franchi uniquement parce que "il est possible de le faire"!!!
Pourquoi diable faudrait il passer une instance qui donne accès "à tout" alors qu'on n'a de toutes manières besoin que d'une infime partie de ce à quoi cette instance donne accès :question:
En tout cas moi je ne comprends plus rien du tout :aie:
Tout se disperse et au final tout m'embrouille.
Je vois un ennemiesManager mais c'est quoi ? D'un coté le World met à jour toutes les entités mais il y a quand même un Manager par type d'entité, je suis paumé sans compter des événements à transmettre à tout le monde :calim2: 8O
Je pense qu'un exemple complet serait le bienvenue ;)
Bon, reprenons...
Je te conseille, depuis le départ, de déléguer au maximum les responsabilités en créant ce que j'appelais au départ des "systèmes de gestion", mais dont le terme correct est en fait beaucoup plus proche de "module".
Tu créerais donc une série de modules gérant le "cycle de vie" des différents types d'entités en les considérant comme tels.
Cela te donne (cf le code de cette réponse) des modules tels que:Chaque module tournant, entre autres, autour d'une classe dont la responsabilité est de garder une trace de toutes les entités du type concernées par le module qui sont créées, entre le moment de leur création et celui de leur destruction.
- un module de gestion des sprites
- un module de gestion des ennemis
- un module de gestion des éléments de décors
- un module de gestion des objets d'inventaire
- ...
Tous ces modules sont "mis en relation" entre eux grâce au game engine qui dispose, d'une (et une seule) instance de cette fameuse classe dont la responsabilité est la gestion des entités d'un type particulier: ce que j'ai nommé (dans ma dernière intervention, mais aussi dans le message auquel j'ai déjà fait référence plus haut :D ) sous les noms de ennemiesManager_ , gamerManager_ , ou encore entitiesManager_ .
Les gros avantages de travailler de la sorte sont:
- De nous permettre, pour chaque entité existante, de disposer d'un moyen de l'utiliser en fonction de son type réel
- De nous permettre de gérer "à la chaine" l'ensemble des entités d'un type particulier ( comprend : dont le type réel correspond au type d'entité dont un module particulier a la charge)
- De permettre de travailler sur un type d'entités particulier sans "s'inquiéter du reste"
Parallèlement à cela, il faut mettre en place un système permettant aux différents modules de réagir les uns par rapport aux autres...
Le meilleur moyen d'y arriver est de travailler sur les événements qui peuvent survenir: Chaque module va générer un nombre "restreint" d'événement en fonction du type d'entité dont il a la responsabilité.
Chaque fois qu'un événement sera généré par un module particulier, il devra être "récupéré" par le game engine de manière à être transmis aux autres modules afin de leur donner l'occasion d'y réagir, ou non, selon l'intérêt que l'événement peut avoir pour un module donné.
Le monde quant à lui interviendra dans un module qui connait l'ensemble des entités existantes, quel que soit leur type réel, mais qui ne peut donc les manipuler que... en tant que Entity et qui réagira essentiellement aux événement susceptibles de provoquer le "scrolling" vers une position donnée de la carte ;)
En restant dans les modules ou types particuliers, nous avons également le sound player, qui n'a que faire des entités, mais qui réagit exclusivement aux événements (potentiellement à tous les événements), bien qu'ayant (si besoin) également sa propre vie propre (ex: pour la musique de fond), ou le render qui aura pour responsabilité d'afficher l'ensemble des entités considérées par le monde comme étant à afficher ;)
Si je trouve un peu de courage pour le faire, je te placerai (peut etre) un diagramme de classe et (qui sait :D ) un diagramme de communication pour te rendre les choses un peu moins abstraites ;)
Oui je crois comprendre ce que tu veux faire mais encore une fois, je n'arrive pas à voir comment les événements vont être transmis par le moteur de jeu aux différents "manager".
Oui je pense qu'un diagramme de classe serait cool :ccool:
Un diagramme de communication, je ne sais pas ce que c'est je connais le diagramme de séquence mais ca doit être assez proche ;)
Je reviens sur les sprites, tu as dis que pour toi un sprite c'était par exemple un coeur a ramasser. Or après tu dis que ta classe Sprite dérive de MovableEntity. Or un coeur ne se déplace pas...
Donc je vois mal ce que tu appelles "Sprites" car pour moi, un sprite est simplement une image qui peut s'animer (plusieurs images) ou pas (une seule image). Une entité est alors composé d'un Sprite, ainsi que d'autres attributs comme la position, la taille, la visibilité...
Le moteur de jeu est, finalement, le seul point où l'ensemble des modules utilisés est connu, et donc d'où il sera possible d'accéder au manager propre à chaque module.
Or, la plupart des modules vont générer une série d'événements liés aux entités dont ils ont la charge:
Le module de détection de collisions, par exemple, pourrait générer un événement (dérivé du) type CollisionEvent qui dérive quant à lui (de manière plus ou moins directe) de la classe ancêtre Event.
Le moteur de jeu va récupérer tous les événements générés (dont certains lui seront d'ailleurs sans doute exclusivement destinés, pour permettre, par exemple de passer en mode "modification des options"), et, comme il a acces à l'ensemble des modules, il est en mesure de renvoyer tous les événements qu'il reçoit vers une classe qui, en fonction du module dans lequel elle se trouve, réagira de manière adaptée à l'événement.
Si tu crées une hiérarchie suffisamment précise d'événement (par exemple, le lancement d'un sort par quelqu'un génère un événement de type SpriteCreationEvent qui hérite de SpriteEvent qui hérite de Event), il sera particulièrement facile de faire en sorte que cette classe "laisse tomber" tout un pan de la hiérarchie Event, parce qu'elle n'a pas à s'en occuper ;)
Rien n'empêche une MovableEntity d'avoir une direction et une vitesse nulle, ce qui la réduirait de facto à... rester sur place :DCitation:
Je reviens sur les sprites, tu as dis que pour toi un sprite c'était par exemple un coeur a ramasser. Or après tu dis que ta classe Sprite dérive de MovableEntity. Or un coeur ne se déplace pas...
C'est, peut etre, sans effet sur un jeu 2D, mais les objets qui attendent d'être ramassés pourraient parfaitement être soumis à la pesenteur et ne peuvent, en aucun cas, être considérés comme étant aussi immobiles (meme s'il le sont de facto du fait de leur vitesse et leur direction nulle) que l'arbre ou la pierre qui fait partie du décors :aie:
De plus, et, malgré les apparences, un objet d'inventaire est beaucoup plus mobile qu'il ne peut y paraitre au premier abord:
Tu me diras sans doute que j'ergote ou que je joue sur les mots, mais, comprend tu pourquoi il est important qu'un objet d'inventaire soit considéré comme une entité susceptible de se déplacer ?
- Lorsqu'il sera ramassé, il passera du monde à l'inventaire
- Si le joueur le tiens en main, il subira les mouvement du bras auquel est rattachée la main qui le tiens
- S'il y a un "bandeau d'inventaire", il peut se trouver (ou non) à différentes positions dans ce bandeau, et le bandeau lui-même peut se trouver à différentes positions dans l'écran de jeu
Le problème est alors que tout est sprite, car, meme s'il n'y a qu'une image à afficher, cela peut etre considéré comme un sprite :aie:Citation:
Donc je vois mal ce que tu appelles "Sprites" car pour moi, un sprite est simplement une image qui peut s'animer (plusieurs images) ou pas (une seule image). Une entité est alors composé d'un Sprite, ainsi que d'autres attributs comme la position, la taille, la visibilité...
Mais, dans ce cas, quelle différence fait on entre le sprite et l'entité :question: à moins qu'il n'y ait, tout simplement, qu'une relation d'aggrégation entre les deux, et que le sprite participe au comportement d'affichage :question:
Ce n'est pas exclu, c'est une manière d'envisager les choses ;)