III. Description technique▲
À travers cette partie, nous allons véritablement entrer dans la description des différents éléments des couches présentées dans la partie de description de l'architecture. Pour cela nous allons partir de la couche de plus haut niveau (couche jeu) pour terminer par les couches de plus bas niveau du Framework XNA.
III-A. Le Framework étendu▲
III-A-1. Le modèle applicatif▲
L'intention de ce modèle applicatif présent dans cette partie du Framework XNA est de faire abstraction de tous traitements habituels des jeux vidéo, à savoir le suivi de son cycle de vie.
Avec XNA, pas besoin de s'occuper de créer la fenêtre, de gérer les messages, de créer les différents timers et horloge d'exécution, la seule chose sur laquelle le développeur se concentre, c'est le code de son jeu.
Les jeux vidéo en général respectent un schéma de base simple dans leur exécution :
Après avoir été initialisé, le cœur du jeu s'exécute en boucle (la boucle de jeu) en effectuant les opérations suivantes.
Mise à jour du modèle : prise en compte des actions du jeu, d’actions utilisateur, du moteur physique, etc.
Affichage du modèle à l'écran
Dans certains cas, cette boucle peut se voir ajouter une troisième étape qui correspond à une étape de posttraitement pour des effets après rendu.
Ce fonctionnement logique du jeu vidéo doit en général être traité par le développeur, grâce à XNA, toute cette architecture logicielle est prise en charge.
Lors de la création d'un jeu vidéo à travers l'IDE dédié, on remarque que la génération du code respecte un schéma particulier et base le projet sur un squelette de base. Une classe principale, dérivée de la classe Game permet d'utiliser de nombreux éléments conteneurs et autres services, mais propose également cinq méthodes qui décrivent le cycle de vie du jeu et qui sont très utiles dans la simplification du développement :
Initialize
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
Cette méthode permet de créer les composants non graphiques, d'initialiser les paramètres du jeu, de créer les différentes instances nécessaires et de récupérer des informations sur la plateforme d'exécution.
LoadGraphicsContent
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
// TODO: Load any ResourceManagementMode.Automatic content
}
// TODO: Load any ResourceManagementMode.Manual content
}
Cette méthode est appelée automatiquement à travers la méthode Initialize(). C'est à travers cette dernière que l'on charge les éléments et ressources nécessaires au jeu, par exemple les modèles 3d, les textures et autres éléments utiles.
UnloadGraphicsContent
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent)
{
// TODO: Unload any ResourceManagementMode.Automatic content
content.Unload();
}
// TODO: Unload any ResourceManagementMode.Manual content
}
C'est à travers cette méthode que nous procédons à la libération des ressources, on décharge ainsi les éléments de la mémoire, on « dispose » les différents éléments graphiques utilisés.
Update
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
base.Update(gameTime);
}
Cette méthode correspond à l'étape de mise à jour du modèle, de prise en compte des actions du jeu. C'est clairement une méthode présente dans la boucle de jeu qui prend en compte les actions et entrées de l'utilisateur, du moteur physique, etc.
Draw
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
base.Draw(gameTime);
}
Cette méthode correspond à l'étape d'affichage du modèle, il s'agit réellement de celle qui effectue le rendu à l'écran à travers le GraphicsDevice géré par XNA.
Plus de détails sur la boucle de jeu : la boucle de jeu peut être gérée par le développeur pour correspondre à un besoin précis. L'appel successif des méthodes Update() et Draw() peut alors être modifié par le développeur.
Variable step game
Cette première possibilité de gestion de la boucle de jeu présente le fonctionnement suivant : lorsque la frame précédente est affichée à l'écran, l'appel de la méthode Update() est directement réalisé. On attend ainsi l'affichage complet pour appeler la méthode de mise à jour du modèle.
Fixed step game
Cette seconde possibilité impose un intervalle fixe entre les appels de la méthode Update.
Cette seconde possibilité peut s'avérer plus utile et plus simple d'utilisation, car on peut alors effectuer des traitements à intervalles réguliers tels que des déplacements linéaires.
Dans les deux cas, l'animation linéaire d'un objet ou l'évolution linéaire d'un élément peut être assurée, car si dans le cas d'une utilisation d'intervalle régulier il est aisé de traiter cette évolution, dans le cas d'une utilisation d'un intervalle variable on peut utiliser le temps du jeu passé en paramètre de la méthode Update().
Le content pipeline
Les contenus multimédias graphiques et sonores occupent une place plus qu'importante dans les jeux actuels et l'incorporation de ces éléments dans le projet n'est pas chose aisée. La plupart du temps, l'accent est mis sur la recherche de l'exporter approprié, de l'outil nécessaire pour effectuer une tâche particulière. Dans un environnement de travail en équipe, plusieurs personnes travaillent en ce sens pour réussir à inclure ces éléments dans le projet et cela nécessite des moyens importants et réduit la flexibilité de travail.
Le content pipeline faisant partie du Framework XNA propose une approche différente et améliorée.
Il ne vous propose pas les outils de création de contenu (récemment l'apparition de l'outil de Softimage XSI Mod Tools gratuit et dédié à la création pour le jeu), mais cependant, une fois de plus, il facilite les actions du développeur en prenant en charge les étapes nécessaires à l'exploitation du contenu.
Il se veut également ouvert et personnalisable pour combler au mieux le besoin et enfin il vous laisse le choix des outils de création de contenu que ce soit au niveau des graphismes 2D, 3D ou sonore.
Pour comprendre comment fonctionne cette partie importante du Framework, il peut être utile de comprendre comment il fonctionne à plus bas niveau et de découvrir les différentes parties de celui-ci.
Les importers
Lorsque vous ajoutez du contenu à votre jeu vidéo pour la première fois, vous avez besoin de choisir un importer (même si ce choix peut être assisté). Cet importer est responsable de récupérer les données, de les traiter et de les normaliser pour la suite du traitement, il vérifie également leur cohérence. Il n'est donc plus nécessaire de s'occuper de l'importation et des soucis liés, l'importer s'occupe de tout à condition d'être rigoureux dans la création de contenu (fichiers respectueux du format).
Il existe de nombreux importers parmi ceux-ci bien sûr nombre d'importers habituels, mais également quelques-uns très intéressants tels que ceux liés au format Autodesk FBX. Ce format présente la possibilité de sauvegarder des scènes 3D complètes à travers la plupart des outils de création graphiques tant open source que commerciaux, s'avérant ainsi une bonne solution en attendant un réel standard dans les formats de fichier 3D.
Les différents formats de fichiers et types de fichiers supportés sont les suivants, les exporteurs de fichiers dans ce format sont nombreux :
Les fichiers audio de format XAP sont issus de l'outil XACT (Microsoft Cross-Platform Audio Creation Tool) qui sait traiter les fichiers sonores dans des formats standards.
Le content DOM
Une fois que les importers ont réalisé leur tâche d'importation, les données sont présentes dans le content DOM. Le terme DOM est ici simplement utilisé pour représenter une collection de classes et de schémas (de la même manière qu'un fichier XML).
Les données présentes dans le content DOM sont des données fortement typées ce qui signifie qu'elles sont stockées sous un format bien connu par exemple, une collection de sommets ou des données de textures auront le même format dans ce content DOM.
Ce point est important pour la suite du traitement de ces données, ce résultat peut d'ailleurs être inscrit sur le disque au format XML pour le débogage uniquement, il permet un traitement homogène dans les autres étapes d'exploitation des contenus.
Les processors
Un processor est chargé de récupérer les données présentes dans le content DOM et de créer un objet qui pourra être utilisé en exécution. Cet objet peut alors être simple comme un processor model (sur lequel nous reviendrons juste en dessous) ou complexe en combinant plusieurs de ces processors.
Par défaut, le Framework XNA propose plusieurs processors de base tels :
le processor model : utile pour les objets simples avec textures ;
le processor Texture2D : utile pour les sprites ou pour combiner avec les models ;
le processor Effect : utile pour manipuler les matériaux des models ;
il en existe bien d'autres…
La plupart du temps donc, ces processors sont utilisés en combinaison et sont très utiles pour effectuer des tâches importantes de traitement des données en plus du fait qu'ils sont très permissifs et facilement personnalisables pour exploiter n'importe quel contenu, car en utilisant le content DOM qui normalise les données, ces processors peuvent traiter tout type de fichiers de contenu.
Il est possible depuis ces processors et le content DOM, d'effectuer des opérations d'optimisation qui peuvent améliorer le rendement de votre jeu vidéo.
Le content building
Cette partie du content pipeline est également importante et il est géré par le coordinateur de construction du projet. Ainsi lorsque le projet est compilé, le contenu est construit et enregistré sur le disque de la même façon que votre code.
Le content manager
Le content manager est véritablement l'élément qui vous permet d'utiliser vos contenus dans votre jeu, il s'occupe de charger rapidement ces éléments en mémoire.
Son utilisation se veut très simple et voici un exemple de code qui vous permettra de récupérer un élément que vous avez ajouté à votre projet.
Exemple de code issu du code de projet nouvellement créé :
ContentManager contentManager = new ContentManager(Services);
Model model = contentManager.Load("element");
Il est donc très aisé d'utiliser du contenu à votre jeu vidéo en utilisant cette technologie qu'est le content pipeline intégré à XNA et ceci devrait encore s'améliorer au fil des versions.
Voici un schéma qui résume le fonctionnement global du content pipeline :
Pour aller plus loin avec cette pièce importante du Framework XNA, vous pouvez consulter le Livre Blanc rédigé par Valentin Billotte - MVP C#/DirectX technologies. Cet article est très complet et bien plus juste que cette introduction du content pipeline.
III-B. Prérequis pour le développement avec XNA▲
Visual C# Express Edition 2005 - http://msdn2.microsoft.com/en-us/express/aa700756.aspx
Il faut tout d'abord installer cet IDE gratuit de Microsoft. Seule cette version permet de développer officiellement avec XNA. Par la suite, le développement sera supporté sur toutes les éditions de Visual Studio, mais cela arrivera avec la sortie de XNA 2.0.
XNA Game Studio Express - http://msdn2.microsoft.com/en-us/xna/aa937795.aspx
Extension qui vient s'ajouter à C# Express Edition 2005, on peut alors retrouver des templates de projets correspondant à des jeux Windows ou Xbox, des bibliothèques, mais aussi les « starter kits ».
Service Pack 1 - C# Express Edition 2005 http://www.microsoft.com/downloads/details.aspx?familyid=7b0b0339-613a-46e6-ab4d-080d4d4a8c4e&displaylang=en
III-C. Création d'un projet de jeu développé avec XNA▲
Une fois ces éléments installés, il est possible de créer un projet XNA. Pour cela, à travers Visual C# Express Edition 2005, on crée un nouveau projet : File>New>Project…
Plusieurs modèles de projets s'offrent alors à nous :
Windows Game : jeu basé sur le Framework XNA destiné à être utilisé sous plateforme Windows (PC).
Xbox 360 Game : jeu destiné à être utilisé pour la console Xbox 360.
Windows Game Library : bibliothèque de jeu basé sur XNA destiné à être utilisé à travers un jeu Windows.
Xbox 360 Game Library : la même chose, mais pour la console Xbox 360.
Et enfin, des starters kits : par défaut en installant les éléments indiqués on peut utiliser le starter kit « Spacewar » proposant ainsi les modèles : Spacewar Windows Starter kit et Spacewar Xbox 360 Starter Kit.
Il est ensuite possible d'utiliser nos propres modèles ou d'autres « starter kits ».
Le choix d'un de ces modèles permet d'obtenir une première architecture de base pour le jeu ou la bibliothèque à développer.
Nommez directement votre projet et situez le répertoire au moment de la création pour vous éviter quelques éventuels soucis.
III-D. Le cœur du Framework▲
À travers cette partie, nous allons aborder les différents namespaces du cœur du Framework et les classes importantes qui les composent.
III-D-1. Le namespace « Graphics »▲
III-D-1-a. Description▲
Ce namespace présente des classes et interfaces disponibles en code managé qui permettent la programmation graphique en s'appuyant sur les API issues de Direct3D 9.
À l'origine basée sur Managed DirectX (qui devrait d'ailleurs progressivement disparaître au profit de XNA), la partie graphique de XNA a été revue en profondeur pour offrir une solution plus simple et plus propre à utiliser en développement, ainsi les fonctions ne sont plus si proches du langage de bas niveau et une nouvelle fois, l'expérience du développeur est améliorée.
La principale amélioration de XNA sur le plan graphique, par rapport aux autres produits de versions précédentes est de favoriser le portage des jeux vidéo sur la plateforme Xbox 360 en imposant un pipeline basé totalement sur les shaders (les shaders sont des jeux d'instruction utilisés au moment du rendu).
Voici une série de méthodes pour réaliser diverses opérations graphiques.
III-D-1-b. Exemple de code▲
Affichage d'élément 2D
Afin d'afficher un élément 2D, on peut utiliser un objet de type SpriteBatch, ce dernier proposant la possibilité d'encadrer les opérations de tracés des textures 2D.
Après avoir déclaré dans la classe principale dérivée de Game mon objet de type SpriteBatch ainsi qu'un objet de type Texture2D qui correspond à la texture à afficher à partir d'un fichier, de cette façon :
SpriteBatch _spriteBatch;
Texture2D _texture2D;
On crée le batch en question dans la méthode d'initialisation (Initialize()) et on place en paramètre les périphériques graphiques gérés par XNA. On peut en profiter pour personnaliser notre fenêtre en modifiant les dimensions ou les titres.
_spriteBatch = new SpriteBatch(_graphics.GraphicsDevice);
//properties of the game window
_graphics.PreferredBackBufferWidth = 800;
_graphics.PreferredBackBufferHeight = 600;
_graphics.IsFullScreen = false;
_graphics.ApplyChanges();
Window.Title = "Microsoft XNA - Boonaert Nicolas";
On ajoute un fichier dans le content pipeline de la même manière que l'on ajoute un fichier au projet (dans le Solution Explorer>Clic droit>Add>Existing Item puis sélectionner content pipeline option puis sélectionner le fichier en question).
Il faut ensuite initialiser la texture2D que l'on va utiliser, ce qui est fait dans la méthode LoadGraphicsContent() :
_texture2D = content.Load<Texture2D>("textures/bill_gates_box");
Dans la méthode Draw() du la classe principale, on procède à l'affichage de l'élément :
_spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
_spriteBatch.Draw(_texture2D,
new Rectangle(10, 10,565,210),
Color.White);
_spriteBatch.End();
Voici le résultat observé en exécution :
Voici le code de la classe principale résumant cette explication :
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace WindowsGame
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager _graphics;
ContentManager content;
SpriteBatch _spriteBatch;
Texture2D _texture2D;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
protected override void Initialize()
{
base.Initialize();
_spriteBatch = new SpriteBatch(_graphics.GraphicsDevice);
//properties of the game window
_graphics.PreferredBackBufferWidth = 800;
_graphics.PreferredBackBufferHeight = 600;
_graphics.IsFullScreen = false;
_graphics.ApplyChanges();
Window.Title = "Microsoft XNA - Boonaert Nicolas";
}
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
_texture2D = content.Load<Texture2D>("textures/vs2008");
}
}
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent)
{
content.Unload();
}
}
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
_graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
_spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
_spriteBatch.Draw(_texture2D,
new Rectangle(10, 10,565,210),
Color.White);
_spriteBatch.End();
base.Draw(gameTime);
}
}
}
Affichage d'élément 3D
Pour afficher un modèle 3D, la mise en œuvre est relativement proche de la précédente en 2D.
On ajoute la déclaration d'un objet de type Model à la classe principale :
Model _model;
On charge le modèle depuis le content pipeline après avoir ajouté le modèle et la texture associée.
_model = content.Load<Model>("models/p1_wedge");
Enfin dans la méthode Draw(), on récupère les sommets de l'objet 3D, on initialise les propriétés du modèle, puis la caméra et ses propriétés.
// Copy any parent transforms.
Matrix[] transforms = new Matrix[_model.Bones.Count];
_model.CopyAbsoluteBoneTransformsTo(transforms);
// Draw the model. A model can have multiple meshes, so loop.
foreach (ModelMesh mesh in _model.Meshes)
{
// This is where the mesh orientation is set, as well as our camera and projection.
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index]
* Matrix.CreateRotationX(modelRotation/10)
* Matrix.CreateRotationY(modelRotation)
* Matrix.CreateTranslation(modelPosition);
effect.View = Matrix.CreateLookAt(cameraPosition,
Vector3.Zero,
Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f),
1.33f,
1.0f,
10000.0f);
}
// Draw the mesh, using the effects set above.
mesh.Draw();
}
Voici le résultat en exécution, on observe l'objet 3D en rotation sur ses axes x et y :
Ce modèle est issu du starter kit SpaceWar, de même que sa texture.
Voici alors le code de la classe principale :
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace WindowsGame
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager _graphics;
ContentManager content;
SpriteBatch _spriteBatch;
Texture2D _texture2D;
Model _model;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
protected override void Initialize()
{
base.Initialize();
_spriteBatch = new SpriteBatch(_graphics.GraphicsDevice);
//properties of the game window
_graphics.PreferredBackBufferWidth = 800;
_graphics.PreferredBackBufferHeight = 600;
_graphics.IsFullScreen = false;
_graphics.ApplyChanges();
Window.Title = "Microsoft XNA - Boonaert Nicolas";
}
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
_texture2D = content.Load<Texture2D>("textures/bill_gates_box");
_model = content.Load<Model>("models/p1_wedge");
}
}
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent)
{
content.Unload();
}
}
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
modelRotation += (float)gameTime.ElapsedGameTime.TotalMilliseconds
* MathHelper.ToRadians( 0.1f );
base.Update(gameTime);
}
// Set the position of the model in world space, and set the rotation.
Vector3 modelPosition = Vector3.Zero;
float modelRotation = 0.0f;
// Set the position of the camera in world space, for our view matrix.
Vector3 cameraPosition = new Vector3(0.0f, 80.0f, 5000.0f);
protected override void Draw(GameTime gameTime)
{
_graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// Copy any parent transforms.
Matrix[] transforms = new Matrix[_model.Bones.Count];
_model.CopyAbsoluteBoneTransformsTo(transforms);
// Draw the model. A model can have multiple meshes, so loop.
foreach (ModelMesh mesh in _model.Meshes)
{
// This is where the mesh orientation is set
// as well as our camera and projection.
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index]
* Matrix.CreateRotationY(modelRotation)
* Matrix.CreateRotationX(modelRotation/10)
* Matrix.CreateTranslation(modelPosition);
effect.View = Matrix.CreateLookAt(cameraPosition,
Vector3.Zero,
Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f),
1.33f,
1.0f,
10000.0f);
}
// Draw the mesh, using the effects set above.
mesh.Draw();
}
}
}
}
III-D-2. Le namespace « Audio »▲
III-D-2-a. Description▲
Ce namespace propose un ensemble d'API qui repose sur XACT (Microsoft Cross-Platform Audio Creation Tool) et plus précisément sur les API qui le composent aussi bien pour Windows que pour Xbox 360.
Le principe est le même que pour le contenu graphique, on retrouve la même idée que les shaders de Direct3D. Les créateurs des sons utilisent l'outil XACT pour créer des ensembles (packages) de sons et configurent les propriétés telles que le volume, le canal (dans le cas du 5.1) à travers l'outil dédié.
Les développeurs n'ont alors qu'à importer ces ensembles (packages) de sons, les charger et peuvent ainsi jouer ces sons sans se préoccuper des pénibles étapes d'antan telles que l'initialisation des buffers de sortie ou d'autres opérations de gestion.
Pour exemple, le créateur d'un son d'explosion importe et monte en quelque sorte ses éléments sonores en paramétrant les propriétés, il package l'ensemble des sons wave le constituant ainsi que les informations liées, dans un package XAP généré depuis XACT. Le développeur importe ce package est n'a qu'à le jouer.
III-D-2-b. Exemple de code▲
Pour jouer un son, ici on utilise le fichier XAP issu du starter kit Spacewar.
Il suffit d'ajouter le fichier XAP à votre projet via le Solution Explorer, puis dans le dossier où est situé le fichier XAP dans votre projet, déposer le dossier des fichiers WAV associés.
On déclare les objets des composants nécessaires à l'exploitation de sons dans XNA.
//Audio API components
AudioEngine _audioEngine;
WaveBank _waveBank;
SoundBank _soundBank;
L'objet de type AudioEngine correspond au moteur de rendu sonore, il est utilisé en paramètre des autres objets.
L'objet de type WaveBand correspond à l'ensemble des sons ajoutés au projet par l'intermédiaire du fichier XAP.
Enfin l'objet de type SoundBank représente une collection de sons qu'on va pouvoir jouer dans notre jeu.
Dans la méthode Initialize(), on prend soin d'initialiser les objets utilisés comme suit :
//initialize audio components
_audioEngine = new AudioEngine(@"audio\SpaceWar.xgs");
_waveBank = new WaveBank(_audioEngine, @"audio\SpaceWar.xwb");
_soundBank = new SoundBank(_audioEngine, @"audio\SpaceWar.xsb");
Enfin dans cette même fonction, on peut choisir de jouer le son :
_soundBank.PlayCue("title_music");
Le nom du fichier à placer en paramètre est celui fixé lors de la création du fichier XAP dans le XACT, il peut être retrouvé en ouvrant le fichier XAP dans visual studio.
Le résultat en exécution est une fenêtre de base qui au lancement, joue la musique du jeu Spacewar que nous avons récupérée.
Voici le code de la classe principale permettant de réaliser ce traitement :
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace WindowsGame
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager _graphics;
ContentManager content;
//Audio API components
AudioEngine _audioEngine;
WaveBank _waveBank;
SoundBank _soundBank;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
protected override void Initialize()
{
base.Initialize();
//initialize audio components
_audioEngine = new AudioEngine(@"audio\SpaceWar.xgs");
_waveBank = new WaveBank(_audioEngine, @"audio\SpaceWar.xwb");
_soundBank = new SoundBank(_audioEngine, @"audio\SpaceWar.xsb");
_soundBank.PlayCue("title_music");
Window.Title = "Microsoft XNA - Boonaert Nicolas";
}
protected override void LoadGraphicsContent(bool loadAllContent)
{
}
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent)
{
content.Unload();
}
}
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
_graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
}
}
}
III-D-3. Le namespace « Input »▲
III-D-3-a. Description▲
L'API de gestion des entrées de l'utilisateur (inputs) repose sur XInput qui est l'API multiplateforme qui permet d'exploiter la manette classique (manette de la Xbox 360), mais aussi le clavier et la souris.
Parmi ces trois types de périphériques d'entrées supportés, les possibilités du contrôleur de la Xbox 360 sont plus étendues, car il est possible d'aller plus loin dans les possibilités du jeu vidéo.
On y retrouve par exemple, la possibilité d'exploiter les sticks présents sur le contrôleur qui ne renvoient pas de valeurs booléennes comme un clavier, mais des valeurs flottantes, on peut aussi utiliser les vibrations du contrôleur tout cela pour renforcer la prise en main et l'immersion de l'utilisateur final.
Bien entendu l'idée de multiplateforme n'est jamais très loin et la possibilité de « mapper » directement les contrôles entre les périphériques sont d'ores et déjà présentes.
III-D-3-b. Exemple de code▲
Pour exploiter les périphériques d'entrées, on utilise une fonction UpdateInput() que l'on crée dans la classe principale pour alléger le code de la méthode Update(), cette méthode appellera à son tour trois autres fonctions respectivement pour la manette Xbox 360, le clavier et la souris :
UpdateInputGamePad()
protected void UpdateInputGamePad()
{
// Get the game pad state.
GamePadState currentState = GamePad.GetState(PlayerIndex.One);
if (currentState.IsConnected)
{
// Rotate the model using the left thumbstick, and scale it down.
modelRotationY -= currentState.ThumbSticks.Left.X * 0.10f;
modelRotationX -= currentState.ThumbSticks.Left.Y * 0.10f;
// Create some velocity if the right trigger is down.
Vector3 modelVelocityAdd = Vector3.Zero;
// Find out what direction we should be thrusting, using rotation.
modelVelocityAdd.X = -(float)Math.Sin(modelRotationY);
modelVelocityAdd.Z = -(float)Math.Cos(modelRotationY);
// Now scale our direction by how hard the trigger is down.
modelVelocityAdd *= currentState.Triggers.Right;
// Finally, add this vector to our velocity.
modelVelocity += modelVelocityAdd;
GamePad.SetVibration(PlayerIndex.One,
currentState.Triggers.Right,
currentState.Triggers.Right);
// In case you get lost, press A to warp back to the center.
if (currentState.Buttons.A == ButtonState.Pressed)
{
modelPosition = Vector3.Zero;
modelVelocity = Vector3.Zero;
modelRotationY = 0.0f;
modelRotationX = 0.0f;
}
}
}
Cette fonction met à jour des variables de la classe principale depuis des valeurs lues sur le périphérique d'entrée qui ici correspond au contrôleur de la Xbox 360 connecté au PC.
On remarque ainsi l'utilisation d'un objet de type GamePadState qui récupère l'état du contrôleur souhaité en utilisant la classe GamePad.
GamePadState currentState = GamePad.GetState(PlayerIndex.One);
Après avoir vérifié la connexion du contrôleur, on récupère la valeur sur le stick de gauche qui servira à effectuer la rotation du modèle 3D sur les axes X et Y :
modelRotationY -= currentState.ThumbSticks.Left.X * 0.10f;
modelRotationX -= currentState.ThumbSticks.Left.Y * 0.10f;
On remarque que les sticks principaux sont appelés Thumbsticks dans cette classe, on doit sélectionner celui qu'on souhaite sur le contrôleur (Droite ou Gauche).
Il existe plusieurs catégories d'éléments du contrôleur qui peuvent être lus via cet objet d'état tels que les thumbsticks, les triggers (gâchettes du dessus du contrôleur), les buttons (boutons du contrôleur).
Cette fonction UpdateInput initialise ensuite les variables permettant d'effectuer la rotation, enfin on peut remarquer une ligne de code intéressante ici :
GamePad.SetVibration(PlayerIndex.One,
currentState.Triggers.Right,
currentState.Triggers.Right);
Ici, on propose que la vibration du contrôleur s'effectue sur l'appui de la gâchette de droite, on peut changer cette valeur par une autre valeur flottante issue par exemple des sticks principaux.
Cette fonction SetVibration() prend en paramètres l'index du contrôleur sur lequel il faut appliquer l'effet, puis les deux valeurs flottantes qui suivent sont les valeurs de vibration des moteurs du contrôleur (moteur gauche et droit) allant de 0.0 à 1.0.
Cette méthode UpdateInputGamePad() est appelée au sein de la méthode UpdateInput(). Cette dernière appelle également deux autres méthodes :
UpdateInpuKeyboard()
protected void UpdateInputKeyboard()
{
if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
modelRotationX += 0.2f * 0.10f;
}
if (Keyboard.GetState().IsKeyDown(Keys.Down))
{
modelRotationX -= 0.2f * 0.10f;
}
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
modelRotationY -= 0.2f * 0.10f;
}
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
modelRotationY += 0.2f * 0.10f;
}
if (Keyboard.GetState().IsKeyDown(Keys.Space))
isLocked = !isLocked;
}
Cette fonction permet d'obtenir l'état (pressée ou non) des touches directionnelles du clavier.
Keyboard.GetState().IsKeyDown(Keys.Up))
Ici on récupère l'état du clavier et on teste si la touche “Up” (flèche du haut) est appuyée.
On peut remarquer le traitement particulier pour la touche espace qui permet de verrouiller ou non la vue en activant ou non la prise en charge de la souris pour la rotation.
UpdateInputMouse()
protected void UpdateInputMouse()
{
MouseState mState = Mouse.GetState();
if (!isLocked)
{
modelRotationY -= (mState.X - 400) * 0.0002f;
modelRotationX -= (mState.Y - 300) * 0.0002f;
}
}
Cette fonction permet de récupérer la position de la souris par rapport au centre de la fenêtre et d'effectuer la rotation en fonction de la position X et Y de la souris par rapport au centre. On utilise une variable « isLocked » qui nous permet de verrouiller ou non la prise en charge de la souris sur l'appui de la touche espace du clavier.
Voici le code de la classe principale :
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace WindowsGame
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager _graphics;
ContentManager content;
Model _model;
// Set the velocity of the model, applied each frame to the model's position.
Vector3 modelVelocity = Vector3.Zero;
// Set the position of the model in world space, and set the rotation.
Vector3 modelPosition = Vector3.Zero;
float modelRotationY = 0.0f;
float modelRotationX = 0.0f;
// Set the position of the Camera in world space, for our view matrix.
Vector3 cameraPosition = new Vector3(0.0f, 50.0f, -5000.0f);
//Game information
bool isLocked = true; // vision locked (mouse inactive)
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
protected override void Initialize()
{
base.Initialize();
//properties of the game window
_graphics.PreferredBackBufferWidth = 800;
_graphics.PreferredBackBufferHeight = 600;
_graphics.IsFullScreen = false;
_graphics.ApplyChanges();
Window.Title = "Microsoft XNA - Boonaert Nicolas";
}
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
_model = content.Load<Model>("models/p1_wedge");
}
}
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent)
{
content.Unload();
}
}
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// Get some input.
UpdateInput();
// Add velocity to the current position.
modelPosition += modelVelocity;
// Bleed off velocity over time.
modelVelocity *= 0.95f;
base.Update(gameTime);
}
#region inputs
protected void UpdateInput()
{
UpdateInputGamePad();
UpdateInputKeyboard();
UpdateInputMouse();
}
protected void UpdateInputGamePad()
{
// Get the game pad state.
GamePadState currentState = GamePad.GetState(PlayerIndex.One);
if (currentState.IsConnected)
{
// Rotate the model using the left thumbstick, and scale it down.
modelRotationY -= currentState.ThumbSticks.Left.X * 0.10f;
modelRotationX -= currentState.ThumbSticks.Left.Y * 0.10f;
// Create some velocity if the right trigger is down.
Vector3 modelVelocityAdd = Vector3.Zero;
// Find out what direction we should be thrusting, using rotation.
modelVelocityAdd.X = -(float)Math.Sin(modelRotationY);
modelVelocityAdd.Z = -(float)Math.Cos(modelRotationY);
// Now scale our direction by how hard the trigger is down.
modelVelocityAdd *= currentState.Triggers.Right;
// Finally, add this vector to our velocity.
modelVelocity += modelVelocityAdd;
GamePad.SetVibration(PlayerIndex.One,
currentState.Triggers.Right,
currentState.Triggers.Right);
// In case you get lost, press A to warp back to the center.
if (currentState.Buttons.A == ButtonState.Pressed)
{
modelPosition = Vector3.Zero;
modelVelocity = Vector3.Zero;
modelRotationY = 0.0f;
modelRotationX = 0.0f;
}
}
}
protected void UpdateInputKeyboard()
{
if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
modelRotationX += 0.2f * 0.10f;
}
if (Keyboard.GetState().IsKeyDown(Keys.Down))
{
modelRotationX -= 0.2f * 0.10f;
}
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
modelRotationY -= 0.2f * 0.10f;
}
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
modelRotationY += 0.2f * 0.10f;
}
if (Keyboard.GetState().IsKeyDown(Keys.Space))
isLocked = !isLocked;
}
protected void UpdateInputMouse()
{
MouseState mState = Mouse.GetState();
if (!isLocked)
{
modelRotationY -= (mState.X - 400) * 0.0002f;
modelRotationX -= (mState.Y - 300) * 0.0002f;
}
}
#endregion
protected override void Draw(GameTime gameTime)
{
_graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// Copy any parent transforms.
Matrix[] transforms = new Matrix[_model.Bones.Count];
_model.CopyAbsoluteBoneTransformsTo(transforms);
// Draw the model. A model can have multiple meshes, so loop.
foreach (ModelMesh mesh in _model.Meshes)
{
// This is where the mesh orientation is set
// as well as our camera and projection.
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index]
* Matrix.CreateRotationY(modelRotationY)
* Matrix.CreateRotationX(modelRotationX)
* Matrix.CreateTranslation(modelPosition);
effect.View = Matrix.CreateLookAt(
cameraPosition,
Vector3.Zero,
Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f),
1.33f,
1.0f,
10000.0f);
}
// Draw the mesh, using the effects set above.
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
III-D-4. Le namespace « Storage »▲
III-D-4-a. Description▲
L'API de gestion du stockage permet au développeur de lire et écrire des données telles que la progression du joueur, les meilleurs scores, etc.
Le système de fichiers de la Xbox 360 étant très différent de celui d'un PC fonctionnant sous Windows, il était alors nécessaire d'avoir une API qui uniformalise ces opérations ce qui est chose faite à travers cette API.
Il est en effet possible d'effectuer des opérations de stockage sur les cartes mémoire ou le disque dur de la Xbox 360 en fonction du profil du joueur, tout comme sur le disque dur dans un système Windows.
On peut bien sûr, dans le cadre d'un jeu Windows, utiliser System.IO, mais cela n'est pas possible sur la Xbox 360 pour des raisons de sécurité, et est donc à éviter pour conserver la possibilité multiplateforme du jeu.
III-D-5. Le namespace « math »▲
III-D-5-a. Description▲
L'API math propose des types souvent utiliser dans la programmation de jeu vidéo, ces types sont par exemple les Vector2, Vector3, Vector4, Matrix, Plan et Ray.
Tandis que les premiers types sont utilisés pour des calculs, les deux derniers correspondent à des types principalement utilisés dans les traitements physiques :
Plan : plan de collision déterminé par ses dimensions et son orientation décrite par son vecteur normal (perpendiculaire à la face) ;
Ray : vecteur dans l'espace, décrit par une direction, il permet de vérifier simplement s'il existe une collision avec un élément sur cette trajectoire.
On retrouve également les « bounding volumes » qui sont conteneurs utiles pour la détection de collision entre éléments du jeu vidéo, parmi ceux-ci figurent :
les « Bounding Boxes » : boites englobant les objets ;
les « Bounding Spheres » : sphères englobant les objets ;
les « Bounding Frustum » : la caméra détermine une limite englobante par l'espace imposé par son champ de vision, les objets entrent en collision avec les bords du champ de vision.