Bonjour,
Je suis en train de coder un petit moteur 2D, et, étant débutant, je me heurte à de nombreux problèmes de conceptions dont un que je n'arrive pas à résoudre seul. En effet, j'aimerais mettre en œuvre un système de gestion de collisions capable de faire interagir différents types de collisions entres eux (Box, Point, Circle...).
Afin de rendre opaque les différents algorithmes d'interaction des collisions et de rendre générique leur utilisation, j'étais parti sur l'idée de créer une classe abstraite ACollision, de laquelle je ferais dériver une classe fille par type de collision à gérer. Voici un exemple édulcoré de code pour donner une idée plus précise de ce que j'imagine :
J'aurais donc voulu gérer mes collisions par polymorphisme et laisser les différentes surcharges de la fonction check_collision s'occuper d'utiliser le bon algorithme pour moi. Seulement voilà, sur un pointeur ACollision, impossible d’appeler les méthodes check_collision puisqu'elles ne sont pas encore connues. J'aurais pu faire des dynamic_cast pour résoudre le problème, mais j'ai lu partout que c'était à éviter car vraiment pas propre.
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 class ACollision { public : virtual ~ACollision( void ) {}; virtual void check_collision( const Box& box ) = 0; virtual void check_collision( const Circle& circle ) = 0; virtual void check_collision( const Point& point ) = 0; }; class Box : public ACollision { public : virtual void check_collision( const Box& box ) { std::cout << "Box/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Box/Circle collision" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Box/Point collision" << std::endl; } }; class Circle : public ACollision { public : virtual void check_collision( const Box& box ) { std::cout << "Circle/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Circle/Circle collison" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Circle/Point collision" << std::endl; } }; class Point : public ACollision { public : virtual void check_collision( const Box& box ) { std::cout << "Point/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Point/Circle collison" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Point/Point collision" << std::endl; } };
J'ai donc demandé conseil sur le chat du site, et on m'a orienté vers le design pattern visitor, qui semble en effet tout indiqué dans mon cas. Seulement voilà, si je l'implémente dans sa forme classique, je ne résous pas vraiment mon problème. En effet, les implémentations que j'ai pu voir du pattern visitor permettent bien de déclencher une action spécifique à un type de classe donné, mais pas de faire interagir deux classes entre elles. Voilà ce que j'ai tenté :
J'ai beau retourner le problème dans tous les sens, j'ai toujours l'une des deux classes dont le type m'est inconnu, ce qui m'empêche de pouvoir appeller la bonne méthode. Sur l'exemple ci dessus, dans la classe Collision_visitor, je me retrouve avec une référence sur un ACollision, et une classe fille dont le type est donc connu. Seulement, impossible de faire appeler la bonne méthode check_collision via la référence sur ACollision, car cette dernière ne connais pas encore cette méthode. A l'inverse, impossible d'envoyer la classe ACollision à la classe de type connu, car les méthodes check_collisions sont censée traiter un type précis, hors ACollision n'est pas une collision traitable en tant que telle.
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 #include <iostream> #include <list> class Visitor; class Box; class Circle; class Point; class Visitor { public: virtual void visit( Box& box ) = 0; virtual void visit( Circle& circle ) = 0; virtual void visit( Point& point ) = 0; }; class ACollision { public : virtual ~ACollision( void ) {}; virtual void accept( Visitor& visitor ) = 0; }; class Box : public ACollision { public : virtual void accept( Visitor& visitor ) { return ( visitor.visit( *this ) ); } virtual void check_collision( const Box& box ) { std::cout << "Box/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Box/Circle collision" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Box/Point collision" << std::endl; } }; class Circle : public ACollision { public : virtual void accept( Visitor& visitor ) { return ( visitor.visit( *this ) ); } virtual void check_collision( const Box& box ) { std::cout << "Circle/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Circle/Circle collison" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Circle/Point collision" << std::endl; } }; class Point : public ACollision { public : virtual void accept( Visitor& visitor ) { return ( visitor.visit( *this ) ); } virtual void check_collision( const Box& box ) { std::cout << "Point/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Point/Circle collison" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Point/Point collision" << std::endl; } }; class Collision_visitor : public Visitor { public : Collision_visitor(const ACollision& object) : object( object ) { } virtual void visit( Box& box ) { object.check_collision( box ); //Problème : l'une des deux classes à comparer n'est pas de type concret, je n'ai donc pas moyen de faire appeler la bonne méthode check_collision! } virtual void visit( Circle& circle ) { object.check_collision( circle ); //Problème : l'une des deux classes à comparer n'est pas de type concret, je n'ai donc pas moyen de faire appeler la bonne méthode check_collision! } virtual void visit( Point& point ) { object.check_collision( point ); //Problème : l'une des deux classes à comparer n'est pas de type concret, je n'ai donc pas moyen de faire appeler la bonne méthode check_collision! } private : const ACollision& object; }; int main( void ) { std::list< ACollision* > objects_list; objects_list.push_back( new ( Box ) ); objects_list.push_back( new ( Circle ) ); objects_list.push_back( new ( Point ) ); std::list< ACollision* >::iterator objects_it1 = objects_list.begin(); std::list< ACollision* >::iterator objects_end = objects_list.end(); for ( ; objects_it1 != objects_end ; ++objects_it1) { std::list< ACollision* >::iterator objects_it2 = objects_list.begin(); for ( ; objects_it2 != objects_end ; ++objects_it2 ) { Collision_visitor collision_visitor( *( *objects_it2 ) ); ( *objects_it1 )->accept( collision_visitor ); } } return ( 0 ); }
J'ai tenté de trafiquer le code précédent, et j'ai trouvé une solution qui fonctionne parfaitement. Seulement voilà, pour résoudre mon problème, j'ai dû déclarer des méthodes dans la classe mère qui reçoivent des types de classe fille. Sans être un crack en conception, il me semble que le fait d'avoir une classe mère au courant des classes filles est relativement crado. Voilà ce que ça donne :
Ce code affiche :
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 #include <iostream> #include <list> class Box; class Circle; class Point; class ACollision { public : virtual ~ACollision( void ) {}; virtual void check_collision( ACollision& object ) = 0; virtual void check_collision( const Box& box ) = 0; virtual void check_collision( const Circle& circle ) = 0; virtual void check_collision( const Point& point ) = 0; }; class Box : public ACollision { public : virtual void check_collision( ACollision& object ) { return ( object.check_collision( *this ) ); } virtual void check_collision( const Box& box ) { std::cout << "Box/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Box/Circle collision" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Box/Point collision" << std::endl; } }; class Circle : public ACollision { public : virtual void check_collision( ACollision& object ) { return ( object.check_collision( *this ) ); } virtual void check_collision( const Box& box ) { std::cout << "Circle/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Circle/Circle collison" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Circle/Point collision" << std::endl; } }; class Point : public ACollision { public : virtual void check_collision( ACollision& object ) { return ( object.check_collision( *this ) ); } virtual void check_collision( const Box& box ) { std::cout << "Point/Box collision" << std::endl; } virtual void check_collision( const Circle& circle ) { std::cout << "Point/Circle collison" << std::endl; } virtual void check_collision( const Point& point ) { std::cout << "Point/Point collision" << std::endl; } }; int main( void ) { std::list< ACollision* > objects_list; objects_list.push_back( new ( Box ) ); objects_list.push_back( new ( Circle ) ); objects_list.push_back( new ( Point ) ); std::list< ACollision* >::iterator objects_it1 = objects_list.begin(); std::list< ACollision* >::iterator objects_end = objects_list.end(); for ( ; objects_it1 != objects_end ; ++objects_it1) { std::list< ACollision* >::iterator objects_it2 = objects_list.begin(); for ( ; objects_it2 != objects_end ; ++objects_it2 ) ( *objects_it1 )->check_collision( *( *objects_it2 ) ); } return ( 0 ); }
Ce qui est tout à fait valide. C'est exactement le comportement que je recherchais.Box/Box collision
Circle/Box collision
Point/Box collision
Box/Circle collision
Circle/Circle collison
Point/Circle collison
Box/Point collision
Circle/Point collision
Point/Point collision
J'en arrive donc à ma question : Comment pourrais-je implémenter ce mécanisme de manière propre?
En vous remerciant d'avance.
Partager