Simple Game Framework

Un framework C++ basé sur la SFML

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

Bonjour à tous, je me présente, LeB0ucEtMistere, aka Arthur D. Je suis actuellement dans des études de mathématiques (prépa maths) et je développe en C++ majoritairement pendant mon temps libre.

Je viens vous présenter mon nouveau projet, en maturation depuis quelques années déjà : Le Simple Game Framework (SGF pour les intimes)

En savoir plus sur le projet

Genèse

Après plusieurs années de programmation et une flopée de programmes plus ou moins avortés, j'ai eu comme tout le monde envie de ma lancer dans le développement de mon jeu vidéo. Et là, comme beaucoup ça a été le drame, à plusieurs reprises ! Mais j'ai pris de la bouteille et j'ai surtout réalisé que faire un bon jeu, ça demande une team de passionnés, du temps (beaucoup) et des outils. J'ai donc (momentanément) laissé tombé l'idée de créer mon jeu pour me lancer corps et âme, ou presque, dans ce projet : créer des outils utiles à la plus grande majorité souhaitant développer un jeu vidéo en C++, en se basant sur la librairie SFML.

Il faut savoir que je ne souhaite pas créer un moteur de jeux à part entière. J'ai donc choisi de me baser sur la SFML. Cependant, après de nombreuses années passées à traîner sur le forum C++ d'OC (oui oui, et j'y suis encore ^^), j'ai constaté que de nombreux débutants (moi y compris) doivent à chaque fois réinventer la roue : gestion des ressources externes (images, textures, musiques, …), gestion de la boucle principale de jeu, import de maps, création d'un ECS, création d'algorithmes récurrents (pathfinding notamment). J'ai donc eu (comme beaucoup avant moi, certes) l'idée de regrouper ces fonctions au sein d'un framework.

Généralités et avancement

Le projet est développé en C++, autour de la librairie SFML. Il sera à terme composé d'un noyau solide comprenant un ECS et un "moteur" de jeu basique (boucle principale, events, statemachine, …). Et autour de ce corps viendront s'adjoindre des modules complémentaires. On pourra imaginer une GUI, un passeur de Lua, un module de scripting, etc …

Actuellement, le projet est en pré-alpha : entendez par là que seul le noyau est commencé et à peu près fonctionnel. Je précise dès maintenant que mes études me prennent beaucoup de temps et que le projet n'avance que pendant les vacances scolaires, et relativement lentement. Je n'ai pas d'échéancier et ne compte pas en avoir ! Certes le projet contient encore très peu de features et tout peut être amené à changer dans les mois qui suivent, mais je suis déjà dessus depuis maintenant 1 an et j'ai envie de partager mon expérience avec la communauté. De plus, j'ai actuellement besoin de retours sur ce que j'ai déjà fait, et j'aimerai avoir votre avis sur plusieurs questions que je me pose quant au développement de certaines features.

Features actuelles (rien n'est 100% fini of course mais ce qui est listé ici est fonctionnel) :
  • ECS
  • Machine à états
  • Boucle de jeu principale
  • Gestionnaire de ressources externes
  • Spécialisation du gestionnaire pour les classes de bases de la SFML
  • Logger paramètrable
  • Système d'exceptions
  • Tout début de GUI (pour faire des tests)
Je compte prochainement développer (par ordre de priorité) :
  • un système de message pour que les systèmes de l'ECS puissent interagir.
  • refonte du pipeline de rendu : je ne suis pas du tout heureux de ce qui est fait actuellement, pas assez flexible…
  • rafraichissement du code des ressource managers, trop verbeux pour l'utilisateur à mon goût
  • optimisation de l'ECS : pool d'entité et autres
  • documentation du code

Objectifs

L'objectif numéro un du framework est comme son nom l'indique la simplicité d'utilisation. Il s'adresse principalement aux codeurs débutants dans le monde du jeu vidéo et aux amateurs développant pour leur plaisir. Il n'a strictement aucune visée commerciale. Je veux juste fournir un outil performant et agréable à utiliser. Ce framework ne sera probablement pas un foudre de guerre (du moins surement pas tout de suite) mais je ne compte pas non plus faire de trop gros compromis sur la qualité. Il doit être capable de réaliser un jeu performant !

Je vise pour mon projet une optique d'expansion. A savoir que lorsque la première release stable sera sortie, j'aimerai encourager les développeurs intéressés par mon projet à prendre part à son développement en créant des modules. Nous reviendront à cette idée de modules par la suite. Ce projet devra rester à dimension humaines, pas de machine à gaz ! Ça irait à l'encontre de l'essence même du projet d'ailleurs.

Le projet et son originalité

Comme dit ci-dessus, ce projet vise les amateurs et non pas le commerce. Toutes les sources seront mis sous licence GPL, et seront accessibles.

De plus, j'ai l'intention de rendre le projet modulaire. Comme je le disais plus tôt, je souhaite mettre en place une interface pour que les users puissent facilement ajouter et proposer leurs propres modules, toujours dans une idée de collaboration. Dès que la première release stable sortira, je commencerai le développement de ces genres de modules et pourquoi pas, intégrerai vos propres modules si vous m'en proposez des pertinents. J'ai notamment sur ma to-do liste : les algos de pathfinding, les algos de bruits, un parseur de TMX files, un module de scripting, etc …

Le mot de la fin

Voilà donc le repository GitHub contenant mon code. (pour l'instant, seul la branche develop contient du code) Je remercie tous ceux qui ont pris la peine de me lire jusqu'au bout et j'attends vos retours d'une part sur le concept, et d'autre part sur le code ! J'essaye de produire un code propre et moderne mais je reste un autodidacte et je sais qu'il y a ici des gens bien plus compétents à même de me conseiller. J'en profite d'ailleurs pour remercier tous les gens du forum C++ d'OC qui m'ont permis d'arriver jusqu'ici : entre autres Kssas`Peuk, Jo_Link_Noir, gbdivers, lmghs, bacelar, et j'en oublie surement mais j'ai une pensée pour vous tous :) Enfin, ce post sera tenu à jour au fur et à mesure de mes avancées, je me servirai aussi du fil comme dev-blog.

PS : punaise ça fait du bien un éditeur markdown qui fonctionne bien ! (cc @OpenClassrooms, ne te sens pas visé)

Édité par LeB0ucEtMistere

+3 -0

Salut LeB0ucEtMistere, Ton projet est intéressant, même s'il doit probablement exister quelque chose de semblable, comme tu le dit, il n'a pas particulièrement de grande originalité. Ce qui rend ce projet intéressant à mes yeux, c'est d'un point de vue "pédagogique" ^^ Je cherche des idées de projet pour approfondir ma connaissance du C++, je vais donc suivre ce projet encore à son début et donc facile à apprendre :D

Le premier conseil que j'aimerai te donner, avant de continuer, ce qui est indispensable pour un projet open source, c'est de la documentation avec le code. J'ai regardé le code source, et il n'y a aucun commentaire "utile" j'entends pas là, des commentaires spécifiant l'utilisation, et l'utilité d'une classe, d'une méthode… Ce qui rend l'ensemble assez difficile à comprendre.

Si ton projet avance bien, je pense essayer de me lancer dans un projet parallèle au tien, à savoir un petit jeu qui utiliserait ton framework, une sorte de jeu démo du framework :)

Bonne continuation

Shellbash

Édité par Shellbash

+0 -0
Auteur du sujet

Hello : ) Tout d'abord merci d'avoir pris le temps de répondre. Pour la doc, j'en suis tout à fait conscient ! Le souci c'est que comme tout ce qui est ici est potentiellement amené à changer (pas plus tard que cet aprem, j'ai remis en cause l'organisation entière de mes components .... ^^) je ne vais pas documenter ce qui risque de changer, du moins pas avant la première alpha release utilisable ;) Cependant j'écris du code de test dans le dossier examples (notamment dans le fichier introState.cpp qui contient tout mon code de test) et qui pourra t'aiguiller dans l'apréhension des mécanismes du framework. Après, je vais essayer de prendre le temps de vous pondre 1 ou 2 exemples de code pour ce que je n'ai pas l'intention de modifier actuellement ;) Pas de souci, ça me ferais très plaisir de voir ce que mon projet peut donner, et surtout ça me permettrai d'avoir un retour user plus sérieux que mes petits tests à l'arrache ^^ Bonne continuation à toi !

