Latest article: Exporter des données depuis Excel

Afficher un sprite

Dans le précédent article nous avons préparé notre projet. Cet article a pu paraitre un peu compliqué et rebutant, je le conçois. C’est parce qu’il montre une façon non conventionnelle d’installer XNA.

Rassurez-vous, ce qui est fait n’est plus à faire et cet article (ainsi que ceux qui suivront) seront plus simples et plus intéressants.

Maintenant que vous voilà rassurés, place à la 2D !

Notre but dans cette étape est de gérer l’affichage dans notre programme et d’afficher une image à l’écran.

Cela n’a rien de compliqué, bien au contraire.

La théorie

Avant d’implémenter quoi que ce soit nous devons comprendre comment fonctionne un jeu.

Shéma montrant une boucle de jeu classique

Un jeu est en perpétuelle évolution, il est sans arrêt mis à jour. L’emploi d’une “boucle infinie” qui ne s’arrête que lorsque le jeu est quitté est la base de tout jeu vidéo.
Voyons les différentes étapes en détails :

Initialisation : c’est dans cette étape que nous chargeons nos ressources : images, sons, modèles 3D, etc. Nous préparons le terrain pour la suite, c’est le chargement du jeu.

Traitements : la gestion du jeu en lui même. C’est cette partie qui s’occupera de prendre en charge les contrôles du jeu. C’est là qu’intervient toute la logique du jeu, comme l’IA par exemple.

Affichage : maintenant que la situation du jeu est à jour, il faut que le joueur en ai un retour. C’est le but de l’affichage : envoyer au joueur une image qui reflète la situation actuelle. C’est sur cette partie que nous allons nous concentrer cette fois-ci.

Libération : sous entendu, des ressources. On fait le ménage en quelque sorte, on passe le balais après la fête. Il s’agit de détruire toutes les ressources chargées et de rendre la main au système.

Si vous regardez bien, la partie “libération” mise à part, cela correspond exactement aux trois méthodes de l’objet GameManager créer précédemment :

protected override void Initialize ()
{
	base.Initialize ();
}

protected override void Update ( GameTime gameTime )
{
	base.Update ( gameTime );
}

protected override void Draw ( GameTime gameTime )
{
	base.Draw ( gameTime );
}

XNA s’occupe donc déjà de mettre en place cette boucle de jeu, tout ce que nous avons à faire c’est compléter ces trois fonctions.

Il sera peut être nécessaire de rajouter une méthode “Delete” à notre objet GameManager, mais pour le moment le Garbage Collector du framework .NET fera très bien l’affaire.

Revenons-en donc à notre boucle infinie, et notamment à la partie “affichage”.

Pour bien comprendre comment marche l’affichage dans un jeu vidéo, il faut comprendre le double buffering.

Le principe du double buffering est simple : afficher une image prend du temps, on ne peut donc pas se permettre de l’afficher directement à l’écran. Ou alors le joueur verrait l’image se construire sous ses yeux, très rapidement, ce qui provoquerait un scintillement permanent des plus déplaisants.

Il faut donc tout dessiner sur un autre écran, caché, et permuter les deux lorsque tout est prêt. C’est le principe même du double buffering : dessiner sur le back buffer (la mémoire de la carte graphique) puis échanger avec le front buffer (l’écran).

Cette notion est très importante.

Une fois que les deux buffers ont été permutés et que l’image finale apparait à l’écran, il convient de dessiner la suivante. Pour cela nous devons d’abord effacer la précédente qui est restée dans le buffer.

Ensuite nous redessineront notre scène, puis nous invertirons de nouveau les buffers. Et bis repetita.

L’opération doit se faire au moins 24 fois par secondes pour que l’animation soit crédible et que l’œil la voie bien comme une animation, et pas une simple suite d’images. Toutefois, la fréquence de rafraichissement idéale est celle de l’écran. Le nombre d’images dessinées par seconde est alors appelé framerate ou FPS (frames per seconds).

C’est quelque chose que vous retrouverez tout le temps par la suite, le FPS est notamment un très bon indicateur de performances.

Notre fonction d’affichage se résume alors à :

  1. Effacer le contenu du back buffer
  2. Afficher nos images
  3. Dire à la carte graphique qu’il est temps d’intervertir les buffers

Par chance, XNA s’occupe tout seul de la dernière étape.

GraphicManager

Toujours a la recherche d’une architecture propre pour notre programme, nous allons créer une classe pour gérer l’affichage.

On va alors créer une classe GraphicManager :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ArticleXNA
{
    class GraphicManager
    {
	public GraphicManager ( GameManager game )
	{

	}
    }
}

C’est notre GameManager qui s’occupera de gérer le GraphicManager, il lui faudra donc une référence vers l’objet :

private GraphicManager _GraphicManager = null;

Et dans la méthode Initialize :

protected override void Initialize ()
{
_GraphicManager = new GraphicManager ( this );

base.Initialize ();
}

Nous passons une référence à notre game manager vers GraphicManager pour pouvoir créer un GraphicDeviceManager.

Cette classe est celle qui s’occupe de gérer le contexte graphique (GraphicDevice) sous XNA. Elle s’occupera notamment de le recréer si besoin est (par exemple lorsqu’on minimise le jeu en plein écran).

