XNA
Débuter un jeu XNA
Préambule
Cette partie de l'exposé vous expliquera comment commencer le développement de votre jeu vidéo. Il est nécessaire d'avoir quelques connaissances en C# pour continuer ce tutoriel. Si vous êtes plutôt un développeur Java, sachez qu'il n'est pas difficile de s'adapter à ce langage. Une maitrise de l'IDE de Microsoft est souhaitable.
Comme dit précédemment, il vous faut l'IDE Visual Studio ou Visual C# ainsi que le framework XNA. Voici les différents liens permettant de les télécharger:
- Visual C# : Cliquez ici
- XNA : Cliquez ici
L'installation ne comporte pas de difficultés particulières et ressemble à une installation de logiciel classique sous Windows. Une fois l'installation terminée, il faut aller dans le menu " démarrer ", puis cliquer sur " Microsoft XNA Game Studio 3.1 " et enfin sur " Microsoft Visual C# 2008 Express Edition " (ou équivalent).
Ceci vous permettra de lancer l'IDE en ayant un environnement optimisé pour la création de votre jeu XNA. Créez votre premier projet en cliquant sur " Fichier " puis sur " Nouveau projet ". Vous devriez aboutir à cette fenêtre :
Il vous est donc possible de créer pour différentes plateformes, mais aussi des bibliothèques(libraries) pour les différentes plateformes Microsoft. Il est aussi possible de reprendre un projet fourni par XNA, via " Platformer Starter Kit ". Ce petit jeu vous offre un exemple concret des possibilités offertes par XNA et permet aussi de comprendre l'architecture d'un jeu XNA.
Notez que Windows Phone n'apparait pas sur les modèles de projet proposés. En effet, le développement de jeux XNA sur cette plateforme n'est disponible qu'à partir de la version 4.0 de XNA.
Dans cet exemple, nous allons créer un jeu pour PC. Vous devriez aboutir à l'écran suivant :
Sur la gauche, vous avez les éléments suivants :
- Properties contient les propriétés Système de votre jeu. Il n'est, à priori, pas nécessaire de modifier ces propriétés.
- References contient l'ensemble des bibliothèques qui seront utilisées par votre jeu.
- Content est un répertoire qui contiendra l'ensemble des ressources (textures, musiques, modèle 3D, etc.). C'est donc à cet endroit là qu'il faudra ajouter ces différentes ressources.
- Game.ico représente l'icône de votre jeu.
- Game1.cs représente la classe principale de votre jeu. C'est ici que seront gérées les différentes phases (Initialize, LoadContent etc.) de votre jeu.
- GameThumbNaiil.png contient une simple vignette.
- Program.cs est la classe qui lancera votre jeu. Il n'est pas nécessaire d'y toucher pour concevoir le jeu.
Voici ci-dessous, le contenu de la classe Game1.cs :
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace WindowsGame1 { /// |
Celle-ci contient plusieurs méthodes, attributs et tâches :
- GraphicsDeviceManager graphics : cet attribut se charge de gérer et configurer la carte graphique.
- SpriteBatch spriteBatch : cet attibut contient des méthodes permettant de dessiner à l'écran et d'appliquer quelques effets graphiques.
- Le constructeur Game1() : celui-ci va initialiser le GraphicsDeviceManager et va spécifier le chemin qui contient les ressources de votre jeu (Content.RootDirectory = "Content";). Content permet donc de gérer l'ensemble des ressources de votre jeu.
- Les différentes phases expliquées dans le chapitre précédent. Ces méthodes ont été préremplies et effectuent les tâches suivantes :
- La méthode Initialize appelle la méthode Initialize de la classe parente
- La méthode LoadContent va initialiser le spriteBatch en spécifiant le gestionnaire de carte graphique concerné.
- La méthode update se contente de quitter le jeu si le joueur appuie sur la touche " Back " de la manette XBOX 360. Par conséquent, les lignes suivantes peuvent être effacées :
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
Elle appelle aussi la méthode update de la classe parente. - La méthode Draw se contente pour l'instant d'effacer le contenu de l'écran et de le " peindre " en bleu via l'appel suivant : GraphicsDevice.Clear(Color.CornflowerBlue); Elle appelle aussi la méthode Draw de la classe parente.
Premiers pas avec XNA
Pour commencer en douceur, nous souhaitons afficher simplement l'image suivante qui se déplacera de gauche à droite :
Dans un premier temps, il faut ajouter cette image à notre projet. Voici les opérations à effectuer :
Clic droit sur " Content " puis cliquer sur " Ajouter " et enfin sur " Ajouter un élément existant ". Un explorateur apparait, permettant de choisir l'élément à ajouter au jeu. Sélectionnez donc le ballon.
Il faut ensuite créer la classe Ballon.cs. Etant donné que ce ballon est un élément affichable à l'écran, elle va hériter de la classe DrawableGameComponent.
Tout d'abord, nous déclarons les attributs suivants :
private Texture2D texture; //texture de l'image private Vector2 position; //vecteur pour la position de l'image private int speedX; //vitesse de déplacement de l'image private SpriteBatch spriteBatch; //spriteBatch de l'image |
Nous déclarons ensuite le constructeur de notre élément :
public Ballon(Game game, Vector2 positionInitial, int speedX) : base(game) { this.position = positionInitial; this.speedX = speedX; this.Game.Components.Add(this); } |
Il faut ensuite mettre en place la méthode initialize :
public override void Initialize() { base.Initialize(); spriteBatch = new SpriteBatch(GraphicsDevice); } |
Ensuite, la méthode LoadContent :
protected override void LoadContent() { texture = Game.Content.Load |
texture = Game.Content.Load
Il est important de noter qu'il ne faut pas mettre l'extension du fichier lorsqu'on décrit le chemin d'une ressource. En effet, lorsqu'on importe une ressource, celle-ci est compressée et converti en un autre format. Par conséquent, vous ne pouvez pas inclure deux ressources avec le même nom mais des extensions différentes. Il faut noter que nous avons ici directement écrit le chemin de la texture " en dur " pour simplifier cet exposé. L'idéal aurait été de passer par un service, comme nous l'avons vu précédemment.
Ensuite, la méthode Update :
public override void Update(GameTime gameTime) { position.X += speedX; base.Update(gameTime); } |
Et enfin, la méthode Draw :
public override void Draw(GameTime gameTime) { spriteBatch.Begin(); spriteBatch.Draw(texture, position, Color.White); spriteBatch.End(); base.Draw(gameTime); } |
- La texture à afficher
- La position où elle doit être affichée
- La teinte que l'on veut appliquer à la texture. LA couleur blanche signifie une absence de teinte.
Il ne vous reste plus qu'à instancier le ballon dans la méthode Initialize de la classe Game :
Ballon ballon = new Ballon(this, new Vector2(10, 10), 5);
Voici un aperçu de ce qui s'affiche à l'écran :
Il existe cependant un petit problème. En effet, vous ne maitrisez pas la fréquence d'appel de la méthode update. Celle-ci dépend de la puissance de la plateforme. Par conséquent, le ballon risque de se déplacer plus vite sur une machine plus puissante et moins vite sur une machine moins puissante.
C'est ici qu'intervient le gameTime passé en paramètre. En effet, via ce paramètre, il est possible de connaitre le temps qu'il s'est écoulé depuis le dernier appel à update ou le temps qu'il s'est écoulé depuis le début du jeu.
Nous allons donc modifier la Ballon. Dans un premier temps, nous ajoutons les deux attributs suivants :
private double previousTimeMoved = 0; //mémorisation du time lors du dernier mouvement private double timeBetweenTwoMoves = 200; //temps qu'il doit s'écouler entre deux déplacements |
Nous modifions donc la méthode update :
public override void Update(GameTime gameTime) { double currentTime = gameTime.TotalGameTime.TotalMilliseconds; if (this.previousTimeMoved == 0) { this.previousTimeMoved = currentTime; } if (this.previousTimeMoved + this.timeBetweenTwoMoves < currentTime) { position.X += speedX; this.previousTimeMoved = currentTime; } base.Update(gameTime); } |
Les musiques et les sons reprennent la même logique, hormis la phase Draw qui, évidemment disparait. Il existe cependant une autre manière de les gérer mais celle-ci ne sera abordé dans cet exposé.
Gestion des contrôles : utilisation des Services
L'étape suivante consiste à permettre au joueur de contrôler ce ballon en appuyant sur le bouton de droite ou celui de gauche. Nous allons pour cela utiliser les Services. Notez qu'il n'est pas forcément nécessaire d'utiliser des Services.
Pour comprendre la manière dont on va utiliser les Services, considérons le schéma suivant :
Au moment de l'étape " update ", la classe Ballon cherchera un service qui pourra lui dire quelle action a été commandé par le joueur. Pour cela, ce service doit respecter un certain contrat. Ainsi, un service correspondant au contrat fourni par le joueur sera extrait. Ainsi le fonctionnement du ballon est complètement indépendant du type de contrôle utilisé. Le développeur pourra, s'il le souhaite, changer de contrôles ou de plateformes sans qu'il n'y ait d'impact sur la classe Ballon.
Le contrat à respecter sera symbolisé par une interface que nous allons nommer ControlManager :
interface ControlManager { Boolean isLeft(); Boolean isRight(); } |
Ensuite, nous allons créer un Service qui respecte ce contrat. Nous nommerons ce service ControlManagerImpl.
class ControlManagerImpl : Microsoft.Xna.Framework.GameComponent, ControlManager { public ControlManagerImpl(Game game) : base(game) { game.Services.AddService(typeof(ControlManager), this); } private KeyboardState getKeyboardState() { return Keyboard.GetState(); } bool ControlManager.isLeft() { return getKeyboardState().IsKeyDown(Keys.Left); } bool ControlManager.isRight() { return getKeyboardState().IsKeyDown(Keys.Right); } } |
Ce Service se charge dans un premier temps de récupérer l'état du clavier via la commande suivante : Keyboard.GetState(). Ensuite, via la méthode IsKeyDown(Keys.Left) et IsKeyDown(Keys.Right), il vérifiera si le joueur a appuyé sur la touche de gauche ou celle de droite.
Il faut donc ensuite modifier la méthode update de la classe Ballon :
public override void Update(GameTime gameTime) { ControlManager cm = (ControlManager)Game.Services.GetService(typeof(ControlManager)); double currentTime = gameTime.TotalGameTime.TotalMilliseconds; if (this.previousTimeMoved == 0) { this.previousTimeMoved = currentTime; } if (this.previousTimeMoved + this.timeBetweenTwoMoves < currentTime) { if (cm.isRight()) { position.X += speedX; } else if (cm.isLeft()) { position.X -= speedX; } this.previousTimeMoved = currentTime; } base.Update(gameTime); } |
nous demandons à Game de nous fournir un Service qui respecte le contrat défini dans ControlManager. Ensuite, nous appelons les méthodes isLeft et isRight pour savoir si le joueur a appuyé sur les touches droite et gauche.
Bien entendu, XNA ne se limite pas à afficher des images qui se déplacent. Le prochain chapitre vous offre un aperçu des possibilités de XNA.