Astuces DotNet (Sébastien Courtois)

17/02/2013

[SharpDX] Direct2D/Direct3D 10/11 Interoperability

Filed under: 3D, DirectX, Intermediaire, Jeux vidéos — Étiquettes : , , , , , , , — sebastiencourtois @ 22:48
Aparté de l’auteur de ce blog

Plus d’un an de silence sur ce blog depuis mon dernier post. Cela s’explique par beaucoup de changement. Tout d’abord, l’abandon de XNA par Microsoft (désormais officiel) m’ont fait me poser beaucoup de questions sur les technologies managées liées aux jeu vidéos que j’allais maintenant blogguer. Heureusement des initiatives comme SlimDX et SharpDX ont permis aux orphelins de XNA comme moi de trouver un alternative pour continuer a faire du jeu vidéo en .NET. De plus, cela fait plusieurs mois que je ne suis pas satisfait de la façon de présenter l’information sur ce blog et je cherche a une nouvelle alternative pour diffuser mes tutoriaux/exemples de code (si vous avez des idées, n’hésitez pas). Enfin, j’ai aussi changé de travail et de pays. Apres un an et demi a San Francisco, je suis maintenant a Montréal travaillant pour Ubisoft Montréal (Assassin’s Creed, Far Cry, Watch Dogs…) en tant que programmeur outils.

 

[EDIT 19/02/13]

Code fonctionnant en DirectX 10/11 Windows 7/8

Les explications techniques se trouvent dans l’article ci-dessous. Dans les différences avec les explications qui suivent se situe sur les classes utilise. Le render target 2d correspond au device context de Direct2d. Cet exemple fonctionne avec les dll SharpDX fournis par NuGet.

Constructeur :

_factory2D = new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded, DebugLevel.Information);
Surface surface = swapChain.GetBackBuffer<Surface>(0);
RenderTargetProperties renderTargetProperties = new RenderTargetProperties
    {
        Usage = RenderTargetUsage.None,
        DpiX = 96,
        DpiY = 96,
        PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm,AlphaMode.Premultiplied),
        MinLevel = FeatureLevel.Level_10,
        Type = RenderTargetType.Hardware
    };
_renderTarget2D = new RenderTarget(_factory2D, surface, renderTargetProperties);
_factoryDW = new SharpDX.DirectWrite.Factory(FactoryType.Isolated);

Dessin :

_renderTarget2D.BeginDraw();
_renderTarget2D.DrawText(s, s.Length, _format, new RectangleF(950, 50, 1280, 300), _colorBrushYellow, DrawTextOptions.None, MeasuringMode.GdiClassic);
_renderTarget2D.EndDraw();

Prérequis pour ce tutorial

Afin de fonctionner, vous devez avoir :

  • Une machine sous Windows 8
  • Visual Studio 2012
  • SharpDX   (ATTENTION : NE PAS UTILISER LE PACKAGE Nuget. Pour un raison que je ne connais pas, il ne fournit pas les DLL Windows8. Celle ci se trouve dans le répertoire ‘Bin\Win8Desktop-net40’ ou vous aurez installer SharpDX.)
  • Les références a ajouter sont :
    • SharpDX
    • SharpDX.Direct2D1
    • SharpDX.Direct3D11
    • SharpDX.DXGI
  • Avoir un pipeline de rendu 3d DirectX 11 fonctionnant déjà de façon autonome (si vous n’en avez pas, vous pouvez utiliser mon pipeline de test).

Introduction

Direct2D et Direct3D sont deux technologies de DirectX permettant l’affichage de dessins 2D et de modèles 3D. Le problème est que Microsoft n’a jamais donné de façon simple  de les faire communiquer depuis la version DirectX 11. En effet, alors que Direct3d 11 était sorti, Direct2D devait toujours dépendre de DirectX 10.1 pour fonctionner ce qui rendait complexe une interaction entre les deux. Certains (comme Alexandre MUTEL – Createur du SharpDX) ont parfois trouvé des solutions de contournement mais cela restait complexe et lourd a mettre en place dans un architecture de moteur 3d déjà existante.

DirectX 11.1 permet de faire un pont entre Direct3d 11 et Direct2d1 en utilisant DXGI. DXGI (DirectX Graphics Infrastructure) est un modèle d’abstraction du driver de la carte graphique et se trouve être la base pour l’ensemble des technologies graphiques DirectX.

dxgi

L’idée va être d’utiliser le Device et la SwapChain créés par Direct3D et les “convertir” en Device/SwapChain DXGI pour être utilisé par Direct2d. Le but étant de faire écrire Direct2d dans le même backbuffer que Direct3d. Tout cela en étant le moins intrusif possible dans le code Direct3d existant.

 

Modification du code Direct3d

Comme indiqué précédemment, nous partons d’un pipeline Direct3d existant et fonctionnant de façon autonome. Si vous avez téléchargé mon pipeline de test, il s’agit de la classe Renderer3d.cs. Si vous lancez le programme, cela vous donne le cube tournant suivant.

cube

Première chose a faire est d’autoriser le support du device Direct3d avec le modèle BRGA utilisé par Direct2d. Pour cela, il suffit de rajouter le DeviceCreationFlags ‘BgraSupport’ lors de la création du Device 3d.

SharpDX.Direct3D11.Device.CreateWithSwapChain(driverType,
                            DeviceCreationFlags.Debug | DeviceCreationFlags.BgraSupport,
                            levels,
                            desc,
                            out _device,
                            out _swapChain);

Ensuite, il est nécessaire de créer les DXGI Device et Swapchain depuis ceux de Direct3d. Cela se réalise en faisant un QueryInterface<>().

_dxgiDevice = _device.QueryInterface<SharpDX.DXGI.Device>();
_dxgiSwapChain = _swapChain.QueryInterface<SharpDX.DXGI.SwapChain>();

Avec ces informations, nous pouvons maintenant initialiser un device Direct2d a partir du pipeline Direct3d.

 

Initialisation de Direct2d

Comme Direct3d 11, Direct2d utilise un device et un device context pour communiquer avec la carte graphique. Nous allons ajouter en plus un lien entre la Surface Direct3d et le bitmap sur lequel nous allons écrire en Direct2d.

_device = new SharpDX.Direct2D1.Device(dxgiDevice);
_deviceContext = new DeviceContext(_device, DeviceContextOptions.None);
Surface surface = swapChain.GetBackBuffer<Surface>(0);
BitmapProperties1 bitmapProp = new BitmapProperties1(new PixelFormat(Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied), 96, 96, BitmapOptions.Target | BitmapOptions.CannotDraw);
_target = new Bitmap1(_deviceContext, surface, bitmapProp);

Nous récupérons ainsi la Surface d’écriture du DXGI SwapChain de Direct3d (i.e le back buffer ou va écrire Direct3d). Cela nous permet de créer un Bitmap Direct2d qui va écrire directement dans le back buffer Direct3d.

Attention : Le BitmapProperties1 PixelFormat doit correspondre au PixelFormat utilisé lors de la création du Device/SwapChain Direct3d (SwapChainDescription.ModelDescription). Si ce n’est pas le cas, vous aurez un exception “The parameter is incorrect.” lors de la creation du bitmap.

 

Dessin de Direct2d

A partir du moment ou Direct2d a été initialisé, il suffit d’indiquer au Device Context Direct2d ou l’on souhaite dessiner avec la propriété Target. La suite reste du code Direct2d classique.

