Technologies Système

[DEV] Comment faire un splash-screen ?

Qu'est ce qu'un splash-screen ? Il s'agit tout simplement d'une sorte de page de garde, mais pour un logiciel. C'est généralement le logo du logiciel en question, accompagné d'un indacteur qui montre l'avancement d'un chargement. Leur utilité principale est de permettre à l'utilisateur de comprendre que ça charge et de ne pas aller tuer la tâche manuellement. Dans un jeu vidéo on met généralement un joli artwork, mais sur d'autres types de logiciels, il faut ruser.

[DEV] Comment faire un splash-screen ? | Le splash-screen d'Adobe Premiere Pro

Le splash-screen d'Adobe Premiere Pro

Pour ce tutoriel, je vais vous apprendre à réaliser un splash-screen pour donner un petit côté "pro" artificiel à vos programmes. C'est parfois utile aussi lorsque votre programme est long à charger, cela permet de faire patienter l'utilisateur.
La principale caractéristique de ces splash-screen, c'est qu'ils sont sans bordures (ou fenêtres du système d'exploitation), et qu'ils doivent à la fois afficher une image, mais aussi indiquer qu'une progression a lieu en arrière plan.

Pré-requis

  • Connaître un peu la programmation orientée objet, au moins les bases. Le tuto est en C++ mais facilement adaptable dans d'autres langages.
  • Avoir installé la SFML (tuto en français code-blocks et linux).

Nous allons utiliser la SFML car c'est une bibliothèque gratuite, très bien maintenue, légère et développée par un français. C'est une surcouche idéale à opengl, très simple d'utilisation et qui s'intègre parfaitement dans un projet utilisant d'autres bibliothèques comme Qt ou Boost. En plus elle existe aussi en d'autres langages.

Etape 1 : Fenêtre sans bordure

Une fois l'installation terminée et testée (si vous arrivez à afficher un cercle vert comme dans la fin des tutos présentés plus haut), commencez un nouveau projet avec ce code minimal :

#include <SFML/Graphics.hpp>

int main(int argc, char ** argv)
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "SPLASH SCREEN"); //Création de la fenêtre

    while (window.isOpen())//Boucle de rafraichissement
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed) //Clic sur la croix de fermeture
                window.close(); //Sortie de la boucle
        }

        window.clear(); //Rafraichissement
        window.display();
    }

    return 0;
}

Ce code minimal affiche normalement une fenêtre noire de 800x600 pixel, avec une bordure et la possibilté de fermer la fenêtre. On va enlever la bordure.
Pour cela, il faut rajouter un troisième paramètre à la création de la fenêtre, enlevant tous les styles. Par contre cela va empêcher aussi de pouvoir cliquer sur la croix pour refermer puisqu'elle n'existera plus. Donc on peut aussi enlever le test de l'évènement de fermeture (laissez la zone des évènements, sinon ça plante). Lors de vos tests, il faudra aussi tuer la fenêtre via le gestionnaire des tâches, parce qu'en l'état il n'y a plus de moyen de la quitter.

Nouveau contenu de la fonction main :

    sf::RenderWindow window(sf::VideoMode(800, 600), "SPLASH SCREEN", sf::Style::None); //Création de la fenêtre

    while (window.isOpen())//Boucle de rafraichissement
    {
        sf::Event event; //Zone d'évènement, vide mais existante sinon ça plante
        while (window.pollEvent(event)) { }
        window.clear(); //Rafraichissement
        window.display();
    }

    return 0;

Maintenant on va placer l'image, le logo ou tout autre élément visuel. On créé un Sprite ayant pour Texture l'image que vous voulez mettre sur le splash screen. N'oubliez pas, l'image doit être de la taille de la fenêtre (vous pouvez la redimensionner mais ça ne sera pas terrible). De plus, ne faites pas un splash screen trop grand, vous limiterez l'utilisation sur des petits formats d'écran.

Nouveau contenu de la fonction main :

    sf::RenderWindow window(sf::VideoMode(400, 400), "SPLASH SCREEN", sf::Style::None); //Création de la fenêtre

    sf::Texture texture; //Création des variables
    sf::Sprite splash;
    if(!texture.loadFromFile("gm-logo.jpg")) //Lecture du fichier texture
        return -1;
    splash.setTexture(texture); //Passe la texture au sprite

    while (window.isOpen())//Boucle de rafraichissement
    {
        sf::Event event;
        while (window.pollEvent(event)) { }

        window.clear(); //Rafraichissement
        window.draw(splash); //Dessin de la texture
        window.display();
    }

    return 0;

[DEV] Comment faire un splash-screen ? | Résultat attendu.

Résultat attendu.

Etape 2 : Charger des informations en même temps

C'est bien joli un splash-screen, mais s'il ne se passe rien derrière ça ne sert à rien. Nous allons donc simuler un chargement, tout en maintenant la fonction de rafraichissement pour par exemple rajouter un texte indicateur de l'état du chargement. Pour cela nous allons utiliser un thread, c'est-à-dire un fil d'éxécution parallèle.
On va aussi ajouter un texte témoin pour bien voir ce qu'il se passe, comme il sera partagé on va protéger les accès avec des verrous (mutex). Je ne vais pas rentrer dans les détails ici parce que ce n'est pas le sujet, mais quand il y a plusieurs fils d'éxécution on ne peut pas lire et écrire une variable en même temps, donc on utilise des verrous pour faire attendre le programme artificiellement et éviter les accès concurrents.

