Attention: cette page se réfère à une ancienne version de SFML. Cliquez ici pour passer à la dernière version.

Afficher un sprite

Introduction

Afficher des sprites est sans doute la partie la plus importante d'une API 2D. Parce que, basiquement, tout est un sprite. Regardons à quoi ils ressemblent dans la SFML.

Charger une image

Habituellement, les sprites sont chargés à partir de fichiers sur le disque dur. Pour charger une image à partir d'un fichier avec la SFML, vous devez utiliser la classe sf::Image et sa fonction LoadFromFile :

sf::Image Image;
if (!Image.LoadFromFile("sprite.tga"))
{
    // Erreur...
}

Vous pouvez tout aussi bien charger un fichier se trouvant directement en mémoire, à l'aide de la fonction LoadFromMemory :

sf::Image Image;
if (!Image.LoadFromMemory(FilePtr, Size))
{
    // Erreur...
}

Les images peuvent également être créées et remplies avec une couleur, ou chargées depuis la mémoire :

sf::Image Image1(200, 200, sf::Color(0, 255, 0));
sf::Image Image2(200, 200, PointerToPixelsInMemory);

// Ou, si vous voulez le faire après la construction :

Image1.Create(200, 200, sf::Color(0, 255, 0));
Image2.LoadFromPixels(200, 200, PointerToPixelsInMemory);

Notez bien que lorsque vous chargez une image à partir d'un tableau de pixels en mémoire, ces pixels doivent être au format 32-bits RGBA.

Vous pouvez aussi accéder aux pixels de l'image en lecture et en écriture :

sf::Color Color = Image.GetPixel(10, 20);
Image.SetPixel(20, 30, Color);

Soyez vigilants lorsque vous manipulez des images : elles peuvent être copiées, mais ce n'est pas une opération légère. Donc essayer d'éviter les copies dans la mesure du possible (passez vos images par référence lorsque vous le pouvez, par exemple).

Créer un sprite

Ok, nous pouvons maintenant créer / charger / modifier une image. Mais ce n'est pas un sprite. Dans la SFML, images et sprites sont séparés en deux classes distinctes. Vous pouvez voir les images comme des tableaux de pixels en mémoire, et les sprites comme un moyen d'afficher ces tableaux de pixels dans une fenêtre. Manipuler les images en dehors de l'interface des sprites a deux avantages principaux :

Revenons aux sprites. Dans la SFML, un sprite est une instance de la classe sf::Sprite. Cette classe dérive de sf::Drawable, qui est la base de tout objet affichable (sprites, chaînes de caractères, post-fx, ...). sf::Drawable définit tout un tas de fonctions pour changer / récupérer l'apparence de l'objet affichable. Voici un exemple avec un sprite, mais ces fonctions sont communes à tous les objets affichables, donc ne les oubliez pas.

sf::Sprite Sprite;
Sprite.SetColor(sf::Color(0, 255, 255, 128));
Sprite.SetX(200.f);
Sprite.SetY(100.f);
Sprite.SetPosition(200.f, 100.f);
Sprite.SetRotation(30.f);
Sprite.SetCenter(0, 0);
Sprite.SetScaleX(2.f);
Sprite.SetScaleY(0.5f);
Sprite.SetScale(2.f, 0.5f);
Sprite.SetBlendMode(sf::Blend::Multiply);

Sprite.Move(10, 5);
Sprite.Rotate(90);
Sprite.Scale(1.5f, 1.5f);

Notez que toutes les fonctions prenant deux floats en paramètre, peuvent également prendre une instance de sf::Vector2f. Les fonctions GetXxx correspondant aux SetXxx prenant un vecteur, renvoient également un sf::Vector2f.

Vous pouvez bien sûr changer ces valeurs à n'importe quel moment de l'exécution, les sprites sont complétement dynamiques.

Le centre de l'objet, défini par la fonction SetCenter, est défini relativement au coin haut-gauche de l'objet et représente son centre de translation, de rotation et de mise à l'échelle. En gros, il s'agit de l'origine de l'objet, qui va rester fixe lorsque vous appliquerez des transformations géometriques à celui-ci.

Remarquez que la couleur a une composante alpha à 128, ce qui signifie que notre sprite sera un peu transparent. Si vous vous demandez pourquoi la position du sprite est exprimée avec des variables flottantes alors que les pixels sont en coordonnées entières, c'est simplement pour plus de flexibilité. En effet lorsque vous bougez un sprite, ce ne sera pas toujours d'une quantité entière de pixels, mais plutôt d'une très faible valeur. Ceci évite donc que vous ayiez à gérer une variable supplémentaire juste pour avoir les coordonnées du sprite en nombre flottant. Une autre raison est que lorsque vous utilisez des vues, les coordonnées de la "scène" n'ont potentiellement plus rien à voir avec les pixels de l'écran ; mais gardons ça pour le prochain tutoriel.

Il n'y a pas grand chose à dire sur les autres variables, excepté que la rotation est un angle exprimé en degrés.

Une fois que vous avez appliqué toutes ces transformations à votre sprite, il peut devenir difficile d'obtenir le rectangle résultant, et de manière générale la position finale de tout point local. C'est pourquoi sf::Sprite et toutes les classes dérivées de sf::Drawable définissent deux fonctions de conversion :

// Convertit un point local en coordonnées globales
sf::Vector2f Global = Sprite.TransformToGlobal(sf::Vector2f(10, 25));

// Convertit un point global en coordonnées locales
sf::Vector2f Local = Sprite.TransformToLocal(sf::Vector2f(425, 120));

Par dessus cela, sf::Sprite fournit des fonctions plus spécifiques :

// On change l'image (texture) source du sprite
sf::Image Image;
...
Sprite.SetImage(Image);