_deviceContext.Target = _target;
_deviceContext.BeginDraw();
_deviceContext.DrawText(s, s.Length, _format, new RectangleF(950, 50, 1280, 300), _colorBrushYellow, DrawTextOptions.None, MeasuringMode.GdiClassic);
_deviceContext.EndDraw();

Remarque : Le dessin Direct2D se fera par dessus le dessin présent dans le backbuffer.  Si vous souhaitez faire un HUD devant la scène 3d, il est nécessaire de dessiner la 2d après la 3d. Dans le cas contraire, votre dessin 2d sera caché par le dessin 3d par superposition. La méthode Present() de Direct3d (qui envoie le backbuffer a l’écran) doit être réalisé après lorsque l’ensemble des dessins sont terminés.

Si vous investissez un peu de temps, vous pouvez avoir une HUD de diagnostic comme dans la démo que j’ai réalisé pour ce tutorial.

cubefps

Vous pouvez telecharger le code du tutorial ici.

Publicités

01/02/2012

[KinectSDK] Affichage des flux vidéo couleur et profondeur de Kinect dans une fenêtre WPF avec MVVM

Filed under: .NET, C#, Débutant, Jeux vidéos, Kinect — Étiquettes : , , , , , — sebastiencourtois @ 05:46

Prérequis pour ce tutoriel :

  • Kinect SDK doit être installé sur votre machine
  • Coding4Fun Kinect Toolkit (la dll utilisée est fournie dans le code exemple a la fin de cet article).
  • Kinect et un câble USB permettant de le connecter à un PC (seules les versions de Kinect achetée séparément de la console en ont un si ma mémoire est bonne. Si vous n’avez pas de câble Kinect/USB, vous devriez pouvoir en trouver sur le Microsoft Store).
  • Créer un projet Windows Application WPF et ajouter les références Microsoft.Research.Kinect (dans GAC) et Coding4Fun.Kinect.Wpf.

Suite à la présentation rapide de Kinect et de son SDK, nous allons maintenant rentrer dans le vif du sujet en créant une application WPF dans lequel nous allons afficher le flux couleur et profondeur de Kinect.

Afin de permettre la réutilisation de ce code, nous allons utiliser une architecture MVVM composée d’un ViewModel pour la fenêtre et d’un « ViewModel » Kinect afin de pouvoir réutiliser la partie Kinect indépendamment dans d’autres projets.

Les fichiers du projet sont :

  • MainWindow.xaml : Fichier xaml représentant la fenêtre.
  • MainWindowViewModel.cs : ViewModel de la fenêtre MainWindow
  • KinectVideoViewModel.cs : « ViewModel » pour acceder aux données Kinect. Une instance de cette classe est incluse dans MainWindowViewModel.

La fenêtre principale (MainWindow.xaml)

kinectdemoblog1

 

 

 

 

 

 

 

 

 

 

Screenshot de l’application finale

La fenêtre est une grille 2 lignes/2colonnes ou la première ligne contient les textes titres. La deuxième ligne contient des images contenant les flux vidéos (flux couleur à gauche et flux profondeur à droite).

Chacune des images voit leur source bindée sur des BitmapSource du KinectViewModel que nous verrons plus tard.

Le ViewModel est créé et lié au DataContext directement dans le fichier XAML.

1 <Window x:Class="KinectWpf.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:vm="clr-namespace:KinectWpf.ViewModel" 5 Title="Kinect Demo 1" Width="800" Height="600" > 6 <Window.DataContext> 7 <vm:MainWindowViewModel /> 8 </Window.DataContext> 9 <Grid> 10 <Grid.RowDefinitions> 11 <RowDefinition Height="Auto" /> 12 <RowDefinition Height="*" /> 13 </Grid.RowDefinitions> 14 <Grid.ColumnDefinitions> 15 <ColumnDefinition Width="*" /> 16 <ColumnDefinition Width="*" /> 17 </Grid.ColumnDefinitions> 18 <TextBlock Text="Color Stream" FontWeight="Bold" FontSize="15" VerticalAlignment="Center" Margin="5,0,0,0" /> 19 <TextBlock Text="Depth Stream" Grid.Column="1" FontWeight="Bold" FontSize="15" VerticalAlignment="Center" Margin="5,0,0,0" /> 20 <Image Grid.Row="1" Grid.Column="0" Source="{Binding Kinect.ColorStreamImageSource}" /> 21 <Image Grid.Row="1" Grid.Column="1" Source="{Binding Kinect.DepthStreamImageSource}"/> 22 </Grid> 23 </Window> 24

Le ViewModel de la fenêtre (MainWindowViewModel.cs)

 

1 public class MainWindowViewModel : INotifyPropertyChanged 2 { 3 public KinectViewModel _kinect; 4 public KinectViewModel Kinect 5 { 6 get { return _kinect; } 7 set 8 { 9 this._kinect = value; 10 OnPropertyChanged("Kinect"); 11 } 12 } 13 14 public MainWindowViewModel() 15 { 16 Application.Current.Exit += (sender, args) => { Clean(); }; 17 Kinect = new KinectViewModel(); 18 Kinect.Start(RuntimeOptions.UseColor | RuntimeOptions.UseDepthAndPlayerIndex); 19 } 20 21 public void Clean() 22 { 23 Kinect.Release(); 24 } 25 26 #region INotifyPropertyChanged Interface 27 28 public event PropertyChangedEventHandler PropertyChanged; 29 public void OnPropertyChanged(string name) 30 { 31 if (this.PropertyChanged != null) 32 this.PropertyChanged(this, new PropertyChangedEventArgs(name)); 33 } 34 35 #endregion 36 }

Comme la plupart des ViewModel, cette classe implémente INotifyPropertyChanged afin de pouvoir remonter les changements au moteur de binding de WPF. Nous ajoutons une propriété KinectViewModel afin qu’il soit accessible directement par binding à la vue MainWindow.

Deux méthodes du KinectViewModel seront utilisées ici. La première est la méthode Start qui est chargée d’initialiser et de lancer la capture des flux vidéos (les paramètres de cette méthode seront détaillés dans la suite). La seconde est la méthode Release() charge de libérer les ressources utilisant Kinect.

Le ViewModel Kinect (KinectViewModel.cs)

 

1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 private Runtime KinectRuntime; 4 5 public KinectViewModel(int id = 0) 6 { 7 if (Runtime.Kinects == null || Runtime.Kinects.Count <= id) 8 throw new InvalidOperationException("No kinect runtime found for id="+ id+"."); 9 this.KinectRuntime = Runtime.Kinects[id]; 10 } 11 [...] 12 }

Cette partie concerne l’initialisation du Runtime Kinect. La classe Runtime (provenant de Microsoft.Research.Kinect) référence, dans sa propriété Kinects sous la forme de Kinect[], l’ensemble des Kinects connectés à l’ordinateur. Notre ViewModel va permettre de sélectionner le Kinect souhaite grâce au paramètre id. Dans la plupart des cas, un seul Kinect est branché, on mettra une valeur par défaut id = 0. Une fois l’objet Kinect trouvé, on le conserve dans une variable KinectRuntime que nous utiliserons dorénavant.

 

