Aller au menu - Aller au contenu

Icône Création d'une classe Sprite

Mise à jour : 31/08/2011
Difficulté : Facile Facile Durée d'étude : 3 heures Creative Commons BY-NC-SA
1 620 visites depuis 7 jours, dont 122 sur ce chapitre classé 81/786
Il est temps de faire une petite pause au niveau de l'apprentissage de XNA pour parler un peu d'organisation du code.
Même si vous connaissez sur le bout des doigts toutes les fonctionnalités de XNA (bonne chance :p ), si vous n'organisez pas un minimum votre code, non seulement il ne pourra pas être repris par d'autres personnes (embêtant pour des projets d'équipe) mais vous même aurez du mal à faire évoluer votre jeu.

C'est pourquoi nous allons voir rapidement comment profiter de la puissance de l'orienté objet en créant une classe Sprite qui regroupe globalement tout ce qu'on a pu voir jusqu'à maintenant :)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Interêt de faire une telle classe

Vous allez peut-être me demander pourquoi créer une classe, après tout c'est vrai on s'en est sorti très bien avec notre classe principale Game jusqu'à présent. Oui mais voila ! Dans notre jeu actuellement combien y a-t-il d'image ? Une seule ! Et on se retrouve déjà avec x variables nécessaires au dessin de cette image (position, déplacement, le Texture2D, etc..). Imaginez alors que notre jeu s'agrandisse et qu'il comporte plus de 20 images, on se retrouverait vite avec 100 variables à déclarer dans notre classe principale, ce n'est donc pas envisageable. ^^

La classe va nous permettre de créer un modèle abstrait qui représentera dans notre code un Sprite, c'est à dire une image et toutes ses fonctionnalités associées (déplacement, dessin, update, etc..) puisque TOUTES les images que nous utiliserons auront besoin de ces fonctionnalités sans quoi elles ne serviraient à rien.

Ensuite, dans la même optique que les fonctionnalités, toutes les images que nous utiliserons auront des caractéristiques communes que nous verrons en détail tout à l'heure, il est donc bon de faire la classe qui nous permettra de représenter un Sprite dans son ensemble à l'aide d'une seule variable.

Enfin, dernière chose, le fait de créer une classe nous donne une base pour une extensibilité de nos Sprites. En effet, si une fonctionnalité est manquante dans notre Sprite de base pour la fonction d'une image particulière (par exemple le saut), il sera aisé de l'ajouter en faisant hériter de Sprite et de réécrire une fonctionnalité pour l'étendre. :)

Les caractéristiques de notre classe

Bien, maintenant que nous avons un peu vu l'utilité de créer une classe, il n'y a plus qu'à se lancer ! :)
Une classe permet de regrouper l'ensemble des caractéristiques et des fonctionnalités d'objets communs (dans notre cas les sprites).

On va donc faire un exercice de réflexion qui va nous permettre d'établir avant de coder l'ensemble des besoins de notre classe pour ne pas avoir de problème plus tard. Évidemment ceci n'empêchera pas d'étendre les fonctionnalités de notre classe au fur et à mesure que l'on voit des choses. ;)

Les caractéristiques


On va commencer par énumérer les caractéristiques communes à tous nos Sprites. Je ne peux que vous conseiller de réfléchir d'abord par vous même, c'est un très bon exercice.

