Partage
  • Partager sur Facebook
  • Partager sur Twitter

Questions pour bien organiser mon moteur de jeu

Réalisation d'un moteur de jeu dans une optique d'apprentissage

    6 octobre 2020 à 0:48:48

    Bonjour, bonsoir ! :D

    Je poste de nouveau ce message, la première fois était sur le forum d'un autre site où je n'ai eu qu'une seule réponse donc peut-être avez-vous déjà vu mon sujet ! Je me permets de poser de nouveau mes questions, car je suis resté sur ma faim :(

    (Il va sans dire que vu la longueur de mon message je ne cherche pas une réponse à absolument tout mais simplement si une des 4 questions semble vous parler, me donner des petites pistes et indications ou juste votre avis global n'importe quel élément de réponse me suffira amplement !)

    ----------------------------------

    J’ai récemment débuté un projet de moteur de jeu 2D utilisant la librairie SFML dans l’optique de m’améliorer dans ce langage et dans la conception 2D. Je n’ai pas de réels soucis techniques pour le moment mais, au fil de l’avancement du projet je me rends compte que je ne sais pas vraiment comment je devrais organiser mon projet et hiérarchiser mes différentes classes.

    Plus précisément, j’ai l’infime conviction de bafouer moult règles de la programmation orientée objet et si je souhaite que mon moteur soit réutilisable et un minimum décent cela ne sera pas tenable sur le long terme.

    Voici donc les questions conceptuelles (mais pas seulement) que je me pose :

    1) J’ai conçu une classe TileMap, qui hérite d’une classe Map. Au début, j’étais parti sur une utilisation d’un std::vector à 2 dimensions, néanmoins j’ai lu dans un livre qu’il était préférable de travailler en interne sur un objet matriciel à une dimension et ainsi linéariser le tableau 2D.

    J’ai alors une architecture proche de :

    class TileMap : public Map{
     
    public:
        TileMap(const SpriteSheet& spriteSheet, const std::vector<std::vector<int>>& mapVector); // L'un des constructeur de ma classe TileMap
     
     
    private:
        SpriteSheet spriteSheet_;
        Matrix<int> mapMatrix_;
    };



    Puis lors dans le corps de ce constructeur je remplis ma matrice avec les éléments du std::vector passé en paramètre.

    Je me demande si il est logique de procéder ainsi ou si il serait préférable de forcer l’utilisateur à utiliser lui-même un objet de type Matrix lors de l’utilisation de la classe TileMap ? Le principe de Demeter ne le permettrait à priori pas ?

    2) Toujours dans cette classe TileMap, j’ai rencontré la problématique suivante : où la logique (j’entends ce qui touche aux collisions de tuiles) devrait-elle se trouver ? Qu’est-ce qui devrait gérer tout cela ?

    Pour le moment ma class TileMap propose de nombreuses méthodes telles que

    makeSolid(int value);

    qui prend en argument un entier censé représenter la valeur de la tile dans la TileMap et qui ajoute cet entier à un tableau qui liste toutes les tiles solides. Une telle méthode a-t-elle sa place ici ? J’ai de plus des méthodes qui me semblent pas mal alourdir ma classe telles que isSolid(Vector2u const& index) qui renvoie un bool si la position en indice (en terme de tiles, (1,1) étant la toute première à l’écran) est solide.

    2) - Suite - J’ai une fonction dans un fichier totalement à part nommé collision.hpp

    template<typename T> bool collisionSolid(T const& A, TileMap const& map);


    Qui grossièrement me dit si mon objet de type T touche une tuile solide.

    De nombreuses autres fonctions sont présentes dans ce fichier comme des tests de collision avec les bordures d’un objet Window en argument, ou des tests de collisions avec la méthode des boites englobantes, la souris...

    Est-ce la bonne façon de procéder ?

    3) Je me demande si il serait judicieux de concevoir une classe abstraite PhysicEntity avec en attributs une masse, une vitesse etc, et dont hériteront les principaux objets sur lesquels je pourrai tester des collisions et appliquer des lois physiques plus tard avec une autre classe Physic. Cette idée serait-t-elle viable sur le long terme ?

    Je me demande aussi si il est logique qu’un Sprite hérite directement de PhysicEntity ?

    De plus, je pourrais dans une telle situation songer à modifier mes fonctions de tests de collision, par exemple :

    bool collisionSolid(PhysicEntity* A, TileMap const& map);

    4) Enfin, pour finir (promis c'est la dernière question cette fois !), il m'arrive très souvent de procéder d'une manière similaire :

    Je construis une classe notons A, et je lui donne en attribut privé un pointeur vers un autre type (primitif ou non).

    class A{
     
    public:
        A(TYPE& objet) : ptr_(&objet) {}
     
    private:
        TYPE* ptr_; // Pointeur d'un type quelconque
    };



    De sorte à pouvoir travailler sur ce pointeur et donc sur l'objet passé en paramètre sur d'autres méthodes de ma classe. C'est notamment très util pour ma classe Animation qui découpe un objet SpriteSheet que je lui passe en paramètre lors de l'utilisation d'un objet de type Animation.

    Je me demande si cette pratique est courante et normale ou si elle est à éviter ?

    De plus, je cherche à éviter le plus possible d'utiliser des raw pointers et je les remplace dès que possible par des smart pointers, néanmoins je n'ai jusqu'à présent pas trouvé le moyen de les utiliser de cette manière là (classe A plus haut). En effet, si j'utilise un unique_ptr, le souci sera que std::make_unique<>(...) créera une copie de l'objet pointé, et ce n'est pas ce que je recherche.

    Grossièrement je recherche un équvalent de ce code : 

    class A{
     
    public:
     
        A(int& v) : ptr_(&v) { } // Constructeur qui fait pointer l'attribut ptr_ vers le passage par référence en argument
     
        void foo() {
            *ptr_ = 0; // Déréférencement
        }
     
    private:
        int* ptr_; // Pointeur
    };
     
    int main(){
     
        int variable { 1 };
     
        A object(variable);
        object.foo();
     
        std::cout << variable; // Affiche bien 0 et non 1
     
        return 0;
    }



    Mais utilisant les smart pointers.

    ----------------------------------------

    Voilà voilà, désolé pour la longueur du message et un grand merci à ceux qui m’auront lu jusqu’au bout,

    Bonne soirée à tous/toutes :)

    • Partager sur Facebook
    • Partager sur Twitter
    Anonyme
      6 octobre 2020 à 7:40:09

      Salut,

      Pour ta question 1. tu peux utiliser une matrice, un vector ou autre ... Peu importe ce que tu vas prendre ça va rester en interne de ta classe donc c'est plus pour le programmeur que ça importe.

      Aussi j'aurais plus tendance à utilisé un initialiser_list au lieu d'un vector dans le constructeur pour plus de rigidité dans ton code et plus de simplicité dans le code de l'utilisateur (on peut l'initialiser avec "{}").

      Pour t'aider essaye de réfléchir à comment ton utilisateur devrait utilisé ta librairie : plutôt orienté Widget (comme Qt ou Java.Swing ou encore de l'html/js) ou quelque chose qui s'approche plus de la SDL, etc ...

      • Partager sur Facebook
      • Partager sur Twitter
        6 octobre 2020 à 11:26:03

        Bonjour, merci beaucoup pour ta réponse :)

        Je ne connaissais pas initialiser_list je vais aller me renseigner un peu plus mais c'est vrai que ça simplifiera les choses pour l'utilisateur

        Concernant l'utilisation de ma librairie je pense plutôt à une utilisation comme SDL ou SFML (que j'utilise pour l'affichage et le son justement), avec des surcouches des objets SFML pour apporter plus de flexibilité à l'utilisateur et des classes supposées faciliter la plupart des phases de création d'un jeu qu'on doit souvent refaire (gestion des animations, collisions, physique...)

        Donc plutôt une utilisation à la SDL effectivement, idéalement je voudrais quelque chose qui dans la forme ressemble au moteur d'un intervenant "Nazara Engine" si vous connaissez.

        Merci encore, bonne journée :)

        • Partager sur Facebook
        • Partager sur Twitter
        Anonyme
          6 octobre 2020 à 22:39:20

          Je viens de me pencher un peu sur la physique que tu veux implémenter dans le code.

          J'aurais tendance à séparer clairement la partie affichage (tilemap et compagnie) de la partie logique (physique et compagnie) dans le programme. En partant du principe que la physique est juste là pour dynamiser l'affichage on peut aisément comprendre que la physique n'est pas l'affichage (donc pas d'héritage) mais le manipule. On peut faire l'analogie avec l'html (affichage) et le JS (physique).

          Pour la classe Sprite je la mettrais dans l'affichage et je créerais une autre classe PhysiqueSprite qui hériterait de la classe principale de la partie physique (PhysiqueEntity ?) qui aurait en plus un pointeur / référence sur un Sprite qu'il va manipuler dans la TileMap.

          • Partager sur Facebook
          • Partager sur Twitter
            7 octobre 2020 à 10:54:27

            Rebonjour merci de prendre de nouveau le temps de me répondre  !

            J'ai souvent lu un peu partout qu'il fallait effectivement séparer l'affichage de la logique, mais je ne suis pas sur de bien comprendre. Finalement ce que tu me dis c'est qu'une méthode, par exemple makeSolid() n'a rien à faire dans ma classe TileMap si celle-ci se charge déjà de charger la map visuellement ? Il faudrait rajouter une classe TileMap logique dans laquelle je récupère directement la TileMap graphique et sur laquelle je travaille, vérifie les collisions etc, c'est bien cela ?

            Et donc cela rejoint ce que tu me dis pour le Sprite il faut absolument éviter de faire hériter directement une classe qui est est déjà un objet Drawable (par exemple) avec une classe représentant des entités physiques ?

            Merci beaucoup :)

            -
            Edité par AmirProg 7 octobre 2020 à 10:55:12

            • Partager sur Facebook
            • Partager sur Twitter
            Anonyme
              7 octobre 2020 à 15:39:45

              AmirProg a écrit:

              Rebonjour merci de prendre de nouveau le temps de me répondre  !

              J'ai souvent lu un peu partout qu'il fallait effectivement séparer l'affichage de la logique, mais je ne suis pas sur de bien comprendre. Finalement ce que tu me dis c'est qu'une méthode, par exemple makeSolid() n'a rien à faire dans ma classe TileMap si celle-ci se charge déjà de charger la map visuellement ? Il faudrait rajouter une classe TileMap logique dans laquelle je récupère directement la TileMap graphique et sur laquelle je travaille, vérifie les collisions etc, c'est bien cela ?

              Et donc cela rejoint ce que tu me dis pour le Sprite il faut absolument éviter de faire hériter directement une classe qui est est déjà un objet Drawable (par exemple) avec une classe représentant des entités physiques ?

              Merci beaucoup :)

              -
              Edité par AmirProg il y a environ 4 heures

              Oui c'est ça ^^
              • Partager sur Facebook
              • Partager sur Twitter

              Questions pour bien organiser mon moteur de jeu

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
              • Editeur
              • Markdown