1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 [...] 4 public void Start(RuntimeOptions options) 5 { 6 this.KinectRuntime.Initialize(options); 7 8 if ((options & RuntimeOptions.UseColor) == RuntimeOptions.UseColor) 9 InitColorStream(); 10 if ((options & RuntimeOptions.UseDepth) == RuntimeOptions.UseDepth) 11 InitDepthStream(); 12 if ((options & RuntimeOptions.UseDepthAndPlayerIndex) == RuntimeOptions.UseDepthAndPlayerIndex) 13 InitDepthAndPlayerIndexStream(); 14 if ((options & RuntimeOptions.UseSkeletalTracking) == RuntimeOptions.UseSkeletalTracking) 15 InitSkeletonTracking(); 16 } 17 [...] 18 }

Une fois le KinectVIewModel crée, il faut initialiser puis démarrer les flux vidéos. Pour cela, il est nécessaire de fournir à Kinect le type des flux que l’on souhaite utiliser. On utilise pour ça l’énumération RuntimeOptions définie par le SDK Kinect comme suit :

 

1 namespace Microsoft.Research.Kinect.Nui 2 { 3 [Flags] 4 public enum RuntimeOptions 5 { 6 UseDepthAndPlayerIndex = 1, 7 UseColor = 2, 8 UseSkeletalTracking = 8, 9 UseDepth = 32, 10 } 11 }

On initialise le Kinect en appelant la méthode Initialize tout en fournissant cette énumération (cette énumération ayant un attribut [Flag], il est possible de les combiner en utilisant les opérateurs binaires). La série de conditions qui suivent la méthode Initialize vont scannes ces RuntimeOptions afin de déterminer les types de flux voulus. Cela va nous permettre d’appeler des méthodes d’initialisation spécifiques à ces flux.

 

1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 [...] 4 #region Color Stream 5 6 public BitmapSource _colorStreamImageSource; 7 public BitmapSource ColorStreamImageSource 8 { 9 get { return _colorStreamImageSource; } 10 set 11 { 12 this._colorStreamImageSource = value; 13 OnPropertyChanged("ColorStreamImageSource"); 14 } 15 } 16 17 private void InitColorStream() 18 { 19 this.KinectRuntime.VideoFrameReady += KinectRuntime_VideoFrameReady; 20 this.KinectRuntime.VideoStream.Open(ImageStreamType.Video, 21 2, 22 ImageResolution.Resolution640x480, 23 ImageType.Color); 24 } 25 26 private void KinectRuntime_VideoFrameReady(object sender, ImageFrameReadyEventArgs e) 27 { 28 this.ColorStreamImageSource = e.ImageFrame.ToBitmapSource(); 29 } 30 31 #endregion 32 [...] 33 }

La partie gérant le flux vidéo couleur comprend 3 parties.

Une propriété bindable ColorStreamImageSource de type BitmapSource lie a un control Image de la MainWindow (<Image Grid.Row="1" Grid.Column="0" Source="{Binding Kinect.ColorStreamImageSource}" />).

Si la méthode Start, décrite ci-dessus, a une option RuntimeOptions.UseColor, alors la méthode InitColorStream ci-dessus sera appelée. Nous commençons par nous abonner à l’événement VideoFrameReady pour récupérer une image lorsque celle-ci est disponible. Une fois récupérer, il suffit de la passer a la méthode d’extension ToBitmapSource() (provenant du Coding4Fun Kinect Toolkit) pour créer le BitmapSource adéquat. Lorsque ce BitmapSource est assigné à la propriété ColorStreamImageSource, le databinding se déclenche pour mettre à jour l’image.

Le lancement du flux vidéo se fait grâce à la propriété VideoStream du KinectRuntime. En appelant la méthode Open avec les paramètres suivants, on lance la récupération des images.

Paramètres de la méthode Open()

Nom du parametre Type Description
streamType ImageStreamType Trois valeurs possibles sur ImageStreamType (Invalid,Video,Depth). Ce paramètre décrit le type de flux recuperer. Dans notre cas, on utilise la valeur Video.
poolSize int

Nombre d’images gardées en buffer par Kinect.

Les valeurs peuvent aller de 0 à 4.

2 est une valeur correcte pour la plupart des applications.

resolution ImageResolution

Résolution de l’image.

4 valeurs sont disponibles dans l’énumération ImageResolution, mais elles ne sont pas toutes disponibles pour tous les flux

Invalid

Resolution80x60 : capteur de profondeur uniquement. rarement utile.

Resolution320x240 : capteur de profondeur uniquement – 15 images/s.

Resolution640x480 : caméra vidéo uniquement – 30 images/s.

Resolution1280x1024 : caméra vidéo uniquement – 15 images/s – en réalité, il s’agit d’une résolution 1280×960.

En cas de valeurs erronées, la méthode Open() remonte une Exception avec le HRESULT=0x80070057

image ImageType

Type d’images/formatage des pixels de l’image.

DepthAndPlayerIndex : Formatage spécifique pour profondeur et player index (16 bits par pixel… 13 bits de profondeur/3 bits pour l’id du joueur. Nous en parlerons dans un prochain article.)

Color : Pixel couleur RGB sur 32 bits (l’ordre exact des couleurs pour chaque pixel est BGRX… 8 bits par couleur)

ColorYuv : Flux video 32 bits en YUV (voir “YUV Video” sur la MSDN pour plus d’informations)

ColorYuvRaw : Flux video 16 bits en YUV

Depth : Formatage spécifique pour la profondeur (16 bits par pixel)

 

1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 [...] 4 #region Depth/PLayerIndex Stream 5 6 public BitmapSource _depthStreamImageSource; 7 public BitmapSource DepthStreamImageSource 8 { 9 get { return _depthStreamImageSource; } 10 set 11 { 12 this._depthStreamImageSource = value; 13 OnPropertyChanged("DepthStreamImageSource"); 14 } 15 } 16 17 private void InitDepthStream() 18 { 19 this.KinectRuntime.DepthFrameReady += KinectRuntime_DepthFrameReady; 20 this.KinectRuntime.DepthStream.Open(ImageStreamType.Depth, 21 2, 22 ImageResolution.Resolution320x240, 23 ImageType.Depth); 24 } 25 26 private void InitDepthAndPlayerIndexStream() 27 { 28 this.KinectRuntime.DepthFrameReady += KinectRuntime_DepthFrameReady; 29 this.KinectRuntime.DepthStream.Open(ImageStreamType.Depth, 30 2, 31 ImageResolution.Resolution320x240, 32 ImageType.DepthAndPlayerIndex); 33 } 34 35 private void KinectRuntime_DepthFrameReady(object sender, ImageFrameReadyEventArgs e) 36 { 37 this.DepthStreamImageSource = e.ImageFrame.ToBitmapSource(); 38 } 39 40 #endregion 41 [...] 42 }

On réalise la même opération pour la profondeur que pour la vidéo couleur. La seule différence résulte dans le fait que le flux vidéo peut renvoyer juste la profondeur ou la profondeur + le player Index. Chacun des deux modes s’initialisant de façon différente, on utilise deux méthodes pour lancer la récupération des images. Les deux s’abonnent au même événements et l’image, dans les deux cas, sera récupéré dans la méthode KinectRuntime_DepthFrameReady.

 

