26-04-2013, 07:39 PM
(Modification du message : 27-04-2013, 10:26 AM par Sephi-Chan.)
Je pense (n'ayant lancé aucun jeu, ça reste ma théorie) que pour créer un jeu viable, il faut — comme pour n'importe quel système informatique — essayer de découpler au plus les modules en composants indépendants.
Le soucis, c'est que ce concept de module est très abstrait et peu avoir différentes formes selon les technologies utilisées.
En prenant l'exemple de mon projet Seelies : une jeu de stratégie asynchrone joué en parties indépendantes (avec environ 5 à 10 équipes de 10 joueurs par partie), j'ai isolé un certain nombre de modules "métiers" :
Techniquement, voici la stack que j'ai prévu d'utiliser :
Pour implémenter tout ça, j'ai choisir d'utiliser pour chaque module métier une application dédiée qui tourne sous la forme d'un daemon.
Pas mal de jeux Web (surtout dans le milieu amateur) sont juste des applications Web, mais c'est souvent un très mauvais choix technique car il pousse à tout faire dans les requêtes requêtes HTTP, ce qui pose plein de problèmes (notamment sur les performances et les accès concurrents à la base). Moi, j'ai choisi d'utiliser l'application Web comme une simple interface avec le joueur.
Pour se parler, les applications utilisent un outil de messaging, RabbitMQ : chaque composant écoute sur une ou plusieurs queues et effectue un traitement à chaque fois qu'un message arrive.
Voici par exemple ce qui se passe dans le scénario que j'ai décrit plus haut :
Voilà, j'espère que cet exemple permettra aux gens à comprendre comment on peut créer une application complexe en la séparant en modules faciles à gérer indépendemment.
L'autre avantage est que chaque module a son propre code et qu'on peut donc les tester facilement et rapidement, et les mettre à jour sans avoir à relancer tout (si on coupe un module, les messages RabbitMQ lui seront envoyés quand il sera relancé).
Bien sûr, on peut partager certains bouts de code (les classes, par exemple), notamment grâce aux systèmes de sous-modules des outils de versionnement comme Git.
PS : on parle plus d'architecture que de structure.
Le soucis, c'est que ce concept de module est très abstrait et peu avoir différentes formes selon les technologies utilisées.
En prenant l'exemple de mon projet Seelies : une jeu de stratégie asynchrone joué en parties indépendantes (avec environ 5 à 10 équipes de 10 joueurs par partie), j'ai isolé un certain nombre de modules "métiers" :
- Un module de match making, qui se charge de garder une trace des invitations des joueurs entre eux pour former des équipes. A invite B et C pour joueur une partie en 2 équipes de 3, ils acceptent tous et forment donc l'équipe 1. D a invité E et F mais F a refusé, puis G a invité H et I qui ont accepté, formant donc l'équipe 2. Le système lance donc une partie qui oppose les équpes 1 et 2).
- Un module de scoring/ranking, qui se charge de faire des stats sur les équipes pour maintenir un tableau de scores ;
- Un module d'achievements, qui se charge de suivre les "succès/médailles/hauts faits" effectués par les joueurs ;
- Un module de gestion des parties, qui se charge de la résolution des tours de jeux, des déplacements des unités, etc ;
Techniquement, voici la stack que j'ai prévu d'utiliser :
- Une application Web (Nginx + Ruby on Rails + Backbone) qui sert les pages Web et qui dispatch les requêtes aux autres modules ;
- Un serveur de base de données (MongoDB) ;
- Un serveur de messaging (RabbitMQ), qui permet aux différents modules de parler entre eux ;
- Un serveur de communication (Faye + Sengrind), qui se charge d'envoyer les messages aux joueurs (en push ou par mail, selon les messages et selon que le joueur est connecté ou non) ;
Pour implémenter tout ça, j'ai choisir d'utiliser pour chaque module métier une application dédiée qui tourne sous la forme d'un daemon.
Pas mal de jeux Web (surtout dans le milieu amateur) sont juste des applications Web, mais c'est souvent un très mauvais choix technique car il pousse à tout faire dans les requêtes requêtes HTTP, ce qui pose plein de problèmes (notamment sur les performances et les accès concurrents à la base). Moi, j'ai choisi d'utiliser l'application Web comme une simple interface avec le joueur.
Pour se parler, les applications utilisent un outil de messaging, RabbitMQ : chaque composant écoute sur une ou plusieurs queues et effectue un traitement à chaque fois qu'un message arrive.
Voici par exemple ce qui se passe dans le scénario que j'ai décrit plus haut :
- Le joueur A se connecte sur le site de Seelies (l'application Web riche en Javascript, propulsée par Ruby on Rails), il se connecte à son compte (stocké dans la base MongoDB), il arrive sur la page qui liste les parties dans lesquelles il joue (toujours en requêtant la base MongoDB) ;
- Il veut jouer avec ses amis B et C dans une petite partie (au format 2 équipes de 3 joueurs), il leur envoie donc une invitation. L'application Web envoie un message dans RabbitMQ sur la queue
match_maker.new_invitation
avec un peu de données encodées en JSON, disons{ sender: 'A', other_players: [ 'B', 'C' ], format: { teams: 2, players_per_team: 3 } }
;
- Le module de match making reçoit ces messages : il va chercher les informations sur les joueurs dans la base MongoDB pour savoir s'ils ont bien le droit d'être invités par ce joueur (par exemple si un joueur en a "bloqué" un autre, c'est là qu'on coupe l'interaction). Il enregistre dans MongoDB la demande d'invitation et envoie des messages dans RabbitMQ pour notifier au joueur A que ses invitations ont bien été traitées et aux joueurs B et C qu'ils sont invités à jouer ;
- Le serveur de communication reçoit ces messages : il sait que A doit être notifié que ses invitations ont bien été envoyées, on lui envoie un push (en push Websocket via Faye) pour lui répondre. Il doit aussi notifier à B et C qu'ils sont invités par A ; comme B est en ligne, il lui envoie aussi sous forme de push pour que le navigateur affiche une popup pour qu'il accepte ou décline l'invitation (ce qui engendrera l'envoi et le traitement de nouveaux messages). C est hors ligne, donc on lui notifie l'invitation via un mail : il y répondra en répondant au mail ou en se connectant au site.
- etc.
Voilà, j'espère que cet exemple permettra aux gens à comprendre comment on peut créer une application complexe en la séparant en modules faciles à gérer indépendemment.
L'autre avantage est que chaque module a son propre code et qu'on peut donc les tester facilement et rapidement, et les mettre à jour sans avoir à relancer tout (si on coupe un module, les messages RabbitMQ lui seront envoyés quand il sera relancé).
Bien sûr, on peut partager certains bouts de code (les classes, par exemple), notamment grâce aux systèmes de sous-modules des outils de versionnement comme Git.
PS : on parle plus d'architecture que de structure.