Les images
Comme vous le savez, Irrlicht est un moteur 3D (comment ça vous ne le savez pas ?

). Mais ça ne veut pas dire pour autant qu'il est limité à gérer des objets en 3 dimensions. Ce chapitre a pour but de vous présenter une de ses fonctionnalités 2D : l'affichage d'images. Et nous verrons au passage quelques types de variables un peu spéciales utilisées par le moteur.
La première chose à faire est de mettre la main sur une image à afficher. Histoire de donner dans l'originalité, on va aller faire un tour dans le SDK (le dossier irrlicht-1.5.1 pour la version 1.5.1).
Toujours dans le dossier
media, il y a un fichier qui s'appelle "2ddemo.bmp". D'ailleurs il n'est pas placé là par hasard. Si vous êtes un peu curieux vous vous apercevrez que tous les fichiers dans le dossier
media sont utilisés pour faire tourner des applications de démo correspondantes aux
tutos officiels d'Irrlicht.
Une fois que vous l'avez, copiez-le à un endroit pas trop loin de votre exécutable et commençons. Pour démarrer, rien d'inhabituel : on crée un
device, un
driver, et un
scene manager. Entamons tout de suite les hostilités avec le type de variable qui permet de stocker un fichier image :
Code : C++ | irr::video::ITexture *image;
|
C'est ce pointeur qui va contenir l'adresse de notre image. Maintenant récupérons ladite image :
Code : C++ | driver->getTexture ("le_path_du_fichier");
|
Irrlicht peut gérer beaucoup de formats de fichiers image différents :
BMP, JPG, TGA, PCX, PNG, etc...
Soyez maintenant attentifs, ça devient intéressant. Vous l'aurez remarqué au nom, la fonction qui permet de charger une image permet en réalité de charger une texture. Ce qui veut strictement dire la même chose. Après tout, la 2D n'est qu'un cas particulier de la 3D. Comme on dit, qui peut le plus peut le moins. Et donc, Irrlicht qui peut gérer des textures dans un espace en 3 dimensions peut tout naturellement gérer des textures dans un espace en 2 dimensions. Textures auxquelles on donne alors le nom d'images ou encore de sprites selon les cas.
Il est intéressant de savoir qu'il existe des tas d'options paramétrables pour le chargement d'une texture. Avant de les passer en revue, voyons l'instruction qui permet de modifier ces options :
Code : C++ | driver->setTextureCreationFlag (irr::video::ETCF_ALWAYS_16_BIT, true);
|
Analysons ces deux paramètres. Le premier est une valeur d'énumération, elle représente justement ce qui est paramétrable. Les différentes valeurs possibles sont (je ne remets pas le
irr::video:: à chaque fois) les suivantes.
- ETCF_ALWAYS_16_BIT permet de ne créer que des textures de 16 bits par pixels. Plus rapides que les 32 bits, et plus petites en mémoire aussi.
- ETCF_ALWAYS_32_BIT permet de ne créer que des textures de 32 bits par pixel.
- ETCF_OPTIMIZED_FOR_QUALITY permet d'optimiser pour la qualité. Donc le plus souvent les formats d'origine sont laissés tels quels. En effet ça n'a pas grand intérêt de convertir une 16 bits en 32. les détails manquants ne vont pas apparaître comme par magie...
- ETCF_OPTIMIZED_FOR_SPEED permet d'optimiser pour la vitesse.
- ETCF_CREATE_MIP_MAPS permet au driver de créer automatiquement des niveaux de MIP map sur la texture.
Quant au deuxième paramètre, il s'agit d'un booléen tout ce qu'il y a de plus classique. Il suffit de le mettre à
true pour activer ce qui est passé en premier paramètre. Une petite remarque avant d'aller plus loin :
Nous allons maintenant devoir faire un petit interlude
musical pour voir les différents types de variables utilisés dans Irrlicht. J'avoue que si je coupe le chapitre en plein milieu c'est parce que nous en aurons besoin pour la deuxième partie sur les images. Mais bon de toute façon il fallait bien caser ça quelque part. Pourquoi pas là ?
Je vous préviens tout de suite, pas la peine de rêver. Je ne vais pas vous faire une liste exhaustive de toutes les structures et classes d'Irrlicht ! Il y a un formidable outil pour ça qui s'appelle
la doc.
irr::core::dimension2d< T >
Vous l'aurez reconnu, il y a un
template qui traîne par là. Ce qui veut dire que dans le code cette classe s'utilisera comme ceci :
Code : C++ | irr::core::dimension2d<irr::s32> taille;
|
Ce qu'il faut retenir ici, c'est le
irr::s32. Il s'agit tout bêtement d'une variable de 32 bits signée. Il existe plein d'autres types de variables de base de ce genre propres à Irrlicht, et comme il serait long, ennuyeux et inutile que je vous les énumère tous, voici un lien vers
la page de la documentation qui le fait.
Revenons à nos moutons. Comme son nom l'indique, cette classe contient les dimensions d'une surface 2D. Elle contient précisément deux attributs appelés
Height et
Width (en français ça donne
hauteur et
largeur). Au niveau de l'utilisation, c'est donc simplissime. Imaginons par exemple que ma variable "taille" de tout à l'heure contienne les dimensions d'une image, pour récupérer la hauteur il suffit de faire :
Code : C++ | irr::s32 hauteur = taille.Height;
|
Et voilà, on vient de stocker la hauteur de l'image dans une variable de type irr::s32.
irr::core::position2d< T >
C'est quasiment la même à peu de choses près. Ici aussi il n'y a que deux attributs :
- La position en x notée X.
- La position en y notée Y.
Donc au niveau du code, cela nous donne :
Code : C++ | irr::core::position2d<irr::s32> position;
position.X = 50;
position.Y = 50;
|
Et voilà, on vient de créer une instance contenant les positions 50 (pixels) sur les 2 axes.
irr::core::rect< T >
Celle-ci est un poil plus complexe que les 2 autres. Notamment car ses attributs sont eux-mêmes des classes. Mais bonne nouvelle, des classes que nous venons juste d'étudier (comme le hasard fait bien les choses). En réalité,
irr::core::rect<T> permet de définir les coordonnées d'un rectangle. Et pour ce faire, il suffit de connaître les coordonnées de 2 de ses coins symétriques par le point central.
Un petit schéma :