1 public class KinectViewModel : INotifyPropertyChanged 2 { 3 [...] 4 #region Skeleton tracking 5 6 private void InitSkeletonTracking() 7 { 8 throw new NotImplementedException(); 9 } 10 11 #endregion 12 13 #region Cleaning 14 15 public void Release() 16 { 17 this.KinectRuntime.Uninitialize(); 18 } 19 20 #endregion 21 22 #region INotifyPropertyChanged Interface 23 24 public event PropertyChangedEventHandler PropertyChanged; 25 public void OnPropertyChanged(string name) 26 { 27 if (this.PropertyChanged != null) 28 this.PropertyChanged(this, new PropertyChangedEventArgs(name)); 29 } 30 31 #endregion 32 33 }

La méthode InitSkeletonTracking() sera décrite dans un prochain article.

La méthode Release permet de libérer les ressources utilisées par Kinect. Pour cela, il est nécessaire, lors de la fermeture du programme ou lorsque l’on ne souhaite plus utiliser Kinect, il est nécessaire d’appeler la méthode Uninitialize() du KinectRuntime.

Code Source de l’article

Conclusion

Vous savez maintenant comment afficher des flux vidéos couleur et profondeurs provenant de Kinect ainsi que détecter si des joueurs sont visibles (dans ce cas, ils apparaissent colorés dans l’image du capteur de profondeur).

D’autres articles sont prévus sur le traitement d’images ainsi que le tracking des joueurs.

kinectappliTest

Exemple de l’application de test Kinect finale avec vidéo couleur (haut gauche)/profondeur (bas gauche)/traitement d’images (Détecteur de contour Sobel en haut à droite)/tracking de joueurs en bas à droite).

Un grand merci aux deux modèles (Gilles Peron/@GillesPeron et Kim Macrez). On se souviendra de votre sacrifice Smile.

28/01/2012

[Kinect SDK] Presentation de Kinect SDK

Filed under: C#, C++, Débutant, Jeux vidéos, Kinect — Étiquettes : , , , , , , , — sebastiencourtois @ 08:44

Cet article est le premier d’une série d’articles sur le SDK de Kinect qui sort le 1er février. Ce SDK pour PC permet de dialoguer avec Kinect en .NET ainsi qu’en C++.

Au travers de cette série d’articles, vous apprendrez à récupérer les informations que fournissent les différents capteurs Kinect. Ce post d’introduction va présenter les différentes possibilités du SDK.

 

1. Kinect : Le Matériel

kinect-hardware

Kinect est composé de :

* une camera video RGB : Il s’agit d’une caméra couleur d’une résolution 640×480 (30 images/seconde) ou 1280×960 (15 images/seconde).

* une caméra de profondeur : Il s’agit d’un laser infrarouge (capteur de gauche) qui balaie la pièce de rayon infrarouge. Ces rayons sont visualisés par un capteur infrarouge (capteur de droite). La sensibilité de ce capteur s’ajuste automatiquement en fonction des conditions de la pièce (luminosité, obstacles…) Ce capteur de profondeur a une résolution de 320×240 ou 80×60 (30 images par secondes).

* 4 microphones chargés d’enregistrer le son et de réaliser des opérations de traitements du signal afin de supprimer les parasites (bruits extérieurs, éclos…). Ce système de microphone réparti sur l’ensemble du capteur permet de détecter la direction du son et ainsi reconnaitre le joueur qui a parlé.

* Un moteur pour incliner verticalement Kinect. Kinect peut aller de –27 degrés à + 27 degrés d’inclinaison. Cela permet au développeur d’adapter la vue si elle n’est pas adéquate. (Par exemple, si on voit le corps d’une personne, mais pas sa tête, le développeur peut incliner Kinect vers le haut afin d’avoir le corps en entier.)

 

2. Le SDK et la partie logicielle de Kinect

Le SDK Kinect est disponible sur le site : Kinect For Windows.

En plus des fonctions de bases de Kinect, le SDK embarque aussi :

* Le tracking de personnes (Skeleton Tracking) : Kinect peut indiquer si elle reconnait une personne et permet de tracker 20 points du corps en temps réel.

* En plus de la récupération d’un son sans parasites, le SDK fournit des aides pour la reconnaissance de la parole basée sur l’API Windows Speech (aussi utilise dans Windows Vista et 7 pour la reconnaissance de la parole).

kinect-voicelocator

Kinect Voice Locator : Un des samples du SDK

3. Les caractéristiques techniques et limitations de Kinect

Pour la caméra vidéo, Kinect fournit des images de 640×480 (30 images/seconde) ou 1280×960 (15images/seconde). Chaque pixel est codé en BGR sur 32 bits.

Pour le capteur de profondeur, Kinect fournit des images en 320×240 (30 images/seconde). Les données du capteur de profondeur sont codées sur 13 bits. Elle représente une distance en millimètre allant de 800 mm à 4000 mm. Lorsque le tracking des personnes est actif, les données de reconnaissance des personnes sont fusionnées avec les données de profondeur. Au final, chaque pixel correspond à 16 bits (13 pour la profondeur et 3 pour l’identification d’une personne). Il est possible de tracker 2 personnes en temps réel + 4 autres personnes « passives » (les personnes sont détectes, mais il n’y a pas de tracking de leur squelette).

L’une des difficultés est que les images venant de la caméra vidéo et du capteur de profondeur ne sont pas synchronisées par le SDK. Si le développeur souhaite faire du traitement d’images en utilisant les deux capteurs, il devra réaliser lui même la synchronisation. Cela peut s’avérer difficile, car les framerates entre les capteurs peuvent être différents et ne sont pas toujours constants.

Le moteur d’inclinaison de Kinect possède aussi ses limitations. En dehors de sa plage de valeurs (allant de –27 dégrées a +27 dégrées d’inclinaison verticale), il n’est pas possible d’envoyer des ordres a Kinect plus d’une fois par secondes ou plus de quinze fois toutes les 20 secondes. Cela est du a des limitations matériel, car le moteur n’est pas conçu pour bouger constamment. De plus, le fait de bouger la caméra prend du temps et peut perturber les capteurs Kinect.

La reconnaissance de la parole est entièrement basée sur des grammaires que le développeur doit fournir. Les grammaires correspondantes aux mots à reconnaitre. Cela peut être défini dans un fichier XML ou directement dans le code.

 

4. Conclusion

Nous avons fait un petit tour du propriétaire du composant Kinect et de ses possibilités. Dans les prochains articles Kinect, nous verrons comment récupérer les différentes informations (flux vidéo & profondeur, détections et tracking de joueurs…) et comment les utiliser (traitement d’images, dessin des squelettes…).

16/05/2011

[XNA/DirectX/OpenGL] Les bases de la programmation 3D

Filed under: 3D, Débutant, DirectX, Intermediaire, Jeux vidéos, OpenGL, Windows Phone, XBOX 360, XNA — Étiquettes : , , , , , , , , — sebastiencourtois @ 09:00

Je n’ai pas été très bavard ces derniers temps (mon dernier post remonte à 5 mois… Confus). Une des mes excuses est mon départ de France pour travailler pour une société américaine à San Francisco. Je suis bien installé maintenant et je vais me remettre à bloguer un peu plus Sourire.

Cela fait quelques temps que je fais des posts sur la 3D mais je n’arrivais jamais à expliquer les bases de la programmation 3D de façon simple et claire. Au travers de mes recherches dans ce domaine, je suis tombé sur le site de Bobby Anguelov. Plutôt spécialisé en Intelligence Artificielle, il a écrit des cours très agréables à lire sur DirectX 10 en C++. Il a aussi été enseignant à l’université de Pretoria (Afrique du Sud). C’est un de ses cours que j’ai décidé de traduire et de publier ici.

