21 juin 2007

Quelques informations sur l'architecture des Jeux Vidéo

Avant de continuer notre série sur l’exploration du Framework XNA, je voudrais discuter de quelques principes d’architecture logicielle appliquée aux jeux vidéo.

Architecture Générale

Comme je l’ai expliqué dans le second article de la série, un jeu vidéo est basé sur un pattern simple : l’utilisation d’une boucle principale de capture des entrées, mise à jour du modèle, et rendu. Trois systèmes se côtoient donc : le modèle, la gestion des entrées utilisateur (les contrôleurs) et les systèmes de rendu audio ou vidéo (les vues). On a là une triade MVC classique – mais nous n’avons pas besoin de liens complexes entres ces deux modules.

Il ne sert par exemple à rien que le contrôleur soit capable - via des observateurs par exemple - de modifier directement le modèle. Bien souvent, la complexité des modifications est telle que cette méthode est en fait très loin d’être optimale. La raison est que le modèle est lui-même très complexe. Imaginez par exemple un monde 3D entièrement déformable, ou chaque bâtiment peut être détruit, et ou les règles du monde physique sont respectées - une seule action de l’utilisateur peut entraîner des calculs couteux. En mettant en place un mécanisme de mise à jour automatique en fonction de l’entrée utilisateur, on se prive de la possibilité de regrouper certains calculs, et donc de les accélérer. Or la rapidité d’affichage est primordiale dans un jeu vidéo - on parle souvent de temps réel, mais la notion qu’on devrait utiliser est en fait celle de temps interactif.

Quoi qu’il en soit, les liens entre les composantes de ce modèle applicatif subsistent, si ce n’est qu’ils sont légèrement différents de ceux présentés dans le GoF[1]. Ainsi, les Vues (vidéo, son) ont toujours besoin de connaître le Modèle, de même que le ou les Contrôleurs. Par contre, même si il est tout à fait possible de l’envisager, le Modèle n’a pas besoin d’être le sujet d’observeurs de Vues (la principale raison pour laquelle les Vues n’ont pas besoin d’observer le Modèle est que la mise à jour des Vues est une opération globale, qui peut être effectuée en séquence après la mise à jour du Modèle), de même qu’il n’a pas besoin d’observer le ou les Contrôleurs.

En substance, le pseudo-code d’un jeu ressemble à celui-ci :

function game_loop
{
  while (the user don’t quit)
  {
    controlers.read(model); // apply user input on the model
    model.update(); // update object state and physics
    view.render(model) ;
  }
}

Ce pseudo-code est (relativement) valide, même aujourd’hui – et ce, bien qu’il soit mono-thread. Il est évident que les architectures matérielles multi-core changent la donne (sans toutefois la changer de manière drastique) - il convient d’adopter une stratégie de multithreading efficace afin de profiter au maximum de la puissance offerte par ces architectures matérielle.

La stratégie que je trouve la plus élégante a été publiée dans le Game Developer Magazine puis mise en ligne sur gamasutra.com. L’idée des auteurs Henry Gabb et Adam Lake est de réserver un thread par module. Ces threads fonctionnent en cascade, de la manière suivante :

  • Le thread Contrôleur lit les entrées utilisateur, et les stocke dans un buffer.
  • Le ou les threads qui mettent à jour le Modèle récupèrent les dernières données du thread Contrôleur (la récupération des données nettoie le tampon). La mise à jour du modèle effectuée, une version est exportée par ces threads vers le pool de rendu.
  • Les threads de Vue récupèrent la dernière version à jour du Modèle et effectuent le rendu.

A aucun moment les threads ne se bloquent, si ce n’est lorsqu’un thread souhaite avoir accès à une donnée partagée - soit en lecture, soit en écriture. En pratique, cela arrive rarement. Le principal avantage de cette stratégie est que le rendu est effectué le plus rapidement possible, sans attendre les mises à jour successives du modèle. On garde donc une certaine responsivité de l’application, même si l’opération de mise à jour du Modèle est onéreuse en temps CPU.