On donne les coordonnées du coin haut gauche et celle du coin bas droit. Et devinez quoi... pour définir ces coordonnées, on utilise
irr::core::position2d<T>.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12 | // On cree les positions 2D des deux coins
irr::core::position2d<irr::s32> position0;
position0.X = 0;
position0.Y = 0;
irr::core::position2d<irr::s32> position1;
position1.X = 300;
position1.Y = 300;
// Puis on les associe aux coins d'un rectangle
irr::core::rect<irr::s32> rectangle;
rectangle.UpperLeftCorner = position0;
rectangle.LowerRightCorner = position1;
|
Vous l'aurez remarqué, les deux attributs de
core::rect<T> sont :
- UpperLeftCorner pour le coin en haut à gauche
- LowerRightCorner pour le coin en bas à droite
Maintenant que nous savons comment stocker une image et que nous sommes au point sur les types de variables pouvant nous être utiles, il ne nous reste plus qu'à l'afficher. Voici la ligne de code à placer dans la boucle de rendu pour se faire :
Code : C++ | driver->draw2DImage(image, irr::core::position2d<irr::s32>(20,20),
irr::core::rect<irr::s32>(0,0,300,300), 0,
irr::video::SColor(255, 255, 255, 255), true);
|
Beaucoup de paramètres, n'est-ce pas ? C'est parce qu'il y a beaucoup de choses à définir.
- Le premier désigne l'image à afficher.
- Le deuxième est la position du coin haut gauche de l'image par rapport à l'origine : le coin haut gauche de la fenêtre
- Le troisième désigne la partie de l'image de base que l'on veut dessiner.
- Le quatrième permet de n'afficher qu'une partie du rectangle qu'on a sélectionné au paramètre précédent. Pour 0, tout le rectangle sera affiché
- Le cinquième représente la couleur avec laquelle l'image sera altérée
- Le sixième et dernier va nous permettre de ne rendre transparentes que certaines parties de l'image
Nous allons maintenant avoir besoin de ce qu'on vient d'étudier plus haut. Vous aurez remarqué que certains paramètres sont du même type de ceux que nous venons de voir. Intéressons-nous au troisième, ce qui me donne l'occasion de faire une remarque :
Si la taille du rectangle passée en paramètre dépasse la taille de l'image à afficher, alors celle-ci sera répétée pour combler le vide. En prenant le coin haut gauche du rectangle comme origine.
Voilà le code complet qui récapitule tout ce qu'on a vu depuis le début du chapitre :
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 | #include <IRR/irrlicht.h>
int main(void)
{
irr::IrrlichtDevice* device = irr::createDevice(irr::video::EDT_OPENGL,
irr::core::dimension2d<irr::u32>(640,480), 32);
irr::video::IVideoDriver* driver = device->getVideoDriver ();
irr::scene::ISceneManager *sceneManager = device->getSceneManager ();
irr::video::ITexture *image = driver->getTexture ("2ddemo.bmp"); // chargement de la texture
while(device->run ()) // la boucle de rendu
{
driver->beginScene(true, true,
irr::video::SColor (0,120,120,120));
driver->draw2DImage(image, // dessin de l'image
irr::core::position2d<irr::s32>(20,20),
irr::core::rect<irr::s32>(0,0,300,300),
0,
irr::video::SColor (255,255,255,255),
true);
driver->endScene ();
}
device->drop ();
return 0;
}
|
Alors, ça pique un peu non ?