Voilà ce que ça donne à cette étape là :

#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>

sf::Mutex mutex; //Création du verrou en partagé dans le programme

void loading(sf::Text* t)                                //Fonction de chargement
{
    //Simulation d'un temps de chargement un peu long
    sf::Clock clock;
    while(clock.getElapsedTime().asSeconds() < 2) { }
    mutex.lock();
    t->setString("Milieu du chargement");
    mutex.unlock();
    clock.restart();
    while(clock.getElapsedTime().asSeconds() < 2) { }

    mutex.lock();
    t->setString("Fin du chargement");
    mutex.unlock();

    clock.restart();                                  //On attend deux secondes sinon on verra pas le texte final
    while(clock.getElapsedTime().asSeconds() < 2) { }
}

int main(int argc, char ** argv)
{
    sf::RenderWindow window(sf::VideoMode(400, 400), "SPLASH SCREEN", sf::Style::None); //Création de la fenêtre

    sf::Texture texture;                       //Création des variables de texture
    sf::Sprite splash;
    if(!texture.loadFromFile("gm-logo.jpg"))   //Lecture du fichier texture
        return -1;
    splash.setTexture(texture);                //Passe la texture au sprite

    sf::Text text;                             //Création des variables de text
    sf::Font font;
    if(!font.loadFromFile("arial.ttf"))        //Lecture du fichier de police
        return -1;
    text.setFont(font);
    text.setString("Debut du chargement");

    sf::Thread thread(&loading, &text);        //Création du thread
    thread.launch();                           //Lancement

    while (window.isOpen())                    //Boucle de rafraichissement
    {
        sf::Event event;
        while (window.pollEvent(event)) { }

        window.clear();                        //Rafraîchissement
        window.draw(splash);                   //Dessin de la texture
        mutex.lock();                          //Déverrouillage
        window.draw(text);                  //Dessin du texte
        mutex.unlock();                        //Verrouillage
        window.display();
    }

    return 0;
}

Sauf que dans cet état là, le système ne sait pas comment s'arrêter. Pour résoudre ce problème il y a plusieurs solutions. On peut tester dans le thread principal régulièrement le texte de fin, ou plus simplement (et efficacement) passer un paramètre partagé au thread qui servira de témoin. Un booléen fera très bien l'affaire. Cependant les fonctions de thread de la SFML ne permettent pas de passer une fonction et plus d'une variable. Il faut passer par des fonctions plus évoluées comme les foncteurs, ou bien ne pas s'embêter et mettre tous ses paramètres dans une structure qui ne fera plus qu'une seule variable.

#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>

sf::Mutex mutex; //Création du verrou en partagé dans le programme

struct watcher //Structure qui va être passée en paramètre du thread
{
    int state;
    sf::Text * text;
};

void loading(watcher * w) //Fonction de chargement
{
    //Simulation d'un temps de chargement un peu long
    sf::Clock clock;
    while(clock.getElapsedTime().asSeconds() < 2) { }
    mutex.lock();
    w->text->setString("Milieu du chargement");
    mutex.unlock();
    clock.restart();
    while(clock.getElapsedTime().asSeconds() < 2) { }

    mutex.lock();
    w->text->setString("Fin du chargement");
    mutex.unlock();

    clock.restart();    //On attend deux secondes sinon on verra pas le texte final
    while(clock.getElapsedTime().asSeconds() < 2) { }
    mutex.lock();
    w->state = true;  //Fin du chargement
    mutex.unlock();
}

int main(int argc, char ** argv)
{
    sf::RenderWindow window(sf::VideoMode(400, 400), "SPLASH SCREEN", sf::Style::None); //Création de la fenêtre

    sf::Texture texture; //Création des variables de texture
    sf::Sprite splash;
    if(!texture.loadFromFile("gm-logo.jpg")) //Lecture du fichier texture
        return -1;
    splash.setTexture(texture); //Passe la texture au sprite

    sf::Text text; //Création des variables de text
    sf::Font font;
    if(!font.loadFromFile("arial.ttf")) //Lecture du fichier de police
        return -1;
    text.setFont(font);
    text.setString("Debut du chargement");

    watcher w;  //Préparation du paramètre du thread
    w.state = false; //Indicateur si le chargement est fini
    w.text = &text;

    sf::Thread thread(&loading, &w); //Création du thread
    thread.launch(); //Lancement

    while (window.isOpen())//Boucle de rafraichissement
    {
        sf::Event event;
        while (window.pollEvent(event)) { }

        mutex.lock();
        if(w.state)   //Si le chargement est fini on ferme la fenetre
            window.close();
        mutex.unlock();

        window.clear(); //Rafraichissement
        window.draw(splash); //Dessin de la texture
        mutex.lock();
        window.draw(*w.text); //Dession du texte
        mutex.unlock();
        window.display();
    }

    return 0;
}

[DEV] Comment faire un splash-screen ? | Et voilà on a tous les états qui apparaîtrons toutes les deux secondes

Et voilà on a tous les états qui apparaîtrons toutes les deux secondes

Le système est maintenant terminé ! Evidemment il peut être implémenté d'une autre manière et amélioré, je vous invite à partager en commentaire vos idées et remarques !

Pour l'utiliser il suffit de mettre ce que vous voulez dans la fonction de chargement et de lancer une nouvelle fenêtre à la suite de la boucle de rafraichissement .

    Personne n'a encore commenté cet article, à vous de jouer !