Un jeu en 2D, c'est avant tout des sprites.
Je me propose ici de vous fournir ma méthode pour gérer ces sprites.
J'utiliserais 2 classes :
- Une classe Sprite
- Une classe Vecteur
La classe vecteur
Un vecteur est un objet simple pour se repérer dans le plan. Je trouve avantageux d'utiliser des vecteurs plutôt qu'un simple système de coordonnées pour nos sprites. En effet, on peut ici ajouter des opérateurs entre nos vecteurs, ce qui va simplifier grandement les calculs (mais peut-être pas le code...).
Nos vecteurs auront pour seul caractéristique une composante sur l'axe des ordonnées, et une sur l'axe des abscisses. De plus, cette composante sera du type déclaré avec le vecteur (grâce aux templates), de façon à avoir là un type de base souple.
Voici le code du vecteur :
La classe Sprite
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 #define cX 0 #define cY 1 /* Classe représentant un vecteur */ template <typename T> class Vecteur { private: // Composantes T m_X; T m_Y; public: Vecteur (); Vecteur (T x, T y); // Accesseurs T GetX () {return m_X;} T GetY () {return m_Y;} // Mutateurs void SetX (T x) {m_X = x;} void SetY (T y) {m_Y = y;} // Méthodes T GetNorme (); // Opérateurs Vecteur operator + (Vecteur vec); // Somme Vecteur operator - (Vecteur vec); // Différence Vecteur operator * (T v); // Produit T operator * (Vecteur vec); // Produit scalaire T& operator [] (int index); // Retourne la composante index (X, Y ou Z) }; template <typename T> Vecteur <T>::Vecteur () { SetX (0); SetY (0); } template <typename T> Vecteur <T>::Vecteur (T x, T y) { SetX (x); SetY (y); } template <typename T> T Vecteur <T>::GetNorme () { return sqrt (pow (GetX (), 2) + pow (GetY (), 2)); } template <typename T> Vecteur <T> Vecteur<T>::operator + (Vecteur vec) { Vecteur <T> new_vec (GetX () + vec.GetX (), GetY () + vec.GetY ()); return new_vec; } template <typename T> Vecteur <T> Vecteur<T>::operator - (Vecteur vec) { Vecteur <T> new_vec (GetX () - vec.GetX (), GetY () - vec.GetY ()); return new_vec; } template <typename T> Vecteur <T> Vecteur<T>::operator * (T v) { Vecteur <T> new_vec (GetX () * v, GetY () * v); return new_vec; } template <typename T> T Vecteur<T>::operator * (Vecteur vec) { return GetX () * vec.GetX () + GetY () * vec.GetY (); } template <typename T> T& Vecteur<T>::operator [] (int index) { if (index == cX) return m_X; else if (index == cY) return m_Y; else return m_X; }
La classe Sprite contiendra :
- Un vecteur Position
- Un vecteur Vitesse
- Un vecteur accélération
- Une vitesse maximum
- Un angle
- Un "zoom" (qui servira à modifier la taille de l'image affichée)
- Un tableau de bitmaps
- Un entier indiquant quelle image du tableau d'images afficher.
Comme méthodes :
-Une fonction Move, qui déplacera le sprite en fonction des différents vecteurs.
- Une fonction Clear qui délestera le sprite de ses images
- Une fonction Draw (surchargée) qui affichera le sprite à l'écran.
De plus, l'on ajouteras 3 fonctions manipulant le sprite :
- Une fonction regardant si le point de coordonnée spécifié est inclue dans le sprite
- Une fonction qui regarde si le pixel spécifié est transparent
Ces deux fonctions étant là pour le traitement des collisions, effectué grâce à la fonction "AreCollide", qui teste les collisions entre deux sprite spécifiés en argument.
Voici la classe :
Voilà les définitions des différentes méthodes, constructeurs, etc :
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 /* Le sprite est un objet graphique, animé ou non. */ class Sprite { protected: // Position, vitesse et accélération Vecteur <float> m_vPos; Vecteur <float> m_vSpeed; Vecteur <float> m_vAccel; // Vitesse max (Norme vecteur vitesse maximum) float m_fVMax; // Angle, radians float m_fAngle; // Echelle float m_fScale; // Image // Tableau d'images ALLEGRO_BITMAP **m_ppImages; int m_iCurrentImage; int m_iNImages; public: Sprite (); Sprite (float posX, float posY, float speedX, float speedY, float accelX, float accelY, float VMax, float angle, float scale, ...); ~Sprite (); // Position, vitesse et accélération // Mutateurs void SetPosX (float value) { m_vPos [cX] = value;} void SetPosY (float value) { m_vPos [cY] = value;} void SetSpeedX (float value) { if (sqrt (pow (value, 2) + pow (GetSpeed ().GetY (), 2)) < GetVMax ()) m_vSpeed [cX] = value;} void SetSpeedY (float value) { if (sqrt (pow (GetSpeed (). GetX (), 2) + pow (value, 2)) < GetVMax ()) m_vSpeed [cY] = value;} void SetAccelX (float value) { m_vAccel [cX] = value;} void SetAccelY (float value) { m_vAccel [cY] = value;} void SetVMax (float value) { m_fVMax = value;} void SetAngle (float angle) { m_fAngle = angle;} void SetScale (float scale) { m_fScale = scale;} // Accesseurs Vecteur <float> GetPos () { return m_vPos;} Vecteur <float> GetSpeed () { return m_vSpeed;} Vecteur <float> GetAccel () { return m_vAccel;} float GetVMax () { return m_fVMax;} float GetAngle () { return m_fAngle;} float GetScale () { return m_fScale;} // Méthodes générales void Move (); void Clear (); void AddEffect (); void Draw (); void Draw (int posX, int posY); // Image void SetNumberOfImages (int n) { m_iNImages = n; m_ppImages = (ALLEGRO_BITMAP **) malloc (n * sizeof (ALLEGRO_BITMAP *));} void SetImage (int n, const char *image); void SetCurrentImage (int value) { m_iCurrentImage = value;} int GetCurrentImage () {return m_iCurrentImage;} int GetNumberOfImages () {return m_iNImages;} ALLEGRO_BITMAP* GetCurrentBitmap () {return m_ppImages [GetCurrentImage ()];} }; /* Fonctions génériques */ bool IsInclude (int, int, Sprite); bool IsTransparent (ALLEGRO_COLOR); bool AreCollide (Sprite *, Sprite *);
Et la fonction de collision (et ses acolytes)
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 #include "Vecteur.h" #include "Sprite.h" Sprite::Sprite () { } Sprite::Sprite (float posX, float posY, float speedX, float speedY, float accelX, float accelY, float VMax, float angle, float scale, ...) { SetPosX (posX); SetPosY (posY); SetVMax (VMax); SetSpeedX (speedX); SetSpeedY (speedY); SetAccelX (accelX); SetAccelY (accelY); SetAngle (angle); SetScale (scale); va_list lptr; // On compte le nombre d'images int NImages = 0; va_start (lptr, scale); for (NImages = 0; va_arg (lptr, const char *) != NULL; NImages++) {} // On alloue le tableau d'images du sprite; SetNumberOfImages (NImages); // On charge les images va_start (lptr, scale); for (int i = 0; i < NImages; i++) SetImage (i, va_arg (lptr, const char *)); SetCurrentImage (0); } Sprite::~Sprite () { } /* La méthode Move permet de modifier les coordonnées du sprite en fonction de sa vitesse et de son accélération */ void Sprite::Move () { /* Accélération */ if (m_vSpeed.GetNorme () < GetVMax ()) { m_vSpeed [cX] = m_vSpeed.GetX () * m_vAccel.GetX (); m_vSpeed [cY] = m_vSpeed.GetY () * m_vAccel.GetY (); } /* Déplacement */ m_vPos = m_vPos + m_vSpeed; } /* Clear libère la mémoire allouée au sprite */ void Sprite::Clear () { for (int i = 0; i < m_iNImages; i++) { al_destroy_bitmap (m_ppImages [i]); } free (m_ppImages); } /* SetImage permet de charger l'image ayant pour chemin "image" dans le tableau d'images du sprite à l'emplacement 'n'. */ void Sprite::SetImage (int n, const char *image) { try { m_ppImages [n] = al_load_bitmap (image); if (m_ppImages [n] == NULL) throw 0; else { al_convert_mask_to_alpha (m_ppImages [n], al_get_pixel (m_ppImages [n], 0, 0)); } } catch (int e) { if (e == 0) std::cerr << "Cannot load image " << image << std::endl; system ("pause"); } } /* La méthode Draw dessine le sprite sur le bitmap de destination, aux coordonnées du sprite */ void Sprite::Draw () { if (m_ppImages [GetCurrentImage ()] != NULL) { al_draw_rotated_scaled_bitmap (m_ppImages [GetCurrentImage ()], al_get_bitmap_width (m_ppImages [GetCurrentImage ()]) / 2, al_get_bitmap_height (m_ppImages [GetCurrentImage ()]) / 2, GetPos ().GetX (), GetPos ().GetY (), GetScale (), GetScale (), GetAngle (), 0); } } void Sprite::Draw (int posX, int posY) { if (m_ppImages [GetCurrentImage ()] != NULL) { al_draw_rotated_scaled_bitmap (m_ppImages [GetCurrentImage ()], al_get_bitmap_width (m_ppImages [GetCurrentImage ()]) / 2, al_get_bitmap_height (m_ppImages [GetCurrentImage ()]) / 2, posX, posY, GetScale (), GetScale (), GetAngle (), 0); } }
La fonction de collision teste si deux pixels des sprites spécifiés sont superposés. Ce traitement étant gourmand, j'ai préféré ne tester qu'un pixel sur 4 (constante PAS), mais l'on peut affiner la précision si nécessaire.
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 /* Retourne vrai si le point de coordonné spécifié appartient au sprite spécifié */ bool IsInclude (int posX, int posY, Sprite *sprite) { int limX = sprite->GetScale () * al_get_bitmap_width (sprite->GetCurrentBitmap ()); int limY = sprite->GetScale () * al_get_bitmap_height (sprite->GetCurrentBitmap ()); if (posX >= sprite->GetPos ().GetX () && posX <= sprite->GetPos ().GetX () + limX && posY >= sprite->GetPos ().GetY () && posY <= sprite->GetPos ().GetY () + limY) return true; return false; } /* Retourne vrai si le point a une composante alpha = 255 */ bool IsTransparent (ALLEGRO_COLOR color) { unsigned char r, g, b, a; al_unmap_rgba (color, &r, &g, &b, &a); if (a != 255) return true; else return false; } /* Regarde si les deux sprites sont en collision */ #define PAS 4 bool AreCollide (Sprite *sprite1, Sprite *sprite2) { // On regarde si la distance entre les deux sprites nécessite un test plus poussé Vecteur <float> s1s2; s1s2 = sprite1->GetPos (); s1s2 = s1s2 * -1; s1s2 = s1s2 + sprite2->GetPos (); // Diamètre sprite 1 Vecteur <float> D1 (sprite1->GetScale () * al_get_bitmap_width (sprite1->GetCurrentBitmap ()), sprite1->GetScale () * al_get_bitmap_height (sprite1->GetCurrentBitmap ())); // Diamètre sprite 2 Vecteur <float> D2 (sprite2->GetScale () * al_get_bitmap_width (sprite2->GetCurrentBitmap ()), sprite2->GetScale () * al_get_bitmap_height (sprite2->GetCurrentBitmap ())); // Si D1 + D2 < distance s1s2 if (D1.GetNorme () + D2.GetNorme () < s1s2.GetNorme ()) return false; // On recréé deux bitmaps correspondants aux nouvelles images ALLEGRO_BITMAP *bmp1 = NULL; ALLEGRO_BITMAP *bmp2 = NULL; ALLEGRO_BITMAP *target = al_get_target_bitmap (); // On reagrde les dimensions... // ...du sprite 1 // 1.5 = marge d'erreur bmp1 = al_create_bitmap (1.5 * sprite1->GetScale () * al_get_bitmap_width (sprite1->GetCurrentBitmap ()), 1.5 * sprite1->GetScale () * al_get_bitmap_height (sprite1->GetCurrentBitmap ())); // ...du sprite 2 bmp2 = al_create_bitmap (1.5 * sprite2->GetScale () * al_get_bitmap_width (sprite2->GetCurrentBitmap ()), 1.5 * sprite2->GetScale () * al_get_bitmap_height (sprite2->GetCurrentBitmap ())); // On dessine les deux bitmaps // Sprite 1 al_set_target_bitmap (bmp1); al_clear_to_color (al_map_rgb (0, 0, 0)); sprite1->Draw (0, 0); // Sprite 2 al_set_target_bitmap (bmp2); al_clear_to_color (al_map_rgb (0, 0, 0)); sprite2->Draw (0, 0); // Remise par défaut du bitmap de destination al_set_target_bitmap (target); al_draw_bitmap (bmp1, 0, 0, 0); al_convert_mask_to_alpha (bmp1, al_map_rgb (255, 0, 255)); al_convert_mask_to_alpha (bmp2, al_map_rgb (255, 0, 255)); al_lock_bitmap (bmp1, al_get_bitmap_format(bmp1), ALLEGRO_LOCK_READONLY); al_lock_bitmap (bmp2, al_get_bitmap_format(bmp2), ALLEGRO_LOCK_READONLY); for (int i = 0; i < al_get_bitmap_width (bmp1); i+=PAS) { for (int j = 0; j < al_get_bitmap_height (bmp1); j+=PAS) { // Si le point donné appartient aux deux sprites simultanément if (IsInclude (sprite1->GetPos ().GetX () + i, sprite1->GetPos ().GetY () + j, sprite2)) { // Si le point donné n'est pas magic pink sur le premier sprite if (!IsTransparent (al_get_pixel (bmp1, i, j))) { // Si le point donné n'est pas de la couleur magic pink sur le second sprite if (!IsTransparent (al_get_pixel (bmp2, sprite2->GetPos ().GetX () + i - sprite1->GetPos ().GetX (), sprite2->GetPos ().GetY () + j - sprite1->GetPos ().GetY ()))) { al_unlock_bitmap (bmp1); al_unlock_bitmap (bmp2); al_destroy_bitmap (bmp1); al_destroy_bitmap (bmp2); return true; } } } } } al_unlock_bitmap (bmp1); al_unlock_bitmap (bmp2); al_destroy_bitmap (bmp1); al_destroy_bitmap (bmp2); return false; }
Notez bien que tout cela est loin d'être parfait, je vous donne juste ma méthode dont vous pouvez vous inspirer si cela vous plait.
Je remerci d'avance ceux qui voudront bien me dire ce qu'ils pensent de tout cela.
(J'ai conscience que c'est pas explicatif, mais je pense qu'on peut se débrouiller avec les commentaires).
Partager