// On récupère les dimensions du sprite
float Width  = Sprite.GetSize().x;
float Height = Sprite.GetSize().y;

// On redimensionne le sprite
Sprite.Resize(60, 100);

// Retourne le sprite sur les axes X et Y
Sprite.FlipX(true);
Sprite.FlipY(true);

// On récupère la couleur d'un pixel donné du sprite
sf::Color Pixel = Sprite.GetPixel(10, 5);

Nous pouvons ensuite facilement ajouter un peu d'interaction avec notre sprite :

// Récupération du temps écoulé
float ElapsedTime = App.GetFrameTime();

// On déplace le sprite
if (App.GetInput().IsKeyDown(sf::Key::Left))  Sprite.Move(-100 * ElapsedTime, 0);
if (App.GetInput().IsKeyDown(sf::Key::Right)) Sprite.Move( 100 * ElapsedTime, 0);
if (App.GetInput().IsKeyDown(sf::Key::Up))    Sprite.Move(0, -100 * ElapsedTime);
if (App.GetInput().IsKeyDown(sf::Key::Down))  Sprite.Move(0,  100 * ElapsedTime);

// On tourne le sprite
if (App.GetInput().IsKeyDown(sf::Key::Add))      Sprite.Rotate(-100 * ElapsedTime);
if (App.GetInput().IsKeyDown(sf::Key::Subtract)) Sprite.Rotate( 100 * ElapsedTime);

Afficher un sprite

Afficher un sprite dans une fenêtre de rendu est extrêmement simple :

App.Draw(Sprite);

Aucun paramètre supplémentaire n'est nécessaire, étant donné que le sprite connaît déjà tout ce qu'il faut savoir sur son apparence (sa position, sa couleur, ...).

Si vous voulez dessiner uniquement une sous-portion de l'image originale, vous pouvez changer le paramètre SubRect du sprite :

Sprite.SetSubRect(sf::IntRect(10, 10, 20, 20));

Ici nous n'afficherons qu'un sous-rectangle de l'image allant du pixel (10, 10) au pixel (20, 20).

sf::IntRect est juste une classe utilitaire pour manipuler les rectangles. Pour les rectangles à coordonnées flottantes, il y a également la classe sf::FloatRect (en fait elles sont toutes deux des instanciations de la classe template sf::Rect).

Gestion des images et des sprites

Vous devez être particulièrement prudent lorsque vous manipulez des images. Une instance de sf::Image est une ressource qui est lente à charger, lourde à copier et qui utilise beaucoup de mémoire.

Beaucoup de gens, particulièrement les débutants, seront tentés d'utiliser simplement une instance de sf::Image à chaque endroit où ils auront une instance de sf::Sprite, car cela peut paraître le moyen le plus simple de dessiner quelque chose. Le fait est que c'est généralement une mauvaise idée. Le problème le plus évident survient lorsque l'on copie de tels objets (le simple fait de les placer dans un tableau peut générer des copies) : les sprites apparaîtront très probablement blancs. La raison est la suivante : un sprite ne fait que pointer vers une image, il n'en possède pas d'instance, ainsi lorsque l'image est copiée, le sprite doit être mis à jour afin de pointer vers la nouvelle copie de l'image. C'est très simple à gérer, vous devez juste définir correctement le constructeur par copie de telles classes possédant (ou dérivant de) un sprite et une image :

class MyPicture
{
public :

    // Ceci est le constructeur par copie
    MyPicture(const MyPicture& Copy) : Image(Copy.Image), Sprite(Copy.Sprite)
    {
        // Voilà la feinte : nous paramétrons le sprite afin qu'il
        // utilise notre image, plutôt que celle de Copy
        Sprite.SetImage(Image);
    }

    // ... d'autres fonctions diverses ...

private :

    sf::Image  Image;
    sf::Sprite Sprite;
};

Mais ceci est un cas plutôt particulier, et en général vous voudrez très probablement partager une même image pour plusieurs sprites. En fait, il ne devrait jamais exister plus d'une instance de sf::Image par image que vous chargez (après tout, pourquoi avoir le même tableau de pixels plusieurs fois en mémoire ?). C'est la raison pour laquelle les concepts de sprites et d'images ont été séparés dans la SFML : les images sont lourdes et coûteuses, alors que les sprites sont légers à manipuler, ils ne sont qu'une représentation visuelle particulière d'une image.

Il existe de nombreuses implémentations qui peuvent s'occuper de la gestion correcte des images. Par exemple, si vous écrivez une classe dont toutes les instances vont utiliser la même image, alors vous pouvez simplement faire ceci :

class Missile
{
public :

    static bool Init(const std::string& ImageFile)
    {
        return Image.LoadFromFile(ImageFile);
    }

    Missile()
    {
        Sprite.SetImage(Image); // chaque sprite utilise la même et unique image
    }

private :

    static sf::Image Image; // partagée par toutes les instances

    sf::Sprite Sprite; // un pour chaque instance
};

Dans un moteur plus complet, les images seraient automatiquement distribuées par un gestionnaire. C'est un moyen plus générique et facile de gérer les ressources. L'idée est de faire en sorte que le gestionnaire stocke les images, en les associant à leur nom de fichier (ou n'importe quel autre identificateur unique), de sorte que si une même image est demandée plusieurs fois, le gestionnaire renverra en fait toujours la même instance.

sf::Sprite Sprite;

// GetImage renverra toujours la même instance pour un même nom de fichier
Sprite.SetImage(ImageManager.GetImage("data/missile.png"));

Conclusion

Le sprite est un concept de base dans une API 2D, et comme vous l'avez vu ils sont très faciles à utiliser dans la SFML. Si vous souhaitez maintenant savoir comment dessiner des formes simples avec SFML, rendez-vous au prochain tutoriel concernant les formes.