Pourquoi ? :

  • Le cours est claire, simple et bien illustré. Pourquoi refaire quelque chose qui a déjà été bien fait par quelqu’un d’autre ? Sourire
  • Le cours est vraiment grand public/débutant. Il suffit d’aimer la 3D et de connaitre les bases de la programmation pour pouvoir lire ce cours.
  • Le cours est plutôt générique et les informations contenues dans ces documents peuvent aussi s’appliquer à toutes les technologies 3D (DirectX/OpenGL/XNA) ==> Il n’y a aucun code source dans ce cours.

Note : La traduction a été complexe car de nombreux termes sont les termes utilisés dans les blogs/forums … Par conséquent, j’ai essayé un maximum de conserver ces termes tout en fournissant les traductions françaises.

Le cours se découpe en 6 chapitres :

  1. Introduction et historique de la 3D (30 Slides)
  2. Mathématiques appliquées à la 3D (37 Slides)
  3. Le pipeline graphique (27 Slides)
  4. L’étape applicative du pipeline graphique (23 Slides)
  5. L’étape géométrique du pipeline graphique (24 Slides)
  6. L’étape rastérisation du pipeline graphique (84 Slides)

Le cours complet en francais est disponible en format zip.

Si vous préférez la version anglaise, vous pourrez trouver les cours à cette adresse : http://takinginitiative.net/computer-graphics-course-slides/

NOTE : Si vous comptez utiliser ces documents pour les publier ou les enseigner, merci de nous prévenir. Nous ne voulons pas de droit d’auteurs sur ces documents Sourire mais nous souhaitons juste savoir où sont utilisés ces informations (par fierté Sourire) et être cité (lien vers nos blogs …). Si vous souhaitez la version Powerpoint, contactez nous (commentaire sur ce blog ou celui de Bobby).

  • La suite … ?

J’ai pas mal d’idées de posts pour la suite. Les statistiques de ce blog montrent que XNA 4 est très populaire en ce moment et j’envisage deux tutoriaux sur le HLSL en XNA 4. Je vais continuer les posts sur DirectX. Toutefois, je vais me focaliser sur DirectX 11. Enfin, j’ai récupérer le SDK de PhysX 3 (sorite ce mois ci) et je vais faire des cours d’introduction à cette API physique très populaire dans les jeux vidéos. Je vais aussi voir pour des tutoriaux sur le Cry Engine 3 quand le SDK sera publique (Août aux dernières nouvelles).

En ce qui concerne XNA sur Silverlight 5…. Je ferais des tutoriaux lors de la sortie finale car l’API actuelle n’est pas encore complète (fin d’année je pense).

N’hésitez pas à mettre des commentaires sur ce cours et si vous avez des idées de tutoriaux/sujets que vous voudriez voir approfondis.

18/09/2010

[XNA 4] Tutoriel 4 : Gestion des entrées : Clavier, Souris, Manette XBOX 360, Windows Phone Touch

Filed under: .NET, C# 4, Débutant, Jeux vidéos, XBOX 360, XNA — Étiquettes : , , , , , , , , , , , — sebastiencourtois @ 13:50

Jusqu’a présent, nous avons fait des applications démos tournant automatiquement. Même si certaines démos commerciales ou films d’animations peuvent être agréables à regarder, un jeu vidéo est intéressant grâce à l’interaction joueur/univers du jeu.

Code Source pour commencer ce tutoriel

Au travers de ce tutoriel, nous allons voir quatre outils d’interactions avec le joueur pour déplacer le tank du tutoriel précédent :

    • Clavier (PC & XBOX 360)
    • Souris (PC uniquement)
    • Touch (Windows Phone uniquement)
    • Manette XBOX 360 (PC / XBOX 360)

Remarques : Il est possible de brancher un clavier USB à une XBOX 360 et récupérer les entrées du clavier comme s’il s’agissait d’un pc. Pour le touch, XNA ne prend pas en charge que le toucher des écrans multitouch PC vendus dans le commerce.

Comme tout les codes de mise à jour du jeu non graphique, la gestion des interactions avec l’utilisateur se réalise dans la méthode Update() de la boucle de jeu.

  • Gestion du clavier

La première chose à faire est de récupérer l’état du clavier à un instant T. Cela se réalise par la méthode statique GetState() de la classe Keyboard

KeyboardState kbState = Keyboard.GetState();

Remarque : GetState() peut avoir un paramètre PlayerIndex (allant de Player.One à Player.Four). Cela est utilisé pour identifier la manette de l’utilisateur utilisant un Chatpad (clavier se branchant directement sur la manette). Pour le PC, on utilisera la version sans paramètre.

On récupère un structure KeyboardState contenant les informations sur les touches pressées ou non. KeyboardState contient 3 méthodes :

    • GetPressedKeys() : Méthode renvoyant un tableau des touches enfoncées (enum Keys)
    • IsKeyUp(Keys key) : Indique si la touche du clavier passée en paramètre est relaché (true si relâchée)
    • IsKeyDown(Keys key) : Indique si la touche du clavier passée en paramètre est enfoncée (true si enfoncée)

L’énumération Keys est une énumération de l’ensemble des touches du clavier (Keys.Up correspond à la flèche du haut, Keys.B à la touche B…). Il est possible d’utiliser les valeurs numériques correspondant à ces alias.

Partant des principes ci-dessus, on souhaite déplacer le tank en utilisant les touches du clavier et doubler la vitesse si, en plus on appuie sur la touche majuscule. On divisera par deux la vitesse si on appuie sur la touche control pendant le déplacement.

//Déplacement remise à 0 du déplacement
TankVelocity = Vector2.Zero;
//Vitesse maximun du tank
int TankMaximunSpeed = 2;

//Gestion du clavier
KeyboardState kbState = Keyboard.GetState();

if (kbState.IsKeyDown(Keys.Up))
    TankVelocity.Y -= TankMaximunSpeed;
if (kbState.IsKeyDown(Keys.Down))
    TankVelocity.Y += TankMaximunSpeed;
if (kbState.IsKeyDown(Keys.Left))
    TankVelocity.X -= TankMaximunSpeed;
if (kbState.IsKeyDown(Keys.Right))
    TankVelocity.X += TankMaximunSpeed;
if (kbState.IsKeyDown(Keys.LeftShift) || kbState.IsKeyDown(Keys.RightShift))
    TankVelocity *= 2;
if (kbState.IsKeyDown(Keys.LeftControl) || kbState.IsKeyDown(Keys.RightControl))
    TankVelocity /= 2;

La variable TankMaximunSpeed est une constante définie comme la vitesse maximum du tank dans une direction donnée. TankVelocity décrit le vecteur de déplacement du tank pour l’itération courante.

  • Gestion de la souris

Comme nous l’avons fait pour le clavier, la première tâche pour récupérer l’état de la souris.

//Gestion de la souris
MouseState mouseState = Mouse.GetState();

La structure MouseState contient trois propriétés importantes :

    • Propriétés *Button : Permet de choisir un des boutons de la souris et de savoir s’il est pressé ou non (Enumération ButtonState).
    • Propriétés X, Y : Indique la position (X,Y) du curseur sur l’écran de l’ordinateur (la position en dehors de la fenêtre de jeu est transmise).
    • Propriété ScrollWheelValue : Indique le nombre d’utilisation du scroll depuis le début du jeu.(positif si scroll vers l’avant).