Faisons abstraction de la couleur un instant pour remarquer quelque chose. L'image s'est répétée en hauteur. Vous pouvez voir qu'en bas on retrouve la même chose qu'en haut. Pour éviter d'avoir à regarder quelle est pile-poil la hauteur en pixels d'une image pour la passer en paramètre, on peut comme tout à l'heure, la récupérer dans une variable et s'en servir.
Pour récupérer les dimensions d'une image, rien de plus simple. Il existe une fonction juste pour ça :
irr::video::ITexture::getSize(). Qui renvoie une variable de type
irr::core::dimension2d<s32>.
Maintenant soyez attentifs le code qui suit est un méga-mix de tout ce qu'on vu dit 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 | #include <IRR/irrlicht.h>
int main(void)
{
irr::IrrlichtDevice* device = irr::createDevice(
irr::video::EDT_OPENGL,
irr::core::dimension2d<u32>(640,480),32);
irr::video::IVideoDriver* driver = device->getVideoDriver ();
irr::scene::ISceneManager *sceneManager = device->getSceneManager ();
irr::video::ITexture *image = driver->getTexture("2ddemo.bmp"); // chargement image
irr::core::dimension2d<irr::s32> taille = image->getSize (); // recuperation dimensions image
irr::core::position2d<irr::s32> position0; // creation position origine
position0.X = 0;
position0.Y = 0;
irr::core::position2d<irr::s32> position1; // creation position coin
position1.X = taille.Width; // bas droit du rectangle
position1.Y = taille.Height;
irr::core::rect<irr::s32> rectangle; // creation rectangle
rectangle.UpperLeftCorner = position0; // contenant l'image
rectangle.LowerRightCorner = position1;
while(device->run ()) // boucle de rendu
{
driver->beginScene(true, true,
irr::video::SColor (0,120,120,120));
driver->draw2DImage(image, // dessin de l'image
position0, // a la position origine
rectangle, // dans le rectangle cree au dessus
0,
irr::video::SColor (255,255,255,255),
true);
driver->endScene ();
}
device->drop ();
return 0;
}
|