PS : n'hésite pas à clone le dépôt si tu veux bosser avec mon code, actuellement le state manager est fonctionnel, seul l'ECS risque de vraiment changer rapidement ;) Comme ça tu pourras aussi me dire si tu aimes la syntaxe d'utilisation ^^ mon avis seul n'est surement pas le plus pertinent !

+0 -0

J'ai bien conscience que le code va changer, et heureusement ^^ mais mettre un petit commentaire d'une ligne ou deux à chaque fois que tu commence à écrire une classe ou une méthode, ça prend pas beaucoup de temps, et ça aide vraiment, même pour toi ;) C'est une habitude à prendre :)

Et avant de me lancer dans ce projet parallèle, je vais attendre un peu que le projet soit un peu plus stable :)

+0 -0
Auteur du sujet

Hello :) Petit post pour vous annoncer que j'ai revu la rendre pipeline, en ajoutant une classe perso sgf::Window, qui fournit la majorité des fonctionnalité d'une sf::RenderWindow, et qui ajoute en plus la gestion des layers de dessin. Du coup, on peut maintenant dessiner de partout dans le code en choisissant ses layers :) ! plus de souci de background qui masque autre chose si dessiné avant ! Bon je ne push pas ces modifs tout de suite sur le github parce que je bosse sur autre chose, mais ça ne va pas tarder ! A plus.