Si l’on souhaite déplacer le tank vers le clic de l’utilisateur, on utilisera le code suivant (le bouton gauche de la souris doit rester enfoncé pour faire avancer le tank).

if (mouseState.LeftButton == ButtonState.Pressed)
{
    Vector2 PositionMouse = new Vector2(mouseState.X, mouseState.Y);
    TankVelocity = PositionMouse - TankPosition;
    TankVelocity.Normalize();
    TankVelocity *= TankMaximunSpeed;
}

Par défaut, XNA ne gère pas les doubles clics ou les clics longs. C’est au développeur de gérer ces cas. Nous verrons, dans un prochain post, comment réaliser ces opérations particulières.

  • Gestion du Touch Windows Phone

Pour utiliser le Touch, il faut tout d’abord vérifier si le matériel peut gérer cette capacité.

//Gestion du touch
TouchPanelCapabilities touchCap = TouchPanel.GetCapabilities();
if (touchCap.IsConnected)
{
   [...]
}

Un fois la compatibilité matérielle vérifié, on récupère l’état de l’interface tactile.

TouchCollection touches = TouchPanel.GetState();

La struncture TouchCollection est un tableau de TouchLocation contenant chacune un identifiant, une position et un état TouchLocationState parmi les 4  états suivants :

    • Pressed : Un nouveau point de pression est disponible
    • Released : Un point pression existant a disparu (l’utilisateur a retiré son doigt de l’écran
    • Moved : Un point de pression existant lors de l’itération précédente est toujours là et la position a été mise à jour
    • Invalid : Problème de reconnaissance des points de pressions (souvent un nouveau point de pression qui est pris pour un point existant).

Si l’on souhaite réaliser la même chose que l’exemple précédent avec la souris, on utilisera le code suivant : (on ne prend que le premier contact tactile pour simuler un curseur de souris).

//Gestion du touch
TouchPanelCapabilities touchCap = TouchPanel.GetCapabilities();
if (touchCap.IsConnected)
{
   TouchCollection touches = TouchPanel.GetState();
   if (touches.Count >= 1)
   {
      Vector2 PositionTouch = touches[0].Position;
      TankVelocity = PositionTouch - TankPosition;
      TankVelocity.Normalize();
      TankVelocity *= TankMaximunSpeed;
   }
}
XNA_WindowsPhoneInput 

L’utilisation de l’interface Touch du Windows Phone sera décrit plus en détail dans un prochain post (notamment l’utilisation de gestures et du multitouch).

  • Gestion de la manette XBOX 360

La manette XBOX 360 est accessible au travers de l’API XNA. La  première étape consiste à voir si une ou plusieurs manettes sont connectées puis de récupérer son état. Dans notre exemple, nous ferons cela juste pour le premier joueur.

GamePadState gamepadState = GamePad.GetState(PlayerIndex.One);
if (gamepadState.IsConnected)
{
    [...]
}

La manette XBOX 360 n’est pas la seule utilisable avec XNA. Un certain nombre d’autres manettes peuvent être vu par XNA (sur PC uniquement). Afin de vérifier les éléments disponibles sur ces manettes, la classe GamepadCapabilities permet d’indiquer la présence ou non des joysticks/boutons.

GamePadCapabilities gamepadCaps = GamePad.GetCapabilities(PlayerIndex.One);

if (gamepadCaps.HasLeftXThumbStick && gamepadCaps.HasLeftYThumbStick)
    TankVelocity = gamepadState.ThumbSticks.Left * TankMaximunSpeed;

else if (gamepadCaps.HasLeftXThumbStick && gamepadCaps.HasLeftYThumbStick)
    TankVelocity = gamepadState.ThumbSticks.Right * TankMaximunSpeed;

else if (gamepadCaps.HasDPadUpButton && gamepadCaps.HasDPadLeftButton && gamepadCaps.HasDPadRightButton && gamepadCaps.HasDPadDownButton)
{
    if (gamepadState.IsButtonDown(Buttons.DPadUp))
       TankVelocity.Y -= TankMaximunSpeed;
    if (gamepadState.IsButtonDown(Buttons.DPadDown))
       TankVelocity.Y += TankMaximunSpeed;
    if (gamepadState.IsButtonDown(Buttons.DPadLeft))
       TankVelocity.X -= TankMaximunSpeed;
    if (gamepadState.IsButtonDown(Buttons.DPadRight))
       TankVelocity.X += TankMaximunSpeed;
}

Pour notre exemple, nous utilisons le joystick gauche s’il existe sinon nous utilisons le joystick droit s’il existe sinon nous utilisons la croix directionnelle (si elle existe). Vous pouvez remarquer que le GamepadState a deux méthodes IsButtonDown/IsButtonUp qui fonctionne de la même façon que le clavier.

Les joysticks (Thumbstick) ont un fonctionnement légèrement différent.La propriété ThumbSticks regroupe l’ensemble des joysticks (2 joysticks au maximum : Left et Right). Chaque joystick est un Vector2 indiquant le décalage  (X,Y) par rapport au point central. Le point central est (0,0) et les “extrémités” étant –1 et 1. On obtient donc une valeur comprise entre –1 et 1 pour chacun des axes.

Certaines manettes (la manette 360 notamment) sont équipées de moteurs pour faire vibrer celle-ci lors de certains évènements. Pour cela, on utilisera la méthode statique SetVibration de la classe GamePad.

if (gamepadCaps.HasLeftVibrationMotor && gamepadCaps.HasRightVibrationMotor)
    GamePad.SetVibration(PlayerIndex.One, 0.7, 0.25);

Les valeurs possibles pour chacun des moteurs vont de 0 (aucune vibration) à 1 (vibration maximum).

Un utilitaire disponible sur le site XNA montre plus clairement la gestion de la manette.

  • Conclusion

Vous pouvez maintenant récupérer les informations provenant de l’utilisateur et modifier l’environnement du jeu en conséquence. Cela conclut la première partie des tutoriels 2D de base sur XNA. Les prochains tutoriels auront pour thème les animations, le son, l’IA, les effets 2D et la gestion de contenu avancé (Content Pipeline) pour arriver sur la 3D.

Code Source de ce tutoriel

  • Exercices pour aller plus loin
  1. Changer le système de déplacement clavier/manette afin que les touches gauche/droite fasse tourner le tank et que les touches haut/bas fasse avancer/reculer le tank en fonction de son orientation.
  2. Faire tourner le tank dans la direction du clic de souris (ou du touch) pendant le déplacement (l’avant du tank doit toujours être “face” à sa destination).

La solution sera fournis prochainement.

17/09/2010

[XNA 4] Tutoriel 3 : Affichage de texte

Filed under: .NET, C# 4, Débutant, Jeux vidéos, XBOX 360, XNA — Étiquettes : , , , , , , , — sebastiencourtois @ 13:39

Après avoir appris à afficher et déplacer des images, nous allons apprendre à écrire du texte à l’écran. Pour ce tutoriel, nous partirons de ce projet (Solution des exercices du Tutoriel 2).

  • Création d’une police de caractère (font)

Afin de pouvoir afficher du texte, il faut indiquer quel police de caractères (aussi appelé font), on souhaite utiliser. Pour cela, on ajoute un nouvel élément au projet Content (Les fonts sont généralement stockés dans le projet Content comme l’ensemble des ressources XNA).

xna_tuto3_1

On sélectionne un élément de type “SpriteFont” en indiquant un nom et on clique sur OK.

xna_tuto3_2

A l’instar des images, les fonts ont aussi un assetName visible et modifiable par la fenêtre Propriétés. 

Si l’on ouvre le fichier contenant la police de caractères, on obtient le fichier XML suivant :

<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
  <Asset Type="Graphics:FontDescription">
    <FontName>Kootenay</FontName>
    <Size>14</Size>
    <Spacing>0</Spacing>
    <UseKerning>true</UseKerning>
    <Style>Regular</Style>
    <CharacterRegions>
      <CharacterRegion>
        <Start>32</Start>
        <End>126</End>
      </CharacterRegion>
    </CharacterRegions>
  </Asset>
</XnaContent>

La balise FontName permet de définir le nom de la police utilisée. Les fonts disponibles sont uniquement des fonts de type TrueType (Liste). La communauté a créé des générateur de font personnalisé (Exemple : FontBuilder). Les plus utilisés sont souvent Arial ou Verdana.

La balise Size indique la taille du texte comme cela se fait dans les suites bureautiques.

La balise Spacing permet d’indiquer si l’on souhaite un espace entre les lettres (valeur en pixel).

La balise UseKerning permet de définir si l’on autorise le kerning. Pour faire simple, le kerning est le fait que deux lettres puissent “se superposer” (voir l’article et cette image qui décrit le kerning).

La balise Style indique si l’on souhaite mettre en gras/italique. Valeurs possibles : Regular, Bold, Italic ou Bold Italic.

La balise CharacterRegions défini les plages de valeurs ascii utilisable par la police (ici de 32 à 126 ce qui correspond au caractères de ‘ ‘ à ‘~’ en incluant les lettres en minuscules et majuscules, les chiffres ainsi que certains signes de ponctuations (cf tableau ASCII). Nous verrons qu’il est possible de rajouter d’autres plages.

  • Chargement de la police de caractères

Une fois défini, il est nécessaire de charger la police de caractères lors du lancement de l’application. Pour cela, on utilise le même principe que pour les textures.

SpriteFont textFont;

protected override void LoadContent()
{
    [...]
    this.textFont = Content.Load<SpriteFont>("MyFont");
}

Rappel : On utilise toujours le AssetName du fichier avec la méthode Load(). On doit donner aussi le type de sortie (SpriteFont pour les polices de caractères).

  • Affichage de texte

L’affichage du texte se déroule dans la méthode Draw() en utilisant le spriteBatch comme pour les textures.

Pour l’exemple, nous allons réutiliser l’exemple du chapitre précédent pour afficher en temps réel les coordonnées du tank, son angle de rotation ainsi que l’angle de rotation de son canon.

protected override void Draw(GameTime gameTime)
{
   GraphicsDevice.Clear(Color.CornflowerBlue);
   spriteBatch.Begin();

   string text1 = string.Format("Position du tank : X = {0}   Y = {1}",this.TankPosition.X,this.TankPosition.Y);
   spriteBatch.DrawString(this.textFont, text1, Vector2.Zero, Color.Red);
   string text2 = string.Format("Rotation du tank : {0}", TankRotation);
   spriteBatch.DrawString(this.textFont, text2, new Vector2(0,20), Color.DarkGreen);
   string text3 = string.Format("Rotation du canon : {0}", CanonRotation);
   spriteBatch.DrawString(this.textFont, text3, new Vector2(0, 40), Color.Yellow);

   spriteBatch.Draw(this.tank, TankPosition, null, Color.White, MathHelper.ToRadians(TankRotation), TankOriginPoint, 1.0f, SpriteEffects.None, 0);            
   spriteBatch.Draw(this.canon, TankPosition, null, Color.White, MathHelper.ToRadians(CanonRotation), new Vector2(canon.Width / 2 - 4.0f, canon.Height / 2), 1.0f, SpriteEffects.None, 0);
            
   spriteBatch.End();
   base.Draw(gameTime);
} 

On utilise la méthode DrawString () de spriteBatch. Cette méthode prend 3 paramètres :

  1. Police de caractères (SpriteFont)
  2. Position (en pixel à l’écran)
  3. Couleur du texte.

Le résultat en image :

xna_tuto3_3

  • Si je veux rajouter le ° à coté de l’angle.

Si vous tentez de faire le changement suivant :

string text2 = string.Format("Rotation du tank : {0} °", TankRotation);
spriteBatch.DrawString(this.textFont, text2, new Vector2(0,20), Color.DarkGreen);

Vous allez rencontre l’erreur : Argument Exception : The character ‘°’ (0x00b0) is not available in this SpriteFont. If applicable, adjust the font’s start and end CharacterRegions to include this character. Parameter name: character

Cela est dû au fait que la balise CharacterRegions ne contient pas ce  caractère dans sa liste de caractère disponible. (‘°’ a le code hexa 0xb0 => 176 en décimal). Il suffit de rajouter une range dans le fichier xml pour ajouter le caractère.

<CharacterRegions>
      <CharacterRegion>
        <Start>32</Start>
        <End>126</End>
      </CharacterRegion>
      <CharacterRegion>
        <Start>176</Start>
        <End>176</End>
      </CharacterRegion>
 </CharacterRegions>

Si vous recompilez et lancez l’application, le symbole apparait.

xna_tuto3_4

  • Conclusion

Vous pouvez maintenant écrire du texte à l’écran. Dans le prochain article, nous verrons l’utilisation du clavier/souris/manette XBOX 360 et Touch sur Windows Phone.

Code de ce tutorial

14/09/2010

[XNA 4] Tutoriel 1 : Création et structure d’un projet XNA

Filed under: .NET, C# 4, Débutant, Jeux vidéos, XBOX 360, XNA — Étiquettes : , , , , , , , — sebastiencourtois @ 13:57

En l’honneur de la sortie de XNA 4, j’ai décidé de réaliser une série de posts sur le sujet. J’essaierais d’être le plus général possible afin que l’ensemble des codes fonctionnent sur les plateformes cibles de XNA 4 : PC, XBOX 360, Zune et le nouveau Windows Phone. Lorsque cela ne sera pas le cas, cela sera indiqué dans le titre ou dans les premières lignes des posts.

Ces tutoriels seront à destination des débutants en programmation 3D.Toutefois, il est nécessaire d’avoir des bonnes notions de C#.

  • Commençons par le début : XNA ?

D’après les forums/FAQ, XNA voudrait dire : “XNA’s Not Acronymed”. Vu comme ça on n’est pas plus avancé (un peu comme le GNU is Not Unix chez les adorateurs de pingouins) :).

XNA est une plateforme (Framework) de développement de jeu vidéo 2D/3D à destination du PC, de la XBOX 360 et d’appareils portables (Zune/Windows Phone pour l’instant). Cette plateforme est en fait une surcouche d’une API de développement de jeu vidéo utilisés dans la plupart des jeux vidéo commerciaux actuels : DirectX.

XNA a été créé pour :

  • Fournir un environnement de développement simplifié pour réaliser des applications graphique 2D/3D rapidement sans trop se préoccuper des couches plus bases (notamment Win32/DirectX qui sont assez indigeste).

  • Fournir un environnement entièrement managé pour la création de jeu vidéo (DirectX étant uniquement axé C++). Un projet de DirectX managé a été lancé par Microsoft, il y a quelque années, mais il fut arrêté au profit de XNA. Un projet communautaire a été relancé sur le même principe : SlimDX. L’idée est de fournir un wrapper directement sur les méthodes DirectX afin de permettre l’utilisation de fonctionnalités non disponibles dans XNA.

  • Permettre de déployer l’application sur plusieurs plateformes différentes (PC,XBOX 360,mobiles) sans avoir à (trop) changer le code.

Pour atteindre ces objectifs, XNA a limité les fonctionnalités disponibles. En effet, XNA ne permet d’utiliser que DirectX 9 afin d’être compatible avec l’ensemble des plateformes cibles (Il faut savoir que DIrectX9 est sorti en Août 2005  et nous sommes actuellement à DirectX 11sorti en Octobre 2009). De plus, XNA étant une plateforme managée, le garbage collector et les mécanismes internes de .NET seront activés et pourront ralentir l’application (comparé à une application DX C++). Toutefois, le niveau des applications XNA restent correcte pour la création de jeu (voir les démos présentées sur le concours Imagine Cup par des étudiants : http://www.xna-connection.com/post/Imagine-Cup-2010-Les-finalistes-section-Game-Design).

  • Fini les discours commerciaux, passons à la pratique :

Afin de développer sur XNA 4, il est nécessaire d’installer :

Une fois installé, lancez Visual Studio, puis aller dans Fichier>Nouveau Projet. Dans l’écran “Nouveau projet”, allez sur Visual Studio > XNA Game Studio 4 puis choisissez “Windows Game (4.0)”. N’oubliez pas de choisir un nom de projet (“Tutorial2D” dans ce post) puis cliquez sur OK.

xnatuto1

 

 

 

 

 

 

 

 

 

 

 

Une fois créé, vous devriez avoir l’arborescence suivante :

xnatuto2

Le projet Tutorial2D est le projet principal contenant le point d’entrée ainsi que toute les références de votre application. Il est composé de 4 fichiers :

  • Game.ico : Icone du jeu (visible dans le coin en haut à gauche de la fenêtre)
  • Game1.cs : Code du jeu (voir suite du tutorial)
  • GameThumbnail.png : Vignette représentant le jeu (visible dans le menu XBOX 360)
  • Program.cs : Point d’entrée du programme.

Le projet Tutorial2DContent est un projet dit “de contenu”. Il accueillera uniquement les ressources (textures, modèles 3D, sons…) qui seront utilisées dans le jeu.

  • Analysons le point d’entrée du jeu : Program.cs
using System;

namespace Tutorial2D
{
#if WINDOWS || XBOX
    static class Program
    {
        static void Main(string[] args)
        {
            using (Game1 game = new Game1())
            {
                game.Run();
            }
        }
    }
#endif
}

Il s’agit d’un point d’entrée classique. Le .NET lance l’application en appelant Program.Main. On crée une instance de Game1 et on appelle “le point d’entrée” du jeu. Le #if Windows || XBOX sont des commandes de préprocesseurs pour indiquer que le code qui encadré est uniquement pour PC/XBOX. Pour une application mobile, le code est différent et fera l’objet d’un prochain post.

  • Déroulement d’un programme XNA

Une fois la méthode Game1.Run() appelé dans Program.cs, XNA va rentrer dans un processus défini comme suit :

xnatuto3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • Initialize() : Zone d’initialisation du jeu. C’est ici que se trouve le chargement et la configuration des modules non graphiques.
  • LoadContent() : Zone de chargement des données du jeu (textures, effets(shaders), modèles 3D…).
  • On rentre ensuite dans une boucle infini (la boucle de jeu). A intervalle régulier (60 fois par secondes au maximum par défaut), les deux méthodes suivantes vont être appelées dans cet ordre :
    • Update() : Mise à jour des données du jeu. Gestion du clavier/souris/gamepad, déplacement des joueurs, moteur physique …
    • Draw() : Affichage de l’ensemble des données graphiques. C’est ici que l’on indique à la carte graphique ce que l’on souhaite afficher à l’écran.
  • UnloadContent() : Lorsque l’on sort de cette boucle infini (En appelant la méthode Exit()), la méthode UnloadContent() est appelée afin de libérer la mémoire des objets dont on n’a plus besoin.

 

  • Game1.cs

L’ensemble des méthodes décrites ci-dessus se retrouvent dans la classe Microsoft.Xna.Framework.Game dont dérive Game1. Il est donc nécessaire de les surcharger afin de pouvoir créer son propre code.

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void Initialize()
    {
            

        base.Initialize();
    }

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
    }

    protected override void UnloadContent()
    {

    }

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();


        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);


        base.Draw(gameTime);
    }
}