Comme vous pouvez le voir (sur le screenshot de droite, si ce n'est sur votre écran), c'est déjà un peu mieux. On vient d'afficher l'image dans sa totalité. Vous pouvez d'ailleurs ouvrir le fichier "manuellement" pour constater qu'on a exactement le même. Vous l'aurez sans doute deviné, cette image sert à faire des tests (ça tombe bien

). Il y a deux bestioles sur le coté et le logo Irrlicht juste en dessous.
La première chose à faire est de faire en sorte que cette vilaine couleur de fond rose ne vienne plus nous agresser les pupilles. Nous allons donc rendre transparente cette couleur précisément.
Pour ce faire 2 méthodes :
La première
Il s'agit d'indiquer à Irrlicht la couleur à rendre transparente par ses composantes :
Code : C++ | driver->makeColorKeyTexture (image, irr::video::SColor (0,255,255,255));
|
Le premier paramètre correspond à l'image qui contient la couleur qui va devenir transparente. Le deuxième représente les composantes de cette couleur. A noter que le premier nombre est la composante alpha, et que dans notre cas elle est inutile, que vous la mettiez à 0 ou à 255, le résultat sera le même. Si on reprend cette ligne, on s'aperçoit donc qu'elle va rendre transparents tous les pixels qui sont blancs.
La deuxième
Contrairement à la première méthode, nous n'allons pas indiquer directement les composantes de la couleur. L'idée est de donner les coordonnées d'un pixel et tous les pixels partageant la couleur de celui-ci seront transparents.
Code : C++ | driver->makeColorKeyTexture (image, irr::core::position2d<s32> (0,0));
|
Et voilà. Le premier paramètre indique toujours l'image sur laquelle on travaille, et le deuxième indique les coordonnées 2D du pixel sur l'image. Donc cette ligne permet de rendre transparents tous les pixels qui ont la même couleur que celui à l'origine de l'image, le point le plus en haut à gauche.
Le rendu
Je crois que nous avons maintenant tout ce qu'il nous faut pour faire un truc sympa.

Nous allons maintenant voir un code où la couleur de fond est transparente et où des parties de l'image vont être collées sur d'autres. Restez concentrés, rien de nouveau mais une mise en pratique de tout ce qu'on a appris dans ce chapitre.
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 | #include <IRR/irrlicht.h>
int main(void)
{
irr::IrrlichtDevice* device = irr::createDevice(
irr::video::EDT_OPENGL,
irr::core::dimension2d<irr::u32>(640,480),
32,false,false,false);
irr::video::IVideoDriver* driver = device->getVideoDriver ();
irr::scene::ISceneManager *sceneManager = device->getSceneManager ();
irr::video::ITexture *image = driver->getTexture("2ddemo.bmp"); // chargement de l'image
driver->makeColorKeyTexture (image, // transparence pour le fond
irr::core::position2d<irr::s32> (0,0));
irr::video::SColor blanc; // creation variable couleur blanche
blanc.set(255,255,255,255);
while(device->run ()) // la boucle de rendu
{
driver->beginScene(true, true,
irr::video::SColor (0,120,120,120));
driver->draw2DImage(image, // dessin de la salle
irr::core::position2d<irr::s32> (20,20),
irr::core::rect<irr::s32> (0,0,342,224),
0, blanc, true);
driver->draw2DImage (image, // dessin du premier truc rouge
irr::core::position2d<irr::s32> (140,140),
irr::core::rect<irr::s32> (349,15,385,78),
0, blanc, true);
driver->draw2DImage (image, // dessin du deuxième truc rouge
irr::core::position2d<irr::s32> (150,20),
irr::core::rect<irr::s32> (387,15,423,78),
0, blanc, true);
driver->endScene ();
}
device->drop ();
return 0;
}
|

Et voilà le travail ! Vous pouvez cliquer sur l'image de droite pour admirer le résultat de plus près.
Bon ok, j'avais dit rien de nouveau et finalement on a utilisé une variable video::SColor qu'on n'avait pas vue... Prenez le code comme argent comptant, on reviendra peut-être dessus dans un prochain chapitre.

Et bien, nous étions partis pour faire un petit chapitre facile et voilà le résultat.

Je suis sûr que vous avez appris plein de trucs, non ?
Si vous aimez les jeux 2D façon Old School, sachez qu'il est tout à fait possible d'en réaliser avec Irrlicht. Même si à la base ce n'est pas forcément prévu pour ça. Pour ceux qui seraient intéressés, voilà
un site qui devrait vous aider. Il n'explique pas comment faire avec Irrlicht précisément, mais de manière générale tous les concepts pour faire des jeux 2D à base de tiles. Enjoy !

Informations sur le tutoriel