Moving Ball > Problème de test de sortie d'écran non effectué

Merci @Steph, c’est très clair.

Comme je trouve l’approche géométrique plus spectaculaire au niveau du rendu, j’ai simplifié ton code pour en faciliter la lecture en conservant les parties pour GEOMETRIC équivalant 1.

Par contre, je ne comprends pas cette formule (notamment .5f et < h ? -2 : 2)

        else {
            fh += .5f * dh;
            if (abs(fh - h) > 1.5f) h += fh < h ? -2 : 2;
        }

Je continue mes investigations !

Ha… personnellement, je trouvais l’effet obtenu plus sympa avec une progression arithmétique. Du coup j’ai bien fait de te proposer une autre solution. :slightly_smiling_face:

Bref, donc, oui, tu peux simplifier le code en enlevant toutes les structures conditionnelles qui prennent en compte la valeur de la macro GEOMETRIC en ne retenant que les portions de codes où on considère qu’elle vaut 1.

Et l’instruction suivante :

if (abs(fh - h) > 1.5f) h += fh < h ? -2 : 2;

est équivalente à :

if (abs(fh - h) > 1.5f) {
    if (fh < h) {
        h -= 2;
    } else {
        h += 2;
    }
}

Est-ce que c’est plus clair pour toi ?

1 Like