Il est à noter que des appels aux classes parents sont réalisés à la fin de Initialize(), Update() et Draw(). Ces appels sont nécessaires au bon fonctionnement de l’application et doivent toujours se trouver à la fin des méthodes concernés. Par exemple, si vous commentez base.Initialize(), XNA ne fera pas l’appel à LoadContent() mais lancera la boucle de jeu directement (Update <=> Draw).

Deux propriétés sont utilisées dans cette classe :

 GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
  • Gestionnaire de carte graphique. Cette propriété permet la configuration de la carte graphiques ainsi que l’accès à celle-ci.

     

  • Cette propriété permet l’affichage de texte et de texture à l’écran.
  • Au sein de la méthode Update(), on remarque l’utilisation du GamePad.

      if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

    Ainsi, si l’on clique sur le bouton “back” (petit bouton blanc à gauche du bouton central), on sort de la boucle de jeu. La gestion des entrées utilisateurs est une des tâches à effectuer dans la méthode Update().

    Nous étudierons l’utilisation de l’ensemble de ces outils en détail dans les prochains posts.

    • Lancement de son premier jeu XNA

    Lorsque l’on lance le projet, on obtient un écran avec un fond bleu.

    xnatuto4

    Cela provient du code de la méthode Draw(). Lors de la première ligne, on demande à la carte graphique (GraphicsDevice) de vider la fenêtre et de mettre une couleur de fond CornFlowerBlue. (si vous souhaitez savoir pourquoi cette couleur en particulier est présente par défaut lorsque l’on crée un projet XNA, je vous conseille la lecture de ce post).

    • C’est déja fini ? non, ce n’est que le début.

    Maintenant que vous avez les bases sur le fonctionnement de XNA (désolé pas de code :(), les prochains posts seront dédiés à la création d’un jeu 2d multiplateforme (PC/XBOX360,Windows Phone). J’ai dans l’idée de faire un petit Space Invader mais si vous avez d’autres idées, n’hésitez pas à poster en commentaires.

    La suite de la séries XNA : Tutoriel 2 : Gestion des images

    Pour ceux qui veulent déjà faire de la 3D, des tutoriaux arriveront très bientôt. En attendant vous pouvez toujours aller sur les liens XNA suivants :

    • Français :

    http://msmvps.com/blogs/valentin/

    http://www.xna-connection.com/

    http://xna-france.com/

    http://www.xnainfo.com/

    http://msdn.microsoft.com/fr-fr/directx/default.aspx

    • Anglais :

    http://creators.xna.com/fr-FR/

    http://blogs.msdn.com/b/shawnhar/

    http://xna-uk.net/

    Créez un site Web ou un blog gratuitement sur WordPress.com.

    %d blogueurs aiment cette page :