class GraphicManager
{

    public GraphicManager ( GameManager game )
    {
        _GDM = new GraphicsDeviceManager ( game );
    }

    private GraphicsDeviceManager _GDM = null;

}

C’est à cet objet que nous fournirons des informations telle la résolution du jeu, si on l’affiche en plein écran ou pas, etc.

_GDM.PreferredBackBufferWidth = 800;

_GDM.PreferredBackBufferHeight = 600;

_GDM.IsFullScreen = false;

_GDM.ApplyChanges ();

Notre fenêtre devrait désormais avoir une taille de 800*600 pixels.

L’affichage : SpriteBatch

De nos jours, la 2D n’est plus que de la 3D sans profondeur. La 2D est donc gérée exactement comme la 3D par la carte graphique, à l’exception près que la perspective est remplacée par une projection orthogonale (concrètement, la caméra fait face à la scène).

Heureusement, XNA nous simplifie la tâche en nous proposant de faire de la 2D facilement, sans connaissances en 3D.

Tout cela se fait par l’intermédiaire de la classe SpriteBatch.

Nous allons donc rajouter une instance de SpriteBatch dans notre GraphicManager :

private SpriteBatch _SpriteBatch = null;

Et dans le constructeur :

public GraphicManager ( GameManager game )
{
    _GDM = new GraphicsDeviceManager ( game );
    _GDM.PreferredBackBufferWidth = 800;
    _GDM.PreferredBackBufferHeight = 600;
    _GDM.IsFullScreen = false;
    _GDM.ApplyChanges ();

    _SpriteBatch = new SpriteBatch ( _GDM.GraphicsDevice );
}

Pour afficher un sprite, il faut le faire entre les instructions Begin() et End() de SpriteBatch.

Un sprite est une texture (une image) que l’on affiche à une position donnée, pour charger une texture avec XNA, on a deux solutions :

  1. Passer par le Content Pipeline, l’avantage est que c’est portable sur XBox 260, le problème, c’est game studio (il faut rajouter l’image à sa solution Visual Studio…)
  2. Pour Windows uniquement : Texture2D.FromFile ( _GDM.GraphicsDevice , “texture.jpg” )

Par exemple :

Texture2D TextureLogo = Texture2D.FromFile ( _GDM.GraphicsDevice , "logo.png" );

Puis pour l’afficher :

_SpriteBatch.Begin ();
_SpriteBatch.Draw ( TextureLogo , new Vector2 () , Color.White );
_SpriteBatch.End ();

On spécifie la position en second argument, et la couleur qui agira comme un filtre.

La position est définie par ses composantes X (abscisses) et Y (ordonnées) de la façon suivante :

Le système de coordonnées pour un écran 2D

Avec le code suivant, on obtient :

Notre seconde application qui affiche un sprite à l'écran

Voici le code source complet (mais temporaire) :

GameManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace ArticleXNA
{
    class GameManager : Game
    {

        public GameManager ()
        {
            _GraphicManager = new GraphicManager ( this );
        }

        protected override void Initialize ()
        {
            Window.Title = "Article XNA";
            IsMouseVisible = true;

            _GraphicManager.Initialize ( 800 , 600 , false );

            base.Initialize ();
        }

        protected override void Update ( GameTime gameTime )
        {
            base.Update ( gameTime );
        }

        protected override void Draw ( GameTime gameTime )
        {
            _GraphicManager.Clear ();

            base.Draw ( gameTime );
        }

        private GraphicManager _GraphicManager = null;

    }
}

GraphicManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ArticleXNA
{
    class GraphicManager
    {

        public GraphicManager ( GameManager game )
        {

            _GDM = new GraphicsDeviceManager ( game );

        }

        public void Initialize (int width , int height , bool fullscreen)
        {
            _SpriteBatch = new SpriteBatch ( _GDM.GraphicsDevice );

            _GDM.PreferredBackBufferWidth = 800;
            _GDM.PreferredBackBufferHeight = 600;
            _GDM.IsFullScreen = false;

            _GDM.ApplyChanges ();

            // Cela n'a en pratique rien à faire dans le GraphicManager, il est ici pour l'exemple !
            TextureLogo = Texture2D.FromFile ( _GDM.GraphicsDevice , "logo.png" );
        }

        public void Clear ()
        {
            _GDM.GraphicsDevice.Clear ( Color.CornflowerBlue );

            // Cela n'a en pratique rien à faire dans le GraphicManager, il est ici pour l'exemple !
            _SpriteBatch.Begin ();
            _SpriteBatch.Draw ( TextureLogo , new Vector2 () , Color.White );
            _SpriteBatch.End ();

        }

        private GraphicsDeviceManager _GDM = null;
        private SpriteBatch _SpriteBatch = null;

        // Cela n'a en pratique rien à faire dans le GraphicManager, il est ici pour l'exemple !
        private Texture2D TextureLogo = null;

    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ArticleXNA
{
    class Program
    {
        static void Main ( string[] args )
        {
            try
            {

                GameManager game = new GameManager ();

                game.Run ();

            }
            catch ( Exception e )
            {
                Console.WriteLine ( e.ToString () );
            }
        }
    }

Discussion

Reply