+1 -0
Auteur du sujet

J'avais promis de vous pondre un bout de code donc voilà : Instanciation d'un MovementSystem

 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
class MovementSystem : public sgf::System<PositionComponent> //nécessitera des entités possédant un PositionComponent
{

public:
    MovementSystem(sgf::World &world): sgf::System<PositionComponent>(world), speed(100)
    {}
    void run(sf::Time const& elapsed) override
    {
    auto time=elapsed.asSeconds();
        for(auto &it: _watchedEntity)
        {
            On récupère les données du composant
            auto &data= it->second.getComponent<PositionComponent>("pos")._data;

            //on fait se déplacer l'entité
            data.x+=time*speed;
            data.y+=time*speed;

        }
    }

private:
    float speed;

};

La création d'une entité et d'un système :

1
2
3
4
5
6
7
8
std::unique_ptr<sgf::Entity> entity(sgf::make_unique<sgf::Entity>(12));
entity->addComponent<PositionComponent>("pos", 200,200);

world.registerEntity(entity);

auto mov=std::make_unique<MovementSystem>(world);
world.addSystem(*mov);
_systems.push_back(std::move(mov));

Et le dessin d'images/élements de gui dans la fonction draw de la main loop :

1
2
3
4
5
6
7
8
9
void IntroState::Draw(sgf::Window& window)
{
    window.draw(_spriteLoader.getRessource("bckg_image" ),sgf::Layer::Background);
    window.draw(jouer.getCurrentSprite(),sgf::Layer::Foreground);
    window.draw(reglages.getCurrentSprite(),sgf::Layer::Foreground);
    window.draw(quitter.getCurrentSprite(),sgf::Layer::Foreground);
    window.draw(_spriteLoader.getRessource("gui_title" ),sgf::Layer::Foreground);

}

Où l'on voit l'utilisation des layers. Il en existe actuellement 3 (ce qui changera par la suite, avec la possibilité pour l'user de créer ses layers, mais pas maintenant) Background, Middle, Foreground Par défaut, un appel à sgf::Window::draw() dessine dans le Middle. Du coup, avec ce code j'ai pu dessiner dans un autre point du programme des entités (via un rendez system) en choisissant de les dessiner au dessus du background. Voilà ce que ça donne (petit test !) avec le rond rouge qui est une entité qui se déplace et qui passe sous les éléments de GUI : Image(un peu grande pour mettre ici)

Voilà voilà, si vous avez des remarques sur la syntaxe c'est le moment de les partager ! ;) Bonne journée

+2 -0
Auteur du sujet

Hello :) Je viens avec des bonnes nouvelles ! J'ai passé la journée à mettre en place le système de messaging. Je tiens à dire que je me suis honteusement inspiré de DrDobbs, en changeant quelques petits trucs pour l'adapter à mon projet. Et là, j'ai actuellement quelque chose de fonctionnel sous les yeux :) bon la syntaxe d'utilisation est peut être un poil lourde, mais pour l'instant ce système me va très bien :p ça marche sur le traditionnel couple EventEmitter-Listeners. Voilà, je vais donc clean un peu le code d'exemple (qui devient de plus en plus sale), clean mon git local, et push d'ici peu toutes les nouvelles modifs. Je pense que ça sera pour samedi. Après je me pencherai sur mon problème le plus embêtant qui m'empêche de passer en release alpha maintenant … Bonne soirée.

LeB0ucEtMistere

PS : Si tout va bien samedi y'aura une petite vidéo de mon projet d'exemple pour vous montrer ce qu'on peut faire actuellement ;)

+2 -0
Auteur du sujet

Bonjour à tous

Le projet a avancé durant les derniers jours et l'ECS devient peu à peu fonctionnel. Il y a maintenant un système de messaging fonctionnel basé sur un design de dispatching. Je vous remet le lien du github. Et comme je vous l'ai promis, un peu de code relatif à ce nouvel et non des moindres ajout !

Donc pour l'exemple on va créer un système de diffusion de messages de type PhysicEvent

 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
enum Side
{
    top,bot,right,left
};

// PHYSIC EVENT //

//Structure de base du listener, qui va proposer des méthodes que l'on peut assimiler à des SLOTS.
//Tout ce qui veut recevoir des messages PhysicEvent devra en hériter et implémenter ces "SLOTS"
struct PhysicEventListener
{
    virtual void bounced(sgf::Entity& entity, Side where) = 0;
};

//Le contexte va contenir toutes les données que l'on souhaite joindre à l'émission du message :
//d'où il vient, ses spécificités, l'entité concernée, ...
struct PhysicEventContext
{
    enum pWhat {bounced} _whatHappenned;
    sgf::Entity* _entity;
    Side _where;
};

//Ici on définit le type qui sera source de ces messages :
//il prend le type de listeners, le type de context, et en facultatif un mutex et un conteneur différent pour le back end
//Tout ce qui veut envoyer des messages PhysicEvent devra en hériter

using PhysicEventSource = sgf::Messaging<PhysicEventListener,PhysicEventContext>;

Il va ensuite falloir faire hériter nos systèmes :

Le système Listener :

 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
class MovementSystem : public sgf::System<PositionComponent>, public PhysicEventListener
//on hérite de PhysicEventListener pour implémenter les "SLOTS"
{

public:
    MovementSystem(sgf::World &world): sgf::System<PositionComponent>(world)
    {}
    virtual void run(float elapsed) override
    {
    ...
    }
    virtual void bounced(sgf::Entity& entity, Side where) //appelé quand un PhysicEvent est lancé avec le type "bounced"
    {
        auto &data= entity.getComponent<PositionComponent>("pos")._data;

        switch (where) {
            case Side::top:
                data.speed.x*= -1;
                break;
            case Side::bot:
                data.speed.x*= -1;
                break;
            case Side::right:
                data.speed.y*= -1;
                break;
            case Side::left:
                data.speed.y*= -1;
                break;
            default:
                break;
        }
    }
};

Et le système source :

 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 PhysicSystem : public sgf::System<PositionComponent>, public PhysicEventSource
//on hérite de la source poru implémenter le dispatching
{
public:
    PhysicSystem(sgf::World &world): sgf::System<PositionComponent>(world)
    {}
    virtual ~PhysicSystem() = default;
    virtual void run(float elapsed) override
    {

        for(auto it=_watchedEntity.begin(); it!=_watchedEntity.end(); it++)
        {
            PhysicEventContext ctx; //Création du contexte qui contiendra les données
            ctx._entity = &it->second;
            auto &data= it->second.getComponent<PositionComponent>("pos")._data;
            if (data.speed.x > 0 && data.position.x>1920-150) //moving down and close to the bottom wall
            {
                //remplissage du contexte
                ctx._whatHappenned = PhysicEventContext::pWhat::bounced;
                ctx._where = Side::top;
                //on lance le message en passant le contexte
                raiseEvent(ctx);
            }
            else if (data.speed.x < 0 && data.position.x<150) //moving up and close to the top wall
            {
                ctx._whatHappenned = PhysicEventContext::pWhat::bounced;
                ctx._where = Side::bot;
                raiseEvent(ctx);
            }
            else if (data.speed.y > 0 && data.position.y>1080-150) //moving right and close to the right wall
            {
                ctx._whatHappenned = PhysicEventContext::pWhat::bounced;
                ctx._where = Side::right;
                raiseEvent(ctx);
            }
            else if (data.speed.y < 0 && data.position.y<150) //moving left and close to the left wall
            {
                ctx._whatHappenned = PhysicEventContext::pWhat::bounced;
                ctx._where = Side::left;
                raiseEvent(ctx);
            }

        }
    }
    void dispatchEvent(PhysicEventListener &p,
                       const PhysicEventContext &context) override
    //fonction centrale devant être implémentée par toute source, héritée de sgf::Messaging
    //elle permet de définir comment dispatch l'event en fonction du contexte, appeler tel ou tel slot par exemple chez les listeners
    {
        switch (context._whatHappenned) {
            case PhysicEventContext::pWhat::bounced:
                p.bounced(*context._entity,context._where);
                break;

            default:
                break;
        }
    }
};

Enfin on a plus qu'à enregistrer les listeners chez la source.

1
2
auto physics=std::make_unique<PhysicSystem>(world);
physics->addListener(*mov);

Et voilà le tour est joué :) dès que run() sera appelée dans le system émetteur, si nécessaire des events seront levés et transmis à tous les listeners via les lots !

N'hésitez pas à me dire ce que vous pensez de tout ça ! ;) Cet ajout rend l'ECS totalement fonctionnel !

Sinon je suis actuellement entrain de bosser sur le dernier gros problème qui m'empêche de release la première alpha et qui risque surtout de m'amener à repenser tout le code des components … ^^ Mais si tout ce passe bien vous pourrez bientôt définir des systems du type sgf::Systems<sf::Transformable> qui manageront tous les composants héritant de sf::Transformable ce qui simplifiera énormément l'utilisation des systems avec les libs telle que la SFML ;)

+3 -0
Auteur du sujet

Non non, juste en pause pendant mes études, je n'ai pas pour l'instant le temps de bosser dessus, mais je m'y remettrai surement pendant les grandes vacances, concours obligent ! En attendant je suis tjr actifs sur les forums et je lis les messages donc si vous avez des suggestions n'hésitez pas ;)

+1 -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