Oui, c’est plus clair comme cela (même si je ne sais pas ce qu’est 1.5f ? f veut dire quoi ?

J’ai avancé cet après-midi sur mon code. Du coup, je m’y prends en 3 étapes :

  1. Simplification de ton code => fait (je pourrai y revenir si je change d’avis entre arithmétique et géométrique :wink:)
  2. Restructuration du code de mon Pong en m’inspirant du tient (i.e. Struct, Constructeur, 1 instance par raquette (joueur / Gamebuino)…) => en cours
  3. Intégration de ton code dans ma version de Pong restructurée => à faire

L’intérêt de reprendre mon code et de l’adapter est que ça me force à comprendre et à m’interroger.
Je ne te cacherai pas que j’en ai bien ch*é à décortiquer ton code, mais j’avoue qu’après y avoir passé du temps, il est bien plus lisible que le miens, et que cela me pousse à poursuivre en C++.

Cela m’amène une première question : dans le code suivant, tu appelles la méthode update() de l’instance player qui contient le corps du jeu.

void update() {

    player.update();
    
}

Dans mon cas, j’ai 2 instances de Paddle : player (raquette de gauche) et computer (raquette de droite). Du coup, est-ce qu’il faut je fasse pareil avec computer.update() ? Car ça n’a pas de sens d’exécuter 2 fois le corps du jeu ? Ou le corps du jeu doit être mis ailleurs que dans Paddle ?

Par contre, j’aurai bien ce code à exécuter pour afficher chacune des requêtes ?

void draw() {

    player.draw();
    computer.draw();
    
}

Je galère encore pour restructurer les “computer.posY” par exemple en C++, mais il faut que je prenne le temps de le faire.

Attention… le “corps du jeu” est contenu dans la fonction loop() et s’articule autour de la séquence classique :

readButtons();
update();
draw();

Et c’est dans la fonction globale update() qu’on est censés mettre à jour les variables du jeu :

void update() {

    player.update();

}

Dans mon petit exemple, je n’avais que les variables du joueur à mettre à jour, mais dans ton cas, il faudra également mettre à jour les variables de l’adversaire :

void update() {

    player.update();
    computer.update();

}

Même chose pour la fonction draw(), comme tu l’as justement suggéré. :slightly_smiling_face:

Bon courage !
Toutes les notions abordées dans ce thread te seront très utiles pour tes projets, et une fois le cap franchi, tu pourras te pencher sur des notions plus avancées (et découvrir les class par exemple). Mais chaque chose en son temps. J’ai moi-même encore une très longue route devant moi concernant le C++ (que j’ai découvert avec la Gamebuino) !

Bon, j’ai simplifié mon code pour commencer par la gestion de la raquette du joueur.

Première difficulté : la raquette sort de l’écran (et disparait) alors que normalement, je l’ai bloquée en haut et en bas avec le code suivant

    void update() {
           if (posY < 0)                           posY = 0;
      else if (posY+hauteur > gb.display.height()) posY = gb.display.height();
      }

Deuxième difficulté : comment faire pour passer la variable “hauteur” définie dans la fonction menu() (dont la taille dépend de la difficulté choisie) dans le modèle Paddle ?

  if (game.difficulty == Difficulty::hard) {
      hauteur = 8;
    }
  else {
      hauteur = 12;
    }

Dernière question par anticipation : lorsque je vais afficher la raquette gérée par l’ordinateur avec une couleur différente (par exemple rouge) de celle du joueur (bleu), comment faire ?
Car il n’y a qu’une seule méthode draw() dans Paddle…

Idem pour le calcul du positionnement de la raquette de l’ordinateur (que je vais mettre dans la méthode update()) : comment sera t-elle différenciée entre player.update() (où je mets le code pour ne pas sortir de l’écran) et computer.update() (qui contiendra le code de calcul de la position par rapport à la balle en mouvement) ?
Désolé pour les questions de néophyte…

Rapidement, parce-que je file au TAF là…

1. La raquette sort de l’écran…

Normal, tu ne la replaces pas correctement… Ce n’est pas :

posY = gb.display.height();

Mais :

posY = gb.display.height() - hauteur;

2. Passer la variable hauteur au modèle Paddle

Il suffit d’ajouter un argument h au constructeur :

Paddle(uint8_t posX, uint8_t posY, uint8_t h)
: posX(posX)
, posY(posY)
, vitesse(V)
, hauteur(h) // <-- la hauteur de la raquette est donc paramétrable
, largeur(L)
{}

Ici tu ne peux utiliser la constante H (hauteur fixée par défaut) définie dans Paddle, puisque ton menu induit une hauteur variable…

3. Des couleurs différentes pour les raquettes

Même chose, tu peux ajouter un argument color au constructeur, que tu dois stocker dans ton modèle pour l’utiliser au moment de dessiner la raquette :

struct Paddle {

    static const uint8_t V = 2;
    static const uint8_t L = 3;

    uint8_t  posX;
    uint8_t  posY;
    uint8_t  vitesse;
    uint8_t  hauteur;
    uint8_t  largeur;
    Color    color; // <-- chaque raquette a sa propre couleur
    
    Paddle(uint8_t posX, uint8_t posY, uint8_t h, Color color) // <-- paramétrée ici
    : posX(posX)
    , posY(posY)
    , vitesse(V)
    , hauteur(h)
    , largeur(L)
    , color(color) // <-- on l'affecte au modèle ici
    {}
    
    void up()   { posY -= V; }
    void down() { posY += V; }

    void update() {
        uint8_t sh = gb.display.height();
             if (posY < 0)            posY = 0;
        else if (posY + hauteur > sh) posY = sh - hauteur;
    }

    void draw() {

        gb.display.setColor(color); // <-- et on fixe la bonne couleur à l'affichage
        gb.display.fillRect(posX, posY, largeur, hauteur);

    }

};

Il faut que tu poses un peu et que tu reprennes un à un les concepts que je t’ai présentés… Tu disposes maintenant de toute la logique pour parvenir à trouver les réponses à tes questions tout seul il me semble. Tu ne crois pas ? Tu as toutes les briques, il ne reste plus qu’à les assembler correctement :wink:

Tu peux aussi t’inspirer des exemples traités dans mon tuto Handling images on the Gamebuino Meta qui utilisent les mêmes concepts… Notamment ceux des chapitres :

  • How to display your images
  • How to move your sprite

Je rebondis juste sur la fin de la réponse de Steph pour appuyer sur ce qu’il a dit: n’hésitez pas à regarder l’académie et à analyser un peu les codes. Rechercher fait certe perdre un peu de temps mais ça fait aussi progresser et ça évite de poser toujours les même questions sur le forum. En diverssifiant les questions, on rend sa lecture plus intéressante et en lisant un peu les codes, les tutos de l’académie et les références de base, ca rend le temps passé par ceux qui les ont écrit plus profitable.

@Steph, Après une pause de quelques semaines, je me lance dans le déboggage de mon jeu.

Il y a un truc que je n’arrive pas à faire : j’ai deux struct consécutives (Paddle et Ball) qui comportent des méthodes, et deux instances (paddle et ball) déclarées après chaque struct.
Ce qui donne un truc comme cela :

struct Paddle {
        ...code...
};

Paddle player(10, 30, LIGHTBLUE);
Paddle computer(gb.display.width()-12, 30, PINK);

struct Ball {
    ...code...
};

Ball ball(20, 20, bSpeed, -bSpeed, 4, 0);

Sauf que pour tester une collision entre la raquette et la balle, j’ai créé une méthode dans Paddle qui compare la position de la raquette (posY) avec celle de la balle (ball.posY) :

if (ball.posY > (posY+hauteur/2) && posY + hauteur < gbheight) {
    posY += 2;
}

Mais j’ai une erreur (error: ‘ball’ was not declared in this scope) car le compilateur ne reconnait pas ball.posY qui n’est déclaré qu’après l’instance paddle dans l’exemple de mon code, comme si il exécutait le code séquenciellement.

Du coup, comment faire car si j’inverse l’ordre des instances paddle et ball, ça me créé des erreurs dans l’autre sens (le compilateur ne reconnait pas les méthodes de paddle appelées dans ball) ? Y a t’il moyen “d’externaliser” l’instance ball pour qu’elle soit reconnue dans l’instance paddle ?

Bref, je ne sais pas si je suis clair :upside_down_face:
J’ajoute le lien vers le code si besoin.

P.S. Cette pause m’a fait du bien, il y a pas mal de concepts que j’ai compris et des trucs que j’ai améliorés par rapport à ma dernière version du code, et ce n’est pas fini !

Peut-être plus simplement : ball.posX est une variable locale utilisée par une instance dans une structure que j’essaie d’appeler depuis une instance d’une autre structure !
Ma question étant : comment rendre cette variable “globale” afin qu’elle soit utilisée par une instance qui n’est pas dans la même structure.

Désolé @Jicehel mais là tu te trompes… et tu risques d’embrouiller notre ami @Cric
Relis plus attentivement les messages suivants :

[ Arghhhh… @jichehel a supprimé ses messages… dommage, on aurait pu en discuter… ]

@Cric je te réponds dès que je trouve un moment…

Bonsoir @Cric,

En effet, le code est lu, compilé et executé de manière séquentielle. Avant d’utiliser une variable, un type, une structure de données, une classe, un objet, etc. ou d’invoquer une fonction ou une méthode, tu dois nécessairement la (ou le) déclarer au préalable.

Par exemple, le code suivant :

#include <Gamebuino-Meta.h>

struct Ball {
    uint8_t x, y;
};

struct Paddle {
    uint8_t x, y;
    void handleCollision() {
        if (ball.x < x) { }
    }
};

Ball   ball;
Paddle player, computer;

void setup() { gb.begin(); }

void loop() {
    gb.waitForUpdate();
    player.handleCollision();
}

… provoquera une erreur à la compilation :

/Users/steph/.../pong/pong.ino: In member function 'void Paddle::handleCollision()':
pong:10:13: error: 'ball' was not declared in this scope
         if (ball.x < x) { }
             ^~~~

… puisque la variable globale ball n’est déclarée qu’après l’instruction qui en fait usage.

Pour résoudre ceci, tu peux donc déclarer l’existence de la variable ball avant de l’utiliser :

#include <Gamebuino-Meta.h>

struct Ball {
    uint8_t x, y;
};

Ball ball;

struct Paddle {
    uint8_t x, y;
    void handleCollision() {
        if (ball.x < x) { }
    }
};

Paddle player, computer;

void setup() { gb.begin(); }

void loop() {
    gb.waitForUpdate();
    player.handleCollision();
}

Et là ça marche beaucoup mieux…

Mais plutôt que d’utiliser directement la variable globale ball dans la méthode handleCollision(), tu peux la passer comme argument à la méthode :

#include <Gamebuino-Meta.h>

struct Ball {
    uint8_t x, y;
};

struct Paddle {
    uint8_t x, y;
    void handleCollision(Ball b) {
        if (b.x < x) { }
    }
};

Ball   ball;
Paddle player, computer;

void setup() { gb.begin(); }

void loop() {
    gb.waitForUpdate();
    player.handleCollision(ball);
}

Ici, l’instruction :

player.handleCollision(ball);

… passe la variable ball par valeur à la méthode handleCollision() :

void handleCollision(Ball b) { ... }

Autrement dit, la variable b qui désigne cette valeur reçoit une copie de l’objet ball, et non l’objet ball en tant que tel. Par conséquent, si la méthode handleCollision() modifie la valeur d’un attribut de la variable b, par exemple en écrivant :

void handleCollision(Ball b) {
    b.x = 0;
}

C’est la valeur de l’attribut x de la copie qui sera modifiée !.. et ça n’aura aucun effet sur la valeur de l’attribut x de l’objet ball original.

Pour résoudre cette problématique, C++ te permet d’invoquer une fonction ou une méthode en lui passant comme argument, non pas la copie de l’objet, mais une référence à l’objet lui-même, c’est à dire une variable qui désigne son adresse mémoire. De cette manière, la fonction ou la méthode peut avoir directement accès à l’objet original (et modifier éventuellement la valeur de ses attributs).

Dans ce cas, on écrira les choses ainsi :

#include <Gamebuino-Meta.h>

struct Ball {
    uint8_t x, y;
};

struct Paddle {
    uint8_t x, y;
    void handleCollision(Ball &b) {
        if (b.x < x) { }
    }
};

Ball   ball;
Paddle player, computer;

void setup() { gb.begin(); }

void loop() {
    gb.waitForUpdate();
    player.handleCollision(ball);
}

La fonction déclare accepter une référence à un objet de type Ball (remarque bien l’usage de l’opérateur & qui préfixe le nom de la variable b) :

void handleCollision(Ball &b) { ... }

Et peut alors modifier directement l’objet original par le biais de sa référence, puisqu’il s’agit en réalité de son adresse mémoire. Par exemple, si on écrit :

void handleCollision(Ball &b) {
    b.x = 0;
}

Et qu’on invoque la méthode ainsi :

player.handleCollision(ball);

… alors la valeur de l’attribut x de la variable globale ball sera modifiée et vaudra 0 après l’invocation de la méthode handleCollision().

Néanmoins, la possibilité qu’une méthode puisse modifier la valeur d’une variable qui lui est passée par référence peut avoir des effets de bord indésirables. On peut donc empêcher cela en déclarant que l’objet passé par référence doit rester constant (ne doit pas être modifié) :

void handleCollision(const Ball &b) { ... }

Par conséquent, si tu écris :

void handleCollision(const Ball &b) {
    b.x = 0;
}

Tu obtiendras l’erreur de compilation suivante :

/Users/steph/.../pong/pong.ino: In member function 'void Paddle::handleCollision(const Ball&)':
pong:10:18: error: assignment of member 'Ball::x' in read-only object
         b.x = 0;
               ^

… te signifiant que tu ne peux pas affecter une valeur à l’attribut d’un objet en lecture seule (imposée par le mot-clé const).

Mais alors, tu vas me dire : “Ben il suffit de lui passer la copie et le tour est joué !”

Pas tout à fait => une copie implique de réserver un espace mémoire supplémentaire pour recevoir les données de la copie justement ! Alors qu’en passant l’objet par référence, tu n’effectues pas de copie de l’objet original en mémoire. Et c’est une énorme différence, surtout si ton objet comporte de nombreux attributs, ou des attributs qui pèsent lourd ! Ça te permet donc d’économiser de l’espace mémoire, dans tous les cas, que tu modifies ou non la valeur des attributs de l’objet passé par référence.

Il existe des notions très voisines à tout ce que je viens de t’expliquer, je veux parler des pointeurs. Mais je préfère ne pas les aborder tout de suite pour ne pas t’embrouiller la tête et te laisser le temps de digérer ma réponse…

Est-ce que j’ai été suffisamment clair ?

Pour résumer, voilà le code qui me paraît le plus approprié par-rapport à ton problème initial :

#include <Gamebuino-Meta.h>

struct Ball {
    uint8_t x, y;
};

struct Paddle {
    uint8_t x, y;
    void handleCollision(Ball &b) {
        if (b.x < x) { }
    }
};

Ball   ball;
Paddle player, computer;

void setup() { gb.begin(); }

void loop() {
    gb.waitForUpdate();
    player.handleCollision(ball);
}

Je n’ai pas déclaré void handleCollision(const Ball &b) parce-que j’imagine que ta gestion des collisions est susceptible de modifier les attributs de la balle. Néanmoins, de manière générale, pose-toi toujours la question de savoir de quel objet relève la responsabilité d’exécuter telle ou telle action, surtout quand il perturbe son entourage…

Pour finir, @Jicehel t’avait répondu avec 3 messages soulevant, par erreur, des points essentiels quant aux notions qui caractérisent les objets en C++. Mais il les a malheureusement retirés, et c’est bien dommage… Je n’aborderai donc pas ces points ici pour ne pas faire dériver la discussion en-dehors de ton propre questionnement. Je trouverai bien l’occasion d’y revenir à un moment clef :wink:

@Steph ,
Merci pour cette longue réponse parfaitement claire comme d’habitude.

Inverser l’ordre des 2 structures de données ne changerait rien à la problématique, puisque l’une et l’autre s’appellent mutuellement au travers de leurs méthodes…

Le passage d’argument dans la méthode (en copie ou en valeur) est effectivement ce que je ne savais pas faire. Comme je ne fais que lire la valeur de la position de la balle, une copie de la variable ball est suffisante pour ma comparaison.

Cependant, que j’utilise une copie ou un passage en valeur de la variable, j’ai toujours un message d’erreur lors de la compilation que je recopie ici :

In member function 'void Paddle::updateComputer(int&)':
Pong_1_Player_V3.2:81:21: error: request for member 'posY' in 'b', which is of non-class type 'int'
               if (b.posY > (posY+hauteur/2) && posY + hauteur < gbheight) {
                     ^~~~

Le code mis à jour avec ton code.

Hello @Cric

Pour commencer, je rebondis sur :

Le passage d’argument dans la méthode (en copie ou en valeur) est effectivement ce que je ne savais pas faire. Comme je ne fais que lire la valeur de la position de la balle, une copie de la variable ball est suffisante pour ma comparaison.

Manifestement, je n’ai pas été assez clair… :confused:

  1. Le passage par valeur implique une copie en mémoire, ça n’est donc pas l’idéal dans ton cas, puisque tu gaspilles de la mémoire.

  2. Il vaut mieux privilégier un passage par référence (pour économiser de la mémoire) et, elle doit être constante, puisque tu n’as pas besoin de modifier les valeurs des attributs de l’objet passé en argument.

Au passage, tu devrais également appliquer ce principe à la méthode update() de Ball :

struct Ball {
    
    void update(const Paddle &player, const Paddle &computer) { ... }
    
};

Par ailleurs, je vois que tu as modifié ton post en ajoutant un lien vers ton code et tu me précises également que les méthodes de Ball et Paddle s’appellent les unes les autres…

C’est un détail important qui implique de restructurer entièrement ton code pour séparer les déclarations de Ball et Paddle, de leurs définitions.

En effet, tu te trouves devant le dilemme suivant :

  1. D’une part, Paddle a besoin de connaître la définition de Ball ;

    struct Paddle {
    
        // ... code ...
    
        void Paddle::updateComputer(const Ball &b) {
            if (b.posY > (posY+hauteur/2) && posY + hauteur < gbheight) {
                posY += 2;
            } else if (b.posY < (posY + hauteur/2) && posY > 0) {
                posY -= 2;
            }
                if (posY < 1) posY = 2;
            else if (posY+hauteur > gbheight) posY = gbheight-hauteur;
        }
    
        // ... code ...
    
    };
    
  2. Et d’autre part, Ball a besoin de connnaître la définition de Paddle :

    struct Ball {
    
        // ... code ...
    
        void update(const Paddle &player, const Paddle &computer) {
            // ... code ...
            if ((posX == player.posX+player.largeur)
            && (posY+taille >= player.posY) 
            && (posY <= player.posY+player.hauteur)) {
                // ball.speedX = 1;
                speedX = 1;
            }
            // ... code ...
        }
    
        // ... code ...
    
    };
    

Ces dépendances croisées posent effectivement problème, et pour les résoudre, il faut :

  1. d’une part, séparer les déclarations et les définitions de chacun des deux modèles objet,
  2. et d’autre part, effectuer une déclaration anticipée (forward declaration) de l’un des deux modèles pour qu’il soit résolu dans l’autre (pour contourner le problème de l’oeuf et de la poule…).

Autrement dit, commençons par déclarer Paddle comme ceci:

struct Paddle {

    static const uint8_t V = 2; // Vitesse
    static const uint8_t L = 3; // Largeur
    static const uint8_t H = 12; // Hauteur

    uint8_t posX;
    uint8_t posY;
    uint8_t vitesse;
    uint8_t hauteur;
    uint8_t largeur;
    uint8_t gbheight = gb.display.height();
    Color   color;

    Paddle(uint8_t posX, uint8_t posY, Color color);

    void up();
    void down();
    void updatePlayer();
    void updateComputer(const Ball &b);
    void draw();
    void drawscore();

};

Tu vois que les méthodes sont déclarées, mais pas encore définies (le corps des méthodes n’est pas encore précisé). La définition viendra plus tard…

Petit problème tout de même :

void updateComputer(const Ball &b);

Alors que Ball n’a pas encore été déclarée…

Pour résoude cela, on peut effectuer une déclaration anticipée de Ball pour indiquer au compilateur que cette entité existe bien quelque-part dans le code et que sa définition suivra :

// Déclaration anticipée permettant de
// résoudre les dépendances croisées à venir
struct Ball;

struct Paddle {

    // ... code ...

    void updateComputer(const Ball &b);

    // ... code ...

};

On peut ensuite (après Paddle) procéder à la déclaration de Ball :

struct Ball {

    uint8_t posX;
    uint8_t posY;
    uint8_t speedX;
    uint8_t speedY;
    uint8_t taille;
    uint8_t modDiff; // Difficulté du jeu
    
    Ball(uint8_t posX, uint8_t posY, uint8_t speedX, uint8_t speedY, uint8_t taille, uint8_t modDiff);

    void update(const Paddle &player, const Paddle &computer);
    void start();
    void draw();

};

Ici :

void update(const Paddle &player, const Paddle &computer);

Tout se passe bien, puisque Paddle a déjà été déclaré.

Il ne reste plus qu’à fournir les définitions de Paddle et Ball.

Par exemple, le constructeur de Paddle est défini comme suit :

Paddle::Paddle(uint8_t posX, uint8_t posY, Color color)
: posX(posX)
, posY(posY)
, color(color)
, vitesse(V)
, hauteur(H)
, largeur(L)
{}

Puis viennent les définitions des méthodes up()et down() :

// Player Up (BUTTON_UP) or Down (BUTTON_DOWN)
void Paddle::up()   { posY -= V; }
void Paddle::down() { posY += V; }

… etc. je ne vais pas toutes les écrire ici…

Ensuite, il faut faire la même chose pour Ball.

Par exemple, le constructeur se définit ainsi :

Ball::Ball(uint8_t posX, uint8_t posY, uint8_t speedX, uint8_t speedY, uint8_t taille, uint8_t modDiff)
: posX(posX)
, posY(posY)
, speedX(speedX)
, speedY(speedY)
, taille(taille)
, modDiff(modDiff)
{}

La méthode draw() comme ça :

// Affichage de la balle en mouvement
void Ball::draw() {
    gb.display.setColor(YELLOW);
    gb.display.fillRect(posX, posY, taille, taille);       
}

… etc. faut faire pareil pour toutes les autres méthodes…

Note que, pour le cas de ton code, toutes les déclarations et définitions se trouvent dans le même fichier (le .ino), mais qu’il est beaucoup plus commode de scinder les choses dans différents fichiers.

C’est là qu’interviennent les notions de fichiers d’en-têtes (header), qui sont déclaratifs et portent l’extension .h, et de fichiers d’implémentation, qui portent l’extension .cpp.

Par exemple :

  • Paddle.h comporterait uniquement la déclaration de Paddle,
  • et Paddle.cpp ses définitions.

Mais ne compliquons pas les choses trop vite et admettons que tout se trouve dans ton .ino comme tu l’as fait jusque-là…

Tu trouveras une restructuration de ton code ici. Je n’ai pas analysé la pertinence de ton implémentation, j’ai juste réécris les choses pour que la compilation se passe bien…

Est-ce que tu y vois plus clair maintenant ?

1 Like

Hello @Steph,

  1. Concernant le passage par valeur, pour mon information, est-ce qu’à chaque fois qu’une copie est faite en mémoire, est-elle détruite au fur et à mesure de chaque rafraîchissement (i.e. il n’y a qu’une seule copie en mémoire à la fois) ? Où la mémoire est-elle dupliquée (saturée ?) à chacune des copies ?
    Dans le second cas, je comprends mieux l’intérêt du passage par référence…

  2. J’ai regardé ton code rapidement et cela va beaucoup m’aider, je t’en remercie. Il se fait tard, je n’aurai pas le temps de m’y mettre ce soir hélas :frowning:

  3. Je trouve mon code extrêmement bordélique et pas aisé à lire. Alors j’attends tes conseils avec impatience pour scinder mon code ! J’ai vu cette utilisation dans les exemples de code que j’ai étudiés, cela va beaucoup simplifier la compréhension du code. En même temps, c’est typiquement un truc que j’aurai pu trouver par moi même en cherchant un peu sur le net…

Salut,
Pour la gestion de la mémoire je crois que si tu passes une primitive par valeur (une simple nombre, un simple caractère, un booléen, etc, la mémoire est libérée lorsque le programme sort du scope ou elle est utilisée.
par exemple

void use_number(int number) {
    // une opération avec number...
} // <- number sort du scope ici et sa mémoire devrait être libérée

par contre si tu passes une copie d’une structure complexe comme l’instance d’un objet, à ce moment là il y aura une allocation faite sur le heap de la mémoire, et en c/c++ il me semble qu’il faut que tu libère la mémoire manuellement si ton objet est censé avoir une durée de vie limitée dans le code. C’est pour ça qu’à priori pour ce genre de structure il vaut mieux utiliser des pointeurs. Mais si tu as quand même besoin d’un copie il faut que tu utilises une fonction por nettoyer la mémoire. En c++ il me semble que tu peux libérer de la mémoire avec le mot clé delete.

struct SomeStruct {
   // ... fields
};

void use_object_copy(SomeStruct an_object_copy) {
    // Utiliser an_object_copy
} // <- la mémoire de an_object_copy n'est pas libérée ici

SomeStruct my_object = new SomeStruct();

SomeStruct my_object_copy = new SomeStruct();
memcpy(&my_object_copy, &my_object, sizeof(my_object_copy)); // il y a peut-être d'autres manière de faire des copies en c++ mais je ne sais plus trop..

use_object_copy(my_obect_copy);
// Ici la mémoire utilisée par my_object_copy est toujours utilisée, donc si tu sais que tu n'en auras plus besoin dans le programme tu peux libérer la mémoire avec
delete my_object_copy;

Le mot clé delete appelle le destructeur de la structure. Si ta structure contient des champs avec des allocations dynamique du genre my_object.a_field = new float(), il me semble qu’il vaut mieux implémenter ton destructeur toi même:

struct SomeStruct {
    // ...
    SomeStruct() { // constructor
        a_number = new float();
    }
    SomeStruct() { // destructor
        delete a_number;
    }
    float *a_number;
};

Voilà, mes exemples ne sont peut-être pas à recopier non plus en l’état, il doit y avoir des imprécisions, je ne fais pas beaucoup de c/c++… mais sur le principe de la gestion de la mémoire à mon avis tu peux retenir qu’il vaut mieux créer et utiliser des pointeurs pour toutes les valeurs non primitives, sinon tu dois faire la gestion de la mémoire de tes copies manuellement… Et donc pour répondre très concrètement à ta question 1: le fait que la boucle de rendu se rafraîchisse ne nettoie pas la mémoire, si tu fais une copie à chaque frame et qu’elle n’est pas nettoyée, effectivement tu auras une fuite de mémoire.

Bon courage avec ton développement !

Ouhlala…

@Codnpix Merci d’avoir pris la peine d’essayer d’apporter une réponse à @Cric … mais hélas, il y a beaucoup de confusion dans ta réponse, et j’ai peur que @Cric soit un peu perdu avec, d’une part, des éléments qu’il ne maîtrise pas, et d’autre part, des erreurs fondamentales… :confused:

Par ailleurs, je suis désolé, mais je ne peux pas non plus me permettre de passer mon temps à tout détailler dans mes explications, c’est beaucoup trop chronophage, et il existe de nombreuses ressources en ligne pour apprendre le C++. Je vous encourage donc à aller les étudier en préambule, puis à revenir poser des questions plus ciblées qui ne demandent pas à chaque fois de toute reprendre à la base. Sinon c’est diificilement gérable à la longue.

Par exemple, voici une bonne référence : Learn C++

Le passage par valeur (que la variable soit d’un type fondamental ou non) entraîne nécessairement une copie des données (passées en argument) sur la pile (stack). Mais le tas (heap) n’intervient pas du tout dans ce processus. Par conséquent, lorsqu’on souhaite passer un objet (donc une aggrégation de données), instance d’un struct ou d’une class, le passage par valeur est à proscrire. Ceci pour éviter d’encombrer la pile avec une multitude de données liées les unes aux autres, et d’éviter du même coup d’effondrer les performances. Surtout si la fonction ou la méthode est appelée de nombreuses fois (comme dans une boucle par exemple). Enfin, une fois que la fonction ou la méthode est terminée, les données sont retirées de la pile automatiquement. Il n’y’a rien à faire de particulier ici. Il ne s’agit pas d’allocation dynamique (qui, elle, implique le tas) et pour laquelle il faut explicitement veiller à libérer la mémoire allouée.

@Cric je t’ai donné des éléments pour écrire les choses correctement lorsque tu manipules des objets, en utilisant le passage par référence. Je n’ai pas ajouté la notion de pointeur pour ne pas t’embrouiller avant que tu aies digéré le code et ses implications (même si ces notions ne sont pas étrangères l’une à l’autre). Essaie de les appliquer et de relire ce que je t’ai écrit, ou d’aller te renseigner sur le sujet en cherchant par toi-même. Les infos pululent sur le sujet…

Pour ce qui est de scinder ton code en plusieurs fichiers, je te laisse un peu chercher par toi-même également (je t’ai déjà donné l’essentiel des éléments, auxquels tu devras ajouter l’utilisation de la directive #include, que tu utilises déjà par ailleurs). Si vraiment tu ne t’en sors pas, tu peux revenir poser la question. :wink:

On essaye de rester sympa quand même hein :wink:

?.. Qu’est-ce qu’il y a de pas « sympa » ?

Ben c’est un peu condescendant ton “oulalah”… on fait ce qu’on peut …

Bref désolé pour ma confusion, j’aurais peut-être pas dû me lancer dans une explication sachant que j’ai pas touché de c++ depuis un moment. Effectivement la copie est automatique lors du passage par valeur, et donc gérée par le compilateur sur la pile, il y a des languages où c’est pas systématique.

Bonne continuation à tous.

1 Like

Absolument pas ! Et je suis désolé que tu l’aies pris comme ça…

Évidemment qu’on fait ce qu’on peut :slight_smile:
Et moi le premier ! Je ne prétends pas avoir toutes les réponses, loin de là, mais je pense m’être déjà penché sur pas mal de questions pour avoir quelques réponses à apporter tout de même.

Maintenant, si mes interventions vous dérangent, je peux aussi cesser d’intervenir…