La mise à jour du Modèle peut elle aussi être séparée en plusieurs threads qui fonctionnent sur un principe similaire. On peut par exemple imaginer un thread de gestion de l’intelligence artificielle, un thread de mise à jour du modèle physique, etc.

Threads d'un moteur de jeux vidéo

Gestion des écrans

Un autre patron de conception est lui aussi très souvent utilisé pour gérer les différents modes du jeu : le patron State Machine.

La machine d’état a pour but de coordonner les différents « écrans de jeu ». Ainsi, dans un jeu de plateforme classique, on aura le plus souvent un ou plusieurs écran de menu, un écran de jeu, un écran de présentation des meilleurs scores, un ou plusieurs écran d’options et un écran de crédits, qui affiche le nom et la responsabilité de tous les développeurs et artistes ayant participé au projet (cette notion est importante dans l’industrie du jeu vidéo, car être crédité dans un jeu vidéo est la preuve que vos compétences ont aidé à la création de ce jeu).

En soit, chaque état-écran ressemble à une mini-application. Le plus souvent, un graphe d’états est créé afin de passer aisément d’un état à un autre, les transitions étant déclenchées par des actions utilisateur – il s’agit donc d’un véritable automate, à gérer comme tel. L’image suivante présente un exemple de machine d’états pour le jeu CuteWorld.

Graphe des états du jeu CuteWorld

On le voit, malgré la simplicité du jeu, le nombre d’états à gérer est quand même relativement élevé. Cela justifie donc la mise en place d’une architecture suffisamment versatile pour ne pas se retrouver bloqué en cours de développement par l’implémentation d’un état ou d’une transition particulière.

De manière intéressante, les machines d’états sont très souvent utilisées dans les jeux vidéo à des fins différentes. Par exemple, pour gérer le comportement des personnages contrôlés par le jeu – il s’agit dès lors d’une forme d’intelligence artificielle très simplifiée (les IA des jeux vidéo sont très souvent extrêmement simples ; au point que je ne suis même pas sûr qu’elles méritent la dénomination d’IA – les termes de gestion automatisée du comportement sont peut être plus précis). Un exemple parmi d’autres : l’article State-Driven Game Agent Design de Mat Bukland (aussi disponible dans le livre Programming Game AI by Example, et dont vous trouverez une implémentation en C# ici). Pour en savoir plus sur l’implémentation de ce type de gestion automatisée du comportement, je vous conseille la lecture prolongée de ai-depot.com.

Note

[1] E. Gamma, R. Helm, R. Johnson, J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, (amazon.com)

Commentaires

1. Le dimanche, janvier 17 2010, 11:35 par Jeux

Malgré la complexité du sujet, votre article a le mérite d'être clair et accessible. Très bon article et bonne continuation pour la suite.

2. Le dimanche, janvier 17 2010, 18:37 par Emmanuel Deloget

Et bien, que dire à part "merci" ? En tout cas, j'essaierais de continuer sur cette lancée.

3. Le vendredi, février 25 2011, 12:00 par pk

Je suis d'accord avec vous, je suis ravi d'avoir attéri sur votre blog. Bonne continuation et longue vie à votre blog !

4. Le vendredi, février 25 2011, 13:55 par Emmanuel Deloget

Alors là, je le laisse (j'ai juste enlevé le lien sur le nom de l'utilisateur), parce que c'est du Spam Magnifique.

Jugez plutôt :

1) message générique, mais de remerciement
2) adresse email (que vous ne voyez pas) avec un prénom féminin
3) le site web est un lien vers un profil sur un forum
4) le profil sur le forum est tout ce qu'il y a de plus soft, même si l'adresse email est différente de la précédente.
5) dans la signature de l'utilisateur, on a un lien vers un site de poker gratuit.

Discret, et ça demande à visiter toute la chaine pour voir qu'en fait, il s'agit bien de spam.

Bien sûr, sur un site comme le mien ou je reçoit de 2 à 6 commentaires par mois, remonter la chaine n'est pas particulièrement complexe. Pour un site recevant 10, 20 , 100 commentaires par jour, c'est tout bonnement impossible. Le spam reste longtemps avant qu'il ne soit détecté et supprimé.

C'est bien joué :)

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

Fil des commentaires de ce billet