Voici ce que je propose (ce n'est évidemment pas la seule solution, il y en a de nombreuses valables).
  • Le Texture2D : je ne pense même pas avoir besoin de l'expliquer :p mais il s'agit de notre image chargée en mémoire.
  • La position : il s'agit évidemment de la position du coin supérieur gauche ou sera dessiné notre image.
  • La direction : ici aussi je pense que ce n'est pas compliqué, c'est la direction de déplacement de notre Sprite. Cependant à chaque modification de cet élément, on s'assurera qu'elle est normalisée pour les raisons que j'ai donnée dans un chapitre précédent. ;)
  • La vitesse : définira la vitesse de déplacement du Sprite dans la direction donnée par la caractéristique précédente.

Voici selon moi les caractéristiques indispensables à tout Sprite, on va maintenant voir de quelles fonctionnalités nous allons avoir besoin.

Les fonctionnalités


Dans le même principe que les caractéristiques de la classe, les fonctionnalités vont représenter ce que peuvent faire tous les Sprites, il s'agit concrètement des méthodes de la classe.
  • L'initialisation : permettra de mettre une valeur par défaut à toutes les caractéristiques vues précédemment.
  • Le chargement de contenu : permettra de charger l'image que l'on souhaite stocker dans notre Sprite.
  • La mise à jour : permettra entre autres de déplacer le sprite en fonction de ses caractéristiques de direction et de vitesse.
  • La gestion des entrées du joueur : permettra de gérer les entrées du joueur. Par défaut cette méthode ne contiendra rien mais elle sera utile pour les classes enfants.
  • Le dessin : permettra de dessiner notre sprite à la position indiquée dans ses caractéristiques.

Vous voyez que ça ressemble étrangement à comment fonctionne XNA. :lol: Ça nous permettra de bien organiser le tout en respectant l'architecture du Framework.

Il est grand temps de passer à l'écriture de cette classe et de son utilisation !

Le code de la classe

Création de la classe


Pour créer la classe, c'est très simple, il suffit de faire un clic droit sur le projet dans Visual Studio puis de faire Add > Class.

Création d'une classe


Une fenêtre apparait, il suffit de choisir un nom à votre classe puis de faire Add.

Création de clase 2


Vous devriez donc maintenant avoir ce code dans le nouveau fichier créé.

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CreationProjetXNA
{
    class Sprite
    {
    }
}


L'implémentation



La première chose à faire va être d'ajouter quelques clauses using à la suite de celles déjà présentes pour pouvoir utiliser le Framework XNA.
Pour faire simple on va reprendre celles qui sont dans le fichier FirstGame.cs et qui sont en relation avec XNA.

Code : C#
1
2
3
4
5
6
7
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

On ajoute ensuite les attributs et propriétés de notre classe qui correspondent en réalité aux caractéristiques que l'on a énumérées juste avant.

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Sprite
{
    /// <summary>
    /// Récupère ou définit l'image du sprite
    /// </summary>
    public Texture2D Texture
    {
        get { return _texture; }
        set { _texture = value; }
    }
    private Texture2D _texture;

    /// <summary>
    /// Récupère ou définit la position du Sprite
    /// </summary>
    public Vector2 Position
    {
        get { return _position; }
        set { _position = value; }
    }
    private Vector2 _position;

    /// <summary>
    /// Récupère ou définit la direction du sprite. Lorsque la direction est modifiée, elle est automatiquement normalisée.
    /// </summary>
    public Vector2 Direction
    {
        get { return _direction; }
        set { _direction = Vector2.Normalize(value); }
    }
    private Vector2 _direction;

    /// <summary>
    /// Récupère ou définit la vitesse de déplacement du sprite.
    /// </summary>
    public float Speed
    {
        get { return _speed; }
        set { _speed = value; }
    }
    private float _speed;
}

Et enfin, on y ajoute nos méthodes.

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/// <summary>
/// Initialise les variables du Sprite
/// </summary>
public virtual void Initialize()
{
    _position = Vector2.Zero;
    _direction = Vector2.Zero;
    _speed = 0;
}

/// <summary>
/// Charge l'image voulue grâce au ContentManager donné
/// </summary>
/// <param name="content">Le ContentManager qui chargera l'image</param>
/// <param name="assetName">L'asset name de l'image à charger pour ce Sprite</param>
public virtual void LoadContent(ContentManager content, string assetName)
{
    _texture = content.Load<Texture2D>(assetName);
}

/// <summary>
/// Met à jour les variables du sprite
/// </summary>
/// <param name="gameTime">Le GameTime associé à la frame</param>
public virtual void Update(GameTime gameTime)
{
    _position += _direction * _speed * (float)gameTime.ElapsedGameTime.TotalMilliseconds;
}

/// <summary>
/// Permet de gérer les entrées du joueur
/// </summary>
/// <param name="keyboardState">L'état du clavier à tester</param>
/// <param name="mouseState">L'état de la souris à tester</param>
/// <param name="joueurNum">Le numéro du joueur qui doit être surveillé</param>
public virtual void HandleInput(KeyboardState keyboardState, MouseState mouseState)
{
}

/// <summary>
/// Dessine le sprite en utilisant ses attributs et le spritebatch donné
/// </summary>
/// <param name="spriteBatch">Le spritebatch avec lequel dessiner</param>
/// <param name="gameTime">Le GameTime de la frame</param>
public virtual void Draw(SpriteBatch spriteBatch, GameTime gameTime)
{
    spriteBatch.Draw(_texture, _position, Color.White);
}

Je ne vais pas revenir en détail sur toutes les méthodes, elle ne sont pas dures à comprendre et on a déjà tout vu précédemment. ;)
Notez quand même que l'on utilise le mot clef virtual sur chacune de nos méthodes pour la simple raison que l'on aura certainement besoin de ré-implémenter certaines de ces fonctionnalités dans les classes enfants pour obtenir des comportements plus spécifiques.

Je vais juste dire un petit mot sur la méthode LoadContent et la méthode Draw puisque c'est assez nouveau ici.
On doit passer à LoadContent deux paramètres, un ContentManager et un string représentant l'asset name de l'image.

Il n'y a rien de compliqué mais on est obligé de faire ceci car la classe Sprite est "isolée" du contexte de la classe principale, il faut donc lui fournir le ContentManager qui va bien (l'attribut Content de la classe principale) et lui dire depuis l'extérieur quelle image charger (sinon tous les sprites auraient la même image donc peu intéressant).

Et pour la méthode Draw, vous vous posez surement une question si vous avez bien suivi le cours jusqu'à présent. :ange:

Eh ! Pourquoi il n'y a pas Begin() avant et End() après la méthode Draw() du SpriteBatch ??


Effectivement ^^ , ce n'est pas un oubli et il faudra les mettre quelque part. Cependant le spriteBatch.Begin() est une opération assez lourde et qui prend du temps à être faite donc il vaut mieux l'ouvrir une seule fois, dessiner tous les sprites et le refermer ensuite que de l'ouvrir et le fermer à chaque fois que l'on veut dessiner un sprite.

Ce n'est donc pas le sprite qui s'occupe d'ouvrir et fermer le SpriteBatch mais ce sera la méthode en charge de dessiner l'ensemble des sprites, dans notre cas la méthode Draw() de la classe FirstGame ! C'est aussi pourquoi on passe à la méthode Draw() du sprite le SpriteBatch qu'il doit utiliser pour se dessiner. :)


L'utilisation


Si vous avez bien compris l'implémentation de la classe alors l'utilisation sera un jeu d'enfant !

Vous pouvez enlever du fichier FirstGame.cs tout ce qui traite de Zozor puisqu'on va tout refaire en utilisant la classe Sprite.

On va donc commencer par créer notre Zozor, sauf que cette fois ci, ce n'est plus un simple Texture2D mais bien un Sprite. :D

Code : C#
1
private Sprite _zozor;

C'est ensuite très logique puisque l'on a respecté l'architecture de XNA, on le créé et on l'initialise dans ... Initialize() !

Code : C#
1
2
3
4
5
6
7
protected override void Initialize()
{
    _zozor = new Sprite();
    _zozor.Initialize();
            
    base.Initialize();
}

Puis on charge l'image de Zozor dans LoadContent en utilisant la méthode de la classe Sprite.

Code : C#
1
2
3
4
5
6
7
protected override void LoadContent()
{
    // Create a new SpriteBatch, which can be used to draw textures.
    spriteBatch = new SpriteBatch(GraphicsDevice);

    _zozor.LoadContent(Content, "zozor6");
}

Et enfin on le met à jour et on le dessine.

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
protected override void Update(GameTime gameTime)
{
    _zozor.Update(gameTime);

    base.Update(gameTime);
}

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

    spriteBatch.Begin();
    _zozor.Draw(spriteBatch, gameTime);
    spriteBatch.End();

    base.Draw(gameTime);
}


Vous pouvez maintenant lancer le jeu et voir que Zozor est toujours là mais cette fois-ci grâce à la classe Sprite. ;)
Modifiez ses attributs direction, vitesse, etc... si vous souhaitez le faire bouger. Cela dit, vous ne pourrez pas le faire rebondir sur les murs pour la simple et bonne raison que la classe Sprite ne le prévoit pas.

Pour ajouter cette possibilité il faudrait créer une autre classe, Zozor par exemple qui hérite de la classe Sprite et qui ajoute la fonctionnalité du rebondissement en réécrivant la méthode Update(). :)
Mais je m'avance un peu, vous aurez bien le temps de comprendre ceci par la suite grâce au TP !

Q.C.M.

Qu'est-ce qui ne fait PAS partie des intérêts de créer une classe ?
Dans le code suivant, à quoi sert le mot clef virtual ?

Code : C#
1
2
3
4
public void virtual Update(GameTime gameTime)
{
    // Du code
}

Statistiques de réponses au QCM

Voila pour ce chapitre portant plus sur l'organisation du code que sur XNA en lui-même mais il était à mon sens nécessaire avant de pouvoir attaquer quelque chose de plus gros. ;)

On se revoit dans le prochain chapitre pour notre premier gros TP !
Chapitre précédent Sommaire Chapitre suivant

Partager

3 commentaires pour "Création d'une classe Sprite"
Note moyenne : 3.91 / 4 (43 votes)
Pseudo Commentaire
Hors ligne djfeeler # Posté le 29/09/2011 à 13:48:59
Avatar

Avis : Très bon

J'ai testé cela fonctionne super ;)
Hors ligne Ataw # Posté le 21/11/2011 à 20:53:07
Avatar

Études : IUT Annecy

édit: En fait mon problème n'est pas spécifique à cette section donc je m'excuse de l'inutilité de ce message. :euh:

En tout cas j'apprécié énormément ce tuto et je tiens à en féliciter l'auteur ;)
Hors ligne Maxilia # Posté le 27/01/2012 à 18:03:02

Avis : Très bon

Bonjour,

J'aimerai savoir quelles sont les avantage de cette solution (création d'une classe abstraite) par rapport à la création de GameComponent.

Merci :)
(Bon tuto au passage ;))

Négocier au bazooka crois-moi sa simplifie les chose ;)
 

Voir tous les commentaires