Sur cette page
Bienvenue dans cette série d’articles dédiés à l’apprentissage de la programmation de jeux avec le Microsoft .Net Framework et le managed directX 9.0.
Ces articles sont d’abord destines aux développeurs débutants qui sont intéressés par le développement de jeux. Le but au final, étant de se faire plaisir en créant nos propres productions et en apprenant les rudiments de la programmation d’applications multimédia. La programmation de jeux d’une manière générale et la programmation DirectX utilise un ensemble de termes et de définitions techniques qui peuvent être difficiles à appréhender pour le débutant, il n’y a aucune inquiétude à avoir à ce propos. Nous progresserons tout au long de cet apprentissage de manière simple, en décodant chaque nouveau terme à leur apparition. Le développement de jeu demande aussi des connaissances mathématiques. Là encore, nous tenterons une approche progressive à l’aide de tutoriaux.
Le but de cette série d’article est de développer un jeu simple qui va illustrer les différentes parties d’un jeu commercial. Nous verrons ainsi comment créer des graphiques 3D évolués, comment interpréter et répondre aux entrées utilisateurs (clavier, souris, joystick), comment ajouter un environnement sonore à ses jeux, comment ajouter une durée de vie à ses applications par l’intermédiaire d’une intelligence artificielle, enfin nous verrons comment ajouter un rendu réel à ses productions (respect des contraintes physiques, générations de mondes virtuels…). Nous verrons aussi comment rendre un jeu jouable en réseau, et une série d’astuces pour optimiser les performances de ses productions multimédia. Tout au long de cet apprentissage nous profiterons enfin de l’avantage de l’OO (orienté objet) pour permettre un développement puissant, rapide et maintenable.
Outils
Avant de commencer à travailler, il nous faut bien évidemment parler des outils que nous allons utiliser.
L’outil le plus important est bien évidement l’environnement de développement. C’est avec lui que nous passerons le plus de temps, que ce soit pour développer ou pour débuguer. Bien entendu il nous faut là un outil puissant.
Visual Studio 2005 est la troisième version .Net de l’IDE de Microsoft. Visual Studio .Net 2005 se trouve décliné en plusieurs versions dites “express” pour les langages VB, C#, C++,J#, et pour les développements Web utilisant ASP.NET. Ces versions sont adaptées aux débutants et aux étudiants par leur simplicité … et par leur prix.
Vous pouvez télécharger une de ces versions à: http://www.microsoft.com/france/msdn/vstudio/express/default.mspx.
Le second outil (tout aussi important) pour créer des applications graphiques digne de ce nom se trouve être L’API (Application Programming Interface) elle-même. Sans une bonne API, il est très difficile d’accéder aux fonctionnalités matérielles de son PC. Ici l’API que nous utiliserons est celle fournie par DirectX. Elle permet de la création d’application multimédia puissante sur la plateforme Windows. Il est nécessaire de télécharger la dernière version du SDK DirectX. Faites bien attention à télécharger le SDK et non pas seulement le redistribuable (contenant que les dll nécessaire pour l’exécution d’un programme DirectX). Le SDK inclus de nombreux programmes d’exemples, de la documentation et quelques outils/goodies très utiles pour développer en DirectX.
Lorsqu’on développe un jeu il est souvent nécessaire de mettre la main à la pate pour modifier soi-même les diverses ressources (graphiques ou sonores) qu’on utilise. L’utilisation de Paint s’avère très fastidieuse pour ce genre d’opération. Essayez donc de vous procurer un outil digne de ce nom.
Au fur et à mesure de notre progression nous donnerons des liens vers des programmes de manipulation d’images ou de son sur le Web.
Pour terminer, vous devez savoir ou trouver de l’aide. Le meilleur emplacement pour cela reste sans conteste les newsgroups. On y trouve tous les passionnés qui y posent des questions ou y répondent mais aussi des MVP ou des employés de Microsoft. Les newsgroups les plus utiles pour nous seront :
On pourra se referrer de meme aux sites :
Quelle est la recette d’un jeu à success ?
Ma première expérience lié au monde de l’information date de 1981 sur un Sinclair ZX Spectrum. Les premières cinq années de ma vie de développeur ont été ainsi dédiées exclusivement à l’écriture et à la modification de jeux sur des Sinclair et plus tard sur des Commodore 64. Si l’évolution en termes de possibilité hardware et d’API de développement est incommensurable, la recette pour la création d’un grand jeu, reste la même.
Un jeu aujourd’hui représente un programme d’une grande complexité qui demande l’intervention d’un grand nombre de développeurs, de designers, de testeurs associés à un structure puissante pour gérer le tout. Les jeux dans leur production n’ont pas à pâlir face aux grosses productions commerciales et peuvent demander un coût de développement avoisinant plusieurs millions de dollars. Le retour du investissement peut par contre être énorme et rivaliser avec les grosses productions hollywoodiennes (Halo 2 a rapporté 100 millions de dollars le jour de sa sortie) !
Tous les jeux doivent répondre à un ensemble de critère pour devenir un succès.
- L’ingrédient principal pour la réussite reste sans conteste l’idée générale et le scenario. Même si le jeu vient avec des graphiques sublimes et une maniabilité parfaite, sans idée, sa durée de vie ne tiendra pas la route et personne n’y jouera.
- Le deuxième ingrédient à succes est la jouabilité. Si le jeu est trop dur, il lassera vite. S’il est trop facile aussi … Il est donc important de trouver un juste milieu ou de proposer des niveaux de difficultés.
- Vient ensuite le design et l’environnement du jeu. Il existe deux “game designer” renommés qui ont transformés des jeux simples en poules aux yeux d’or grâce à des idées simples mais très jouissives. Shigeru Miyamoto (la papa de Donkey Kong, Zelda, et Mario) est sans aucun doute le plus connu, Will Wright (Sim-everything) aussi. On peut trouver une conférence donnée par Miyamoto’s Game Developer’s Conference de 1999 et une interview récente de Wright sur le design de Spore. L’esprit de ces jeux exprimés par ces deux personnes est une bonne source d’inspiration pour les designer de tout bord. Pour terminer, jetez un œil à l’ouvrage “Theory of Fun for Game Design” de Raph Koster qui reste aussi une bonne référence.
- Les graphismes viennent ensuite en tant qu’ingrédient primodial de tout jeu. Ils doivent être bons pour servir au mieux le scenario, la jouabilité et le design du jeu sans être excessifs.
- Vient enfin la performance du jeu. Personne ne désire jouer à un jeu saccadé. L’ingrédient « graphique » et l’ingrédient « performance » sont très liés. Il est donc important de les doser de manière très intelligente. Plus les graphiques sont poussés, plus les performances sont en baisse. L’API utilisée pour développer son jeu est un paramètre primordial à prendre en compte. Il est important de comprendre que la course à l’optimisation n’est pas le meilleur chemin à prendre pour améliorer les performances de son jeu. Il est préférable de bien comprendre l’API utilisée, le fonctionnement du pipeline graphique, d’écrire un code clair, logique et bien conçu. Cela vaut bien mieux qu’une série de méthodes optimisées à outrance.
Si vous gardez en tête ces critères, dans cet ordre, vous pourrez être capable de créer un grand jeu. Des jeux simples construits sur une grande idée et une excellente jouabilité comme Tetris ont marqué l’histoire sans avoir de graphiques 3D poussés. Aujourd’hui encore, des jeux comme Gish démontrent ce qu’un développeur indépendant et créatif peut faire. De même si vous travaillez assez sur un pour mettre en lumière votre idée et votre but, vous pouvez intéresser un grand éditeur.
Notre idée de jeu
Maintenant que nous avons les clés pour créer un grand jeu, l’étape qui vient maintenant est d’expliciter le jeu que nous allons développer dans cette série d’articles.
Idée : Nous allons tricher pour notre jeu en reprenant l’idée de base du tout premier jeu 3D apparu: Battlezone sur Atari. Battlezone est un jeu de tir avec une vue de type first-Person où l’ont voit l’environnement 3D comme si nous nous trouvions à l’intérieur d’un tank. Le but du jeu est de détruire les tanks ennemis avant d’être détruit soi-même. Le terrain de jeu comprend des objets aléatoires derrières lesquels nous pouvons nous cacher. L’écran de jeu inclus un radar pour mettre en évidence notre position et celle des opposants ainsi que notre score courant.
Jouabilité : Le jeu commence doucement avec peu d’opposants sans grande intelligence mais évolue pour garder l’intérêt du joueur avec une intelligence des tanks ennemis accrue et une rapidité sans cesse croissante.
Graphisme : Là, mieux vaut se démarquer de la version originale du jeu. Battlezone sur Atari affichait juste ce qu’il faut pour s’imaginer dans un monde virtuel 3D (à l’époque il était difficile d’afficher un monde complexe sur un processeur 8-bits tournant a 1.5 mégahertz). Le monde affiché était donc en wire frames (fil de fer). Nous allons changer cela grâce à la magie du DirectX (et à 25 années de loi de Moore !).
Capture d’écran de Battlezone– On y voit un monde en wire frames avec des montagnes, des tanks et une magnifique lune en fil de fer pour un maximum de réalisme !
Performance : Le jeu à l’origine poussait les performances au maximum de ce que permettait le matériel à l’époque. Avec le matériel de base que nous offrent les PC aujourd’hui nous pouvons profiter de la puissance gagnée pour afficher un monde réaliste.
Maintenant que nous avons présenté notre jeu, l’étape suivante vise à résumer une présentation du jeu afin d’avoir une vue précise claire et facile à lire de ce que va être notre jeu. Nous devons savoir au minimum le type de jeu, ce que le joueur peut ou ne peut pas faire, et comment il interagit avec le jeu. Nous devons aussi expliciter le système de score employé et les conditions de victoire.
La présentation de notre jeu sera donc :
- Un jeu de tir avec vue à la première personne
- Le but est de détruire un maximum d’ennemis dans un minimum de temps.
- Le joueur peut se déplacer sur un terrain de jeu au sol de la même manière qu’un tank. Le tank ne vol pas et possède une vitesse constante.
- Il sera possible d’interagir avec le jeu à l’aide d’un clavier pour se déplacer et tirer. La souris sera utilisée pour interagir avec les menus, pour lancer ou arrêter le jeu. Nous n’utiliserons pas de joystick.
- Le score de jeu sera calculé sur la distance à partir duquel un tank ennemi a été détruit. Le score évolue avec le temps.
- Le jeu sera divisé en niveaux. Chacun aura un nombre prédéfini d’ennemis. Une fois que tous les ennemis sur le terrain de jeu d’un niveau ont été détruits, on passe au niveau suivant. Il n’y a pas de limites aux niveaux.
Maintenant nous sommes prêts à nous lancer dans le développement. En générale il est plus utile d’aller directement à l’essentiel plutôt que de passer du temps précieux à peaufiner des menus détails. Il s’agit là d’une approche itérative des développements.
Création du projet
La première étape commence par la creation d’une solution sous Visual Studio 2005.
- Selectionnez File | New | Project et choississez Windows Application. Dans le champ Name en bas du dialogue, remplacez le nom par défaut WindowsApplication1 par BattleTank2005 et validez par OK. Visual Studio créé maintenant une nouvelle solution pour nous nommée BattleTank2005qui contient un seul projet portant le même nom.
La première chose à faire est de renommer la classe existante par quelque chose de plus parlant. Le bon nommage de ses classes et la meilleure méthode de travail pour garder un code clair et maintenable. - Cliquez droit sur Form1 dans le Solution Explorer et sélectionnez Rename. Changez Form1.cs en GameEngine.cs. Visual Studio devrait renommer la classe sous jacente avec le même nom. Remplacez Form1 par GameEngine. Vous ne le regretterez pas.
Un autre moyen garder un projet clair est de s’assurer que les fichiers .cs ne contient qu’une définition (classe, structure, enum …) et que le nom du fichier correspond au type qu’il explicite.
Nous avons maintenant une solution qui compile … mais qui ne fait absolument rien. La Form principale n’a aucun bouton, aucun textbox, aucun label et ne permet aucune interaction. Dans le cadre du développement d’une application Winform standard, nous ajouterions des boutons pour les interactions, des textbox pour l’affichage d’information, mais pour un jeu le contenu de la fenêtre sera géré uniquement par l’API DirectX. La fenêtre de jeu sera identifiée par DirectX par l’intermédiaire de son handle (qui se trouve être un identifiant de fenêtre unique pour Windows). Ce handle identifiant notre Form permettra de paramétrer DirectX pour le dessin du contenu de notre jeu. Nous exploiterons en outre les événements liés à la fenêtre pour créer une boucle de jeu.
Ajouter une boucle de jeu
Un jeu ne fonctionne pas de la même manière qu’une application standard comme Microsoft Word. Word affiche un écran qui représente une page sensée reproduire l’environnement d’une machine à écrire. Word passe alors son temps à attendre les actions de l’utilisateur. Ces actions peuvent être, presser des touches au clavier ou cliquer sur les menus. Lorsque l’utilisateur ne fait rien, l’application ne fait rien non plus (en vérité Word travaille quand même un peu : il sauvegarde le contenu de notre page ou lance un processus de vérification de contenu). D’une manière générale, toutes les applications WinForm standards fonctionnent sur ce principe. Pour parler simplement, le processeur de la machine ne fonctionne que lorsque l’utilisateur interagit avec l’application.
Le fonctionnement des jeux est différent. L’écran de jeu a un besoin constant d’être mise à jour. Ceci plusieurs fois par secondes. L’impression rétinienne, qui représente la fusion entre deux images successives au niveau de l’œil, s’obtient a partir du moment où les images vues sont affichées successivement avec un espace temps d’environ 1/16ème de secondes. Bien sûr, cette donnée varie suivant l’illumination (une luminosité importante demande une fréquence d’actualisation, ou frame rate, conséquente) et suivant l’emplacement sur la rétine ou l’image est reçue » (une vision périphérique demande une fréquence plus haute). Si les films demandent 24 images par secondes (24 FPS), 30FPS sont souvent considérés comme la fréquence minimum pour un jeu vidéo, la plupart des jeux actions demandant même pas moins de 60FPS.
La boucle de jeu représente le point central de l’application par lequel passe tous les traitements. Ces traitements peuvent être liés à la gestion graphique/3D du jeu, lié aux calculs « physiques» du jeu, lié à l’IA, à l’exploitation des entrées utilisateur, ou tout simplement aux calculs de données en temps réelles comme le score (l’utilisation d’une gestion à base de thread aurait pu être un autre moyen, mais malgré la puissance du .Net Framework dans ce domaine, une boucle de jeu reste la solution la plus puissante pour répondre aux besoins de FPS).
Comment donc créer cette boucle de jeu ? La fenêtre que nous venons juste de créer possède un événement nommé « Paint ». Cet événement est appelé à chaque fois que la fenêtre nécessite d’être redessinée. Il intervient donc lorsqu’on agrandit une fenêtre, lorsqu’elle est cachée ou bien encore lorsqu’elle est déplacée.
La bonne compréhension du système événementiel dans le développement Winform est primordiale. Bien que l’événement soit « appelé » automatiquement, nous avons besoin de créer une méthode spéciale qui va être lié à cette événement afin de pouvoir l’intercepter et ainsi lui donner une réponse adéquate.
- A l’intérieure de la classe GameEngine ajoutez le code suivant après le constructeur.
Visual C#
protected override void OnPaint(PaintEventArgs e) { }Visual Basic
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) End Sub
Cette méthode sera appelée à chaque fois que l’événement Paint sera généré. Une chose manque encore à ce point de notre programme ; bien que le système Winform soit capable de générer automatiquement l’événement en cas de besoin de rafraichissement du contenu de notre fenêtre, nous n’avons pas de moyen pour concrétiser ce besoin. Winform est assez intelligent par exemple pour ne pas générer l’événement Paint lorsque l’utilisateur réduit la fenêtre : celle-ci étant cachée, pas besoin de mettre à jour son contenu. Il n’est donc pas possible de se fier à Winform pour créer 60 actualisations par secondes dans la mesure où ce dernier ne génère Paint que lorsque le besoin se fait sentir. Nous devons donc nous même générer cet événement afin de créer notre boucle.
Par chance nous pouvons activer cet événement par l’intermédiaire de la méthode Invalidate de la classe Form. Cette méthode génère l’événement Paint. Si nous appelons la méthode Invalidate dans la méthode OnPaint de notre classe nous aurons ainsi une boucle parfaite.
On peut se demander « mais pourquoi ne pas simplement créer une boucle avec un while(true) plutot que de s’embêter avec Paint ? ». La réponse est simple, utiliser une telle méthode reviendra à s’accaparer toutes les ressources de la machine pour les utiliser à l’intérieur de notre boucle. Les événements Windows lié à notre fenêtre ainsi qu’autres aux fenêtres, les processus des autres programmes seraient donc délaissés par le système au profit de notre boucle. Le système deviendrait instable et inutilisable. Il s’agit là d’un moyen radical peu efficace… L’utilisation de l’événement Paint permet de créer une boucle en utilisant le système événementiel Windows en laissant la gestion des ressources processus à notre système d’exploitation. Tout ce que nous faisons ici c’est de demander à l’intérieur de notre méthode de mise a jour, d’appeler à nouveau aussi tôt que possible cette méthode.
- A l’intérieur de la méthode OnPaint ajoutez l’instruction suivante:
Visual C#
Visual Basic
Terminé ! Notre boucle de jeu est prête. Il reste toutefois encore un problème. Seules une partie des mises a jour de contenu pour une fenêtre passent par la méthode OnPaint. Pour forcer tout besoin de rafraichissement a passer par OnPaint nous devons encore ajouter une ligne de code à notre application. Celle-ci sera ajoutée à l’intérieur du constructeur de notre classe afin de s’assurer qu’elle sera appelée avant toute mise à jour du contenu.
- A l’intérieur de la classe GameEngine ajoutez la ligne suivante au constructeur, juste après l’appel à la méthode InitializeComponent method.
Visual C#
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
Visual Basic
Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.Opaque, True)
Mettre la propertie ControlStyles avec la valeur AllPaintingInWmPaint nous assure que Windows utilise uniquement la méthode OnPaint pour redessiner l’écran. Le second paramètre indique que notre fenêtre ne devient pas transparente.
Le framework de base pour notre jeu est maintenant en place. Toutes les instructions liées au fonctionnement de notre jeu se trouveront à l’intérieur de la boucle de jeu.
A propos des timers
Une contrainte importante liée à ces boucles de jeu est de s’assurer qu’un jeu va s’exécuter à la même vitesse quelque soit l’ordinateur sur lequel on l’exécute. Sans contrôle, la boucle va s’exécuter plus vite sur une machine puissante que sur une machine standard, le tout dépend de la mémoire présente et de la puissance du CPU. Nous devons donc placer un contrôle qui va s’assurer que chaque “tour” de boucle s’exécute avec la même fréquence quelque soit la machine. Pour cela nous allons calculer le temps écoulé entre chaque frame et appliquer cette valeur à chacun de nos calculs.
La gestion du temps peut se faire de différente manière sous Windows :
- System.Windows.Forms.Timer: Il s’agit là du timer le plus couramment utilise dans le développement Windows. S’il est facile à utiliser, il ne possède qu’une granularité de 1/18ème de secondes. Elle n’est donc pas assez fin pour les besoin de notre jeu.
- timeGetTime: Cette Dll Windows fourni une résolution de 1 microseconde sur certaines versions de Windows et 5 les Windows NT. Inutilisable donc parce que variable… a moins d’ajouter dans nos calculs un paramètre lié a la version de l’OS présent. Trop fastidieux et instable à utiliser.
- Environment.TickCount: Cet appel (managé) renvoie le nombre de ticks correspondant au nombre de millisecondes passées depuis le lancement du système. Ceci semble parfait mais nous pouvons utiliser quelque chose de mieux.
- QueryPerformanceCounter: Il s’agit du time le plus couramment utilise dans le développement de jeu. Sa résolution est inférieure à 1 microseconde.
L’exploitation de ce dernier timer dans nos programmes n’est pas transparente, elle nécessite un appel à une Dll système nommée kernel32. Heureusement, le besoin d’un timer performant étant universel dans le développement de jeu et le SDK Direct propose une classe clé en main qui nous simplifiera la tache. Celle-ci se trouve dans le répertoire \Samples\Managed\Common du répertoire d’installation du SDK. La classe se nomme FrameworkTimer et se trouve dans le fichier dxmutmisc.cs. Les autres fichiers contiennent eux aussi de nombreuses fonctionnalités utiles que nous utiliserons tout au long de notre apprentissage.
Avant d’ajouter ce fichier, nous allons créer un répertoire séparé dans notre projet afin d’ordonner la solution de manière propre et organisée.
- Selectionnez Add | New Folder. Nommez le nouveau répertoire: DirectXSupport. C’est là que nous placerons toutes les classes d’aide au moment nous nous en aurons besoin.
Ajoutons maintenant le fichier à notre projet.
- Cliquez doit sur le répertoire DirectXSupport et selectionnez Add | Add Existing Item et allez dans le sous repertoire C:\Program Files\Microsoft DirectX 9.0 SDK (February 2005)\Samples\Managed\Common puis selectionnez le fichier dxmutmisc.cs.
Vous pouvez toujours à ce stade parcourir le contenu de ce fichier, il contient de nombreuses classes d’aide qui simplifient la vie du développeur.
La classe FrameworkTimer que nous allons utiliser se trouve dans un autre namespace que celui de notre classe principale. Afin d’éviter d’écrire“Microsoft.Samples.DirectX.UtilityToolkit.FrameworkTimer” a chacune de ses utilisation nous allons ajouter le namespace.
- Tout en haut du fichier GameEngine.cs au niveau des directives using ajoutez la ligne :
Visual C#
Microsoft.Samples.DirectX.UtilityToolkit;
Visual Basic
Microsoft.Samples.DirectX.UtilityToolkit
Nous devons maintenant sauvegarder dans une variable le temps écoulé depuis la dernière frame. Cette variable sera de type double et sera nommée deltaTime. L’utilisation d’un double à la place d’un int permet de ne pas tronquer la valeur et de permettre ainsi une granularité fine dans la gestion temporelle de notre jeu.
- Ajoutez à la fin de la classe GameEngine la declaration :
Visual C#
private double deltaTime;
Visual Basic
Private deltaTime As Double
Nous devons démarer le timer au dernier moment possible afin d’être le plus précis possible.
- A la fin de la méthode OnPaint, juste avant l’appel à la méthode Invalidate insérez l’instruction suivante :
Visual C#
Visual Basic
Microsoft.Samples.DirectX.UtilityToolkit.FrameworkTimer.Start()
Nous devons calculer la temps écoulé (delta) au départ de chaque boucle. Cette valeur sera utilisée dans la plupart des calculs qui suivront.
- Au tout début de la méthode OnPaint, avant la première instruction existante, ajoutez la ligne de code :
Visual C#
deltaTime = FrameworkTimer.GetElapsedTime();
Visual Basic
deltaTime = Microsoft.Samples.DirectX.UtilityToolkit.FrameworkTimer. GetElapsedT
ime()
C’est terminé. Nous avons désormais un moyen de maitriser le temps dans notre jeu. Lors de notre prochain article nous utiliserons ce système pour calculer le FPS de notre jeu. A ce point notre projet ne compile plus : nous devons ajouter une référence vers les DLL de DirectX. Ceci sera vu lors du prochain article.
Résumé
Nous avons parcouru un grand chemin dans notre apprentissage dans cet article. Tout d’abord nous avons énumérés les différents outils qui nous aideront dans notre tache, puis nous avons définit l’idée principale du jeu et créer le projet lié. Enfin nous avons créé la boucle de jeu, clé de voute de l’application pour finalement terminer sur la réalisation d’un support temporel.
Les étapes suivantes exploiteront DirectX en profondeur. J’espère que ce premier article vous a motivé pour créer le jeu dont vous avez toujours rêvé. La programmation de jeu est sans doute, dans le monde du développement l’expérience la plus gratifiante qu’un programmeur puisse connaître. Afin de vous permettre d’atteindre ce but, je vous enseignerai tout au long de ces articles tous les fondements nécessaires.