[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.
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;
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;
}
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 .
Commentaires
Personne n'a encore commenté cet article, à vous de jouer !