Design / Conception d'un "Game Engine"
Bonjour à tous, je suis ici car j’ai en tête depuis bien longtemps de faire un moteur de jeu 2D dans le but de pouvoir créer un RPG du même genre que secret of mana (avec un mode multi à 3 joueurs).
J’ai déjà tenté l’expérience et ca c’est soldé par un échec dû à un design mal pensé qui s’est révélé très difficile à finir. J’ai donc abandonné et repris récemment sur de nouvelles bases en m’inspirant d’un article intéressant trouvé sur gamedev.net . Mais là encore je me rends compte que je fonce droit dans le mur (la série d’article sur laquelle je comptais me baser n’est pas terminée et ne le sera pas, il manque donc de grande étapes conceptuelle que je ne suis pas parvenu à comblé).
Avant de continuer à coder tout et n’importe quoi j’ai décidé de m’arrêter et de réfléchir un peu à ce que je voulais obtenir. Dans un premier temps, je me suis fixer l’idée de faire un « pong » proprement, ce qui me permettra de poser une très grande partie des fonctionnalités de mon game engine . Ensuite je tenterai de faire une version multi-joueurs (2) de ce même pong. Et enfin je me lancerai sans doute dans la réalisation de mon projet initial.
J’ai commencé à sérieusement réfléchir à une architecture de classes me permettant d’avoir un design propre, flexible et surtout viable à terme. J’aimerai beaucoup avoir votre avis dessus, il n’est certainement pas parfait et peut-être même pas viable. Je suis ouvert à toute critique/remarque.
Après avoir tenté de modéliser tout ca, j’en suis venu à considérer le modèle suivant. (Je précise que je ne cherche pas à avoir un code 100% OO, ni même exploitant à fond les possibilités du c++, mais juste un code qui fonctionne en étant maintenable et simple).
Le modèle du « tout-singleton » (non, non ne partez pas !). J’ai tout d’abord voulu séparer la programmation en divers module, et je me suis vite heurté à un problème récurent : comment ces modules vont-ils communiquer entre eux ? La plupart ont besoin des données du jeu pour fonctionner, données que l’ont doit pouvoir rassembler en une classe de base « Entity » ( un menu, un décor, une carte, un objet, une interface … pouvant être considérer comme une Entity distinctes). Ne trouvant pas de moyen élégant de passer les données qu’il faut à chaque module quand il en a besoin, j’ai décidé de rendre ces données disponibles pour tous !
Premier point donc, un « EntityManager » en singleton (oui, je sais singleton dans ce cas équivaut juste a une grosse liste d’Entity déclarée en static …).
Ensuite, il nous faut un endroit où stocker des ressources bruts (image - texture, son - musique, fonts) [Si vous voyez d’autres types de ressources bruts, pouvant être utilisées par plusieurs Entity signalez le moi, car je suis certain que j’en oublie !]. Ca nous amène donc à un « RessourceManager » dont le but sera de charger les type de données énoncé précédemment et de renvoyez un pointeur a l’Entity demandant la ressource. On s’assure ainsi que les ressources ne sont pas chargé en plusieurs exemplaires.
Je n’ai pas pour le moment vraiment réfléchis à la durée de vie de ses ressources, c’est un point sur lequel il va falloir que je me penche, mais je pars du principe que ca peux attendre un peu et que mon design n’en sera pas trop pénaliser (arrêter moi si j’ai tord).
Pour le moment j’ai donc un endroit où stocker des Entity , qui peuvent être composés de ressources bruts et d’autres Entity sans trop de problème.
Petite parenthèse sur les « a côtés » : je dispose déjà d’un système de log qui me convient et d’un profiler externe si besoin. Enfin mon application est emballée dans deux classes. La première ayant juste pour but de garder un « main » clair et de charger des données nécessaire au bon lancement du moteur ( taille de la fenêtre, plein écran ou pas ….) et de lancer le noyau de jeu (Kernel).
Ce Kernel va initialiser certains Manager (Ressources, Entity par exemple) et contenir la boucle de jeu.
Avant d’attaquer cette boucle de jeu, des plus classiques, il me reste encore une ou deux classes à présenter.
L’ « InputManager », dont le rôle sera de récupérer l’état des touches clavier et de la souris à chaque frame. Etant un singleton, tout le monde y aura accès, notamment les Entity , c’est là que réside l’un des mécanismes que je compte utiliser au mieux, mais que j’ai bien du mal à mettre en place (surtout dans les autres parties de mon moteur) : les « Events ». A sa création en particulier ou durant sa vie, chaque Entity pourra enregistrer auprès de l’ InputManager des Events . Un Event sera sous la forme d’une petite classe/structure, contenant : un pointeur vers l’Entity émettrice, le nom de l’Event et une/plusieurs condition de déclenchement.
Ainsi en plus de récupérer l’état des Inputs (touche appuyé cette frame, touche appuyé, touche relâchée cette frame, touche relâchée, souris en X et en Y), l’ InputManager testera chacun des Event qu’on lui aura enregistrer et si d’eux voit ses conditions remplies il enverra à l’Entity dont le pointeur est contenu dans l’Event un message du nom de l’Event en question.
On a donc maintenant un moyen de traquer les inputs à notre convenance et de les envoyer à qui de droit (le seul cas pouvant poser problème est celui des menus de jeu qui peuvent se superposer au jeu actuel et donc il faut veillez a ce que les inputs leur soit attribué en priorité, je n’ai pas encore réfléchis au problème, si vous avez des idées, n’hésitez pas).
A cela on ajoute un SoundManager, singleton, qui va juste permettre des petites choses comme : jouer un son, pauser un son, arrêter un son, mettre en file d’attente un son, jouer un son en dégradé sonore, changer le volume sonore ect…
On peut enfin revenir à notre boucle de jeu (contenu dans le Kernel) qui sera faites ainsi :
- Appel à l’InputManager pour qu’il récupère les inputs, les stocke et envoie les Events dont les conditions sont remplies.
- Appel à une classe Timer, qui va calculer le temps écoulé, le temps total et 2-3 trucs dans se style.
-Appel d’une fonction Update () sur toute les Entity (accessible grâce au singleton : EntityManager), cette fonction va d’abord procéder au traitement de tout les Event venant de l’InputManager, puis va procéder a son update normal (IA, physique, animation etc. …)
- Appel à une classe Network, dont le comportement reste à définir, mais qui devra assez tôt dans la conception être pris en compte ! (surement dés le premier pong terminer, vous pensez que ce n’est pas trop tard ?)
- Appel à une tache : Render() qui va passer en revue chaque Entity et l’afficher à ‘écran si elle est visible.
-> on boucle depuis le début !
Voila l’idée principale ! A vous de me dire ce que vous en pensez
A cela, il reste quelques choses majeures que je n’ai pas résolu :
1) La communication entre Entity, j’aime bien l’idée de l’envoie de message, j’aimerai garder cela mais je ne sais pas trop comment.
2) Comment définir les actions à prendre quand on recoit un message, je me vois mal tout codé en dur, j’aimerai pouvoir définir ca dans des fichiers simplement (xml ou autre) vous avez des idées ? Un langage de script comme LUA ferait-il mon affaire ? (je demande car je n’ai jamais utilisé)
3) Toute la partie network …
Dans le cas du système de messages Input->Entity et Entity<->Entity, il est sans doute préférable de traiter un message dés qu’il est reçu, plutôt que périodiquement à chaque frame, non ?
Merci de m’avoir lu, désolé de la longueur du pavé, j’attends impatiemment vos remarques / conseils / critiques / encouragement ou autre ^^
Par ailleurs si connaissez un livre (fr / en) traitant de se sujet de façons vraiment intéressante, l’architecture même d’un moteur de jeu, abordable d’un point de vue technique (si possible basé sur du C++) je suis preneur !
PS : Ce message sera posté sur au moins deux forums différents, j’essaierai de faire le bilan des remarques d’un post sur l’autre !
Partager