Premmier programme intéressant demande de conseil

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour, je viens de finir mon premier jeu fonctionnant avec la librairie SFML. Tout fonctionne comme prévus, cependant je me demande si mon approche de la POO et de la programmation en c++ et en général est correcte ou si il faut que je change ma manière de concevoir un programme. Donc voici le programme en entier:

main.cpp

  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
#include <SFML/Graphics.hpp>

#include "ball.h"
#include "cursor.h"

using namespace sf;

int main(int argc,char *argv[])
{
  // Paramétrage de la fenêtre
  RenderWindow window(VideoMode(800,600),"Pong for two players");
  window.setFramerateLimit(30);
  window.setKeyRepeatEnabled(false);


  // Définition des textures,sprites et shapes
  Texture menu_texture;
  if (!menu_texture.loadFromFile("menu.png"))
  {
      window.close();
  }
  Sprite menu;
  menu.setTexture(menu_texture);
  menu.setPosition(0,0);

  // Variables et objets
  bool start = false;
  cursor player_1(0);
  cursor player_2(794);
  ball the_ball;

  // Chargement texture objets
  if (!player_1.loadTexture() || !player_2.loadTexture())
  {
      window.close();
  }
  if (!the_ball.loadTexture())
  {
      window.close();
  }

  // Boucle du menu
  while (window.isOpen() && !start)
  {
      Event event;
      while (window.pollEvent(event))
      {
          if (event.type == Event::Closed)
          {
              window.close();
          }
          else if (event.type == Event::KeyPressed && event.key.code == Keyboard::Space)
          {
              start = true;
          }
      }
      window.draw(menu);
      window.display();
  }

  // Boucle du jeu
  while (window.isOpen())
  {
      Event event;
      while (window.pollEvent(event))
      {
          if (event.type == Event::Closed)
          {
              window.close();
          }
      }

      if (Keyboard::isKeyPressed(Keyboard::Up))
      {
          player_2.moveUp();
      }
      else if (Keyboard::isKeyPressed(Keyboard::Down))
      {
          player_2.moveDown();
      }
      if (Keyboard::isKeyPressed(Keyboard::A))
      {
          player_1.moveUp();
      }
      else if (Keyboard::isKeyPressed(Keyboard::W))
      {
          player_1.moveDown();
      }

      // Gestion de l'affichage des curseurs
      window.clear(Color::Black);
      window.draw(player_1.print());
      window.draw(player_2.print());

      // Déplacement de la balle et affichage
      the_ball.move(player_1.getPosX(),player_1.getPosY(),player_2.getPosX(),player_2.getPosY());
      if (the_ball.getPosX() > 800 || the_ball.getPosX() < 0)
      {
          the_ball.restart();
          window.draw(the_ball.print());
          window.display();
          sf::Clock clock;
          sf::Time elapsed1 = clock.getElapsedTime();
          sf::Time constante = sf::seconds(2);
          while (elapsed1.asSeconds() < constante.asSeconds() && window.isOpen())
          {
              elapsed1 = clock.getElapsedTime();
              Event event;
              while (window.pollEvent(event))
              {
                  if (event.type == Event::Closed)
                  {
                      window.close();
                  }
              }
          }
      }
      window.draw(the_ball.print());

      // Actualisation de l'écran
      window.display();
  }
  return 0;
}

ball.cpp

 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
#include <SFML/Graphics.hpp>
#include <cmath>

#include "ball.h"
#include "cursor.h"

// Constructeur
ball::ball()
{
  x = 394;
  y = 294;
  speed_x = -5;
  speed_y = 3;
}

// Chargement de la texture
bool ball::loadTexture()
{
  if (!ball_texture.loadFromFile("ball.png"))
  {
      return false;
  }
  else
  {
      ball_sprite.setTexture(ball_texture);
      return true;
  }
}

// Affichage
sf::Sprite ball::print()
{
  ball_sprite.setPosition(x,y);
  return ball_sprite;
}

// Gestion du mouvement
void ball::move(int x1,int y1, int x2, int y2)
{
  if (std::abs(x + speed_x - x1 - 5) < 6 && (y - 100) <= y1 && y >= y1)
  {
      x = 6;
      speed_x = -speed_x;
      y += speed_y;
  }
  else if (std::abs(x + speed_x - x2) < 6 && (y - 100) <= y2 && y >= y2)
  {
      x = 792;
      speed_x = -speed_x;
      y += speed_y;
  }
  else if (y + speed_y >= 594)
  {
      y = 594;
      speed_y = -speed_y;
      x += speed_x;
  }
  else if (y + speed_y <= 0)
  {
      y = 0;
      speed_y = -speed_y;
      x += speed_x;
  }
  else
  {
      x += speed_x;
      y += speed_y;
  }
}

// Obtention de la position x
int ball::getPosX()
{
  return x;
}

// Replacement de la balle
void ball::restart()
{
  x = 394;
  y = 294;
  speed_x = -speed_x;
  speed_y = 3;
}

cursor.cpp

 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
#include <SFML/Graphics.hpp>

#include "cursor.h"

// Constructeur
cursor::cursor(int set)
{
  x = set;
  y = 249;
}

// Chargement de la texture
bool cursor::loadTexture()
{
  if (!cursor_texture.loadFromFile("cursor.png"))
  {
      return false;
  }
  else
  {
      cursor_sprite.setTexture(cursor_texture);
      return true;
  }
}

// Déplacement vers le haut
void cursor::moveUp()
{
  if (y > 3)
  {
      y -= 4;
  }
  else if (y > 2)
  {
      y -= 3;
  }
  else if (y > 1)
  {
      y -= 2;
  }
  else if (y > 0)
  {
      y--;
  }
}

// Déplacement vers le bas
void cursor::moveDown()
{
  if (y < 497)
  {
      y += 4;
  }
  else if (y < 498)
  {
      y += 3;
  }
  else if (y < 499)
  {
      y += 2;
  }
  else if (y < 500)
  {
      y++;
  }
}

// Affichage
sf::Sprite cursor::print()
{
  cursor_sprite.setPosition(x,y);
  return cursor_sprite;
}

// Obtention de la position x
int cursor::getPosX()
{
  return x;
}

// Obtention de la position y
int cursor::getPosY()
{
  return y;
}

ball.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef ball_h
#define ball_h

#include <SFML/Graphics.hpp>

class ball
{
  public:
      ball();
      bool loadTexture();
      void move(int x1,int y1, int x2, int y2);
      void restart();
      sf::Sprite print();
      int getPosX();

  private:
      sf::Texture ball_texture;
      sf::Sprite ball_sprite;
      int x,y;
      int speed_x,speed_y;
};

#endif

cursor.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef cursor_h
#define cursor_h

#include <SFML/Graphics.hpp>

class cursor
{
  public:
      cursor(int set);
      bool loadTexture();
      void moveUp();
      void moveDown();
      sf::Sprite print();
      int getPosX();
      int getPosY();

  private:
      int x,y;
      sf::Texture cursor_texture;
      sf::Sprite cursor_sprite;
};

#endif

+0 -0

Cette réponse a aidé l'auteur du sujet

Salut,

Tu viens de balancer un gros paquet de code donc je pense que les gens auront la flemme de regarder ça. De ce que j'ai compris c'est un Pong, j'ai bon ? Pour avoir des retours, laisse le code mais rajoute des explications. Pourrais-tu détailler le rôle de chaque classe et pourquoi tu as choisi cette architecture ? Le main et les deux headers permettent de comprendre ça mais je pense que ça peut être une bonne chose de rajouter des explications.

Je ne suis absolument pas un expert de la POO mais pour moi ton architecture est cohérente et respecte l'encapsulation.

+0 -0
Staff

Cette réponse a aidé l'auteur du sujet

Pour aller dans le même sens que Grimur je dirais que l'aspect "pavé de code" est fortement renforcé par la méthode main qui est longue à souhait.

Je pense qu'il faudrait que tu sépares une grande partie de ton main en objet : un objet Game qui aura les actions de base et un objet launcher qui aura initGame (les différentes initialisation), playGame (la boucle infinie) et stopGame (pour terminer le truc une fois sorti de la boucle).

+0 -0

Cette réponse a aidé l'auteur du sujet

Ton main est beaucoup trop gros. Tu dois séparer les différentes étapes. A terme, tu dois pouvoir présenter un code qui contient les parties logiques (déplacement, collision etc..) et les parties affichages distinctement séparées.

 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
while (window.isOpen() && !start)
  {
      Event event;
      while (window.pollEvent(event))
      {
          if (event.type == Event::Closed)
          {
              window.close();
          }
          else if (event.type == Event::KeyPressed && event.key.code == Keyboard::Space)
          {
              start = true;
          }
      }
      window.draw(menu);
      window.display();
  }

  // Boucle du jeu
  while (window.isOpen())
  {
      Event event;
      while (window.pollEvent(event))
      {
          if (event.type == Event::Closed)
          {
              window.close();
          }
      }
...

Pas très élégant, à terme, essaye de te renseigner sur les machines à états : Game State Pattern

Et plus généralement, la wiki de la SFML propose beaucoup de ressources utiles qui pourrait t'aider : wiki SFML

+0 -0

Cette réponse a aidé l'auteur du sujet

Lu'!

Un peu de lecture pour toi :

Je pense en particulier à tes classes Ball et Cursor qui ne dissocient pas leur représentation graphique de leur modèle. Tu peux aussi te pencher sur les questions des sémantiques de classes (j'ai dû mettre les liens dans les discussions) qui sont essentielles à comprendre tôt.

First : Always RTFM - "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein

+0 -0

Cette réponse a aidé l'auteur du sujet

Salut,

juste quelques trucs comme ça, pour le nom des classes ont met souvent des majuscules et pour le nom des attributs il est souvent pratique de les différencier en mettant un préfixe (m_, d_).

Sinon ta fonction cursor::moveDown (et cursor::moveUp par échéance) peut s'écrire plus simplement :

1
2
3
4
5
6
7
void cursor::moveDown()
{
    if (y < 497)
        y += 4;
    else
        y += 500 - y;
}

ou bien avec l'opérateur ternaire

1
2
3
4
void cursor::moveDown()
{
    y += (y < 497) ? 4 : 500 - y;
}

P.S. : J'ai écrit mon message en même temps que Kass'Peuk, je n'ai donc pas lue les discussion qu'il a mis

Pour les constructeurs tu peux utiliser la composition puisque tu n'a pas de vérification à faire :

1
2
3
4
5
cursor::cursor(int set)
    :
    x(set),
    y(249)
{}

et pour ta class ball tu peux même mettre un constructeur par défaut en mettant les valeurs dans ta class, example de code

1
2
3
4
5
6
7
8
9
class Person
{
    std::string d_name = "Jean-Mi";
    int         d_age  = 42;
    float       d_size = 1.7;

public:
    Person() = default;
};

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Auteur du sujet

Merci à tous pour vos réponses, et je suis désolé de ne pas avoir mis d'explications, j'étais encore dans le feu de l'action. Je vais lire et étudier attentivement tout ça , merci. Je détaillerai plus mon post la prochaine fois pour faciliter votre travail.

J'ai encore une petite question:

Salut,

juste quelques trucs comme ça, pour le nom des classes ont met souvent des majuscules et pour le nom des attributs il est souvent pratique de les différencier en mettant un préfixe (m_, d_).

Sinon ta fonction cursor::moveDown (et cursor::moveUp par échéance) peut s'écrire plus simplement :

1
2
3
4
5
6
7
void cursor::moveDown()
{
    if (y < 497)
        y += 4;
    else
        y += 500 - y;
}

ou bien avec l'opérateur ternaire

1
2
3
4
void cursor::moveDown()
{
    y += (y < 497) ? 4 : 500 - y;
}

P.S. : J'ai écrit mon message en même temps que Kass'Peuk, je n'ai donc pas lue les discussion qu'il a mis

Pour les constructeurs tu peux utiliser la composition puisque tu n'a pas de vérification à faire :

1
2
3
4
5
cursor::cursor(int set)
    :
    x(set),
    y(249)
{}

et pour ta class ball tu peux même mettre un constructeur par défaut en mettant les valeurs dans ta class, example de code

1
2
3
4
5
6
7
8
9
class Person
{
    std::string d_name = "Jean-Mi";
    int         d_age  = 42;
    float       d_size = 1.7;

public:
    Person() = default;
};

LudoBike

Comment fonctionne exactement le constructeur par défaut ? Est-ce que ce constructeur reprendrait juste toutes les initialisations faites dans la classe ?

+0 -0

Cette réponse a aidé l'auteur du sujet

Comment fonctionne exactement le constructeur par défaut ? Est-ce que ce constructeur reprendrait juste toutes les initialisations faites dans la classe ?

Birugire

Dans ce cas oui mais lis cette page pour plus d'information

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0

Cette réponse a aidé l'auteur du sujet

Si je peux me permettre, je remarque aussi beaucoup de nombres magiques (des entiers qui apparaissent sans explication) qui rendent beaucoup moins lisible le code. Tu pourrais définir des constantes avec des noms de variables qui expriment ce que représente l'entier inséré dans ta ligne de code.

+0 -0

Je vais redire la même chose que tout le monde, avec d'autres mots.

Ta classe main fait 2 choses ; une boucle pour gérer le menu, puis une boucle pour gérer le jeu. Bof..

En POO, l'idée est que chaque fonction fait une chose et une seule.

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte