Je poursuis l’extension de la façade graphique (cf article précédent), avec ici l’idée d’ajouter une forme de dessin très classique pour les jeux vidéos: les tuiles. Celles-ci permettent de composer une image à partir de petites images appelées tuiles. Il existe plusieurs types de tuiles, je propose dans cet article le cas le plus simple avec des tuiles rectangulaires.
Pour la mise en œuvre, le schéma que je propose de suivre est similaire à celui utilisé pour les images: on utilise une fabrique (Factory Method Pattern) pour instancier des calques (layers) qui permettent de dessiner avec des tuiles. Du côté de l’interface principale GUIFacade
, cela reviens à ajouter les deux méthodes suivantes, l’une pour créer un calque, et l’autre pour le dessiner:
Pour les calques (interface
Layer
), je propose de gérer les tuiles avec une image texture par calque, et dont les tuiles ont une taille fixe. De plus, le rendu des tuiles peut être mémorisé par le calque: ce faisant, il suffit d’indiquer quelles tuiles doivent être affichées à quels endroits, puis le calque affichera ces tuiles en continu. Ces indications de rendu sont appelés dans cet article sprites, pour ne pas les confondre avec les tuiles (i.e., une tuile est juste une petite image, et un sprite est une petite image affichée à un certain endroit à l’écran). A ce stade de conception, une telle propriété peut sembler inutile, cependant plus tard, lorsqu’il faudra faire de la synchronisation multi thread, cette propriété est une bénédiction. En outre, pour les implantations bas niveau (comme OpenGL), cela permet d’utiliser des tampons de vertex, ce qui accélèrent énormément le rendu. Ces propriétés peuvent être obtenues via une interface du type:
- Les méthodes
getTileWidth()
etgetTileHeight()
renvoient la taille des tuiles (par exemple, 16 par 16 pixels); - Les méthodes
getTextureWidth()
etgetTextureHeight()
renvoient le nombre de tuiles dans l’image texture, en largeur et en hauteur; - La méthode
setTileSize()
permet de définir la taille des tuiles; - La méthode
setTexture()
permet de définir et charger l’image texture; - La méthode
setSpriteCount()
permet de définir le nombre de sprites gérés par le calque; - La méthode
setSpriteTexture()
permet de définir les tuiles utilisées par le sprite de numéroindex
. Le rectangletile
définit les tuiles utilisées: ce sont des coordonnées en tuiles, et non en pixels. Par exemple, si la taille des tuiles est 16×16 pixels, et le rectangletile
est (3,4,1,2), alors le rectangle en pixels est (3*16,4*16,1*16,2*16)=(48,64,16,32). Par convention, si letile
estnull
, alors le sprite est désactivé; - La méthode
setSpriteLocation()
permet de définir le lieu où est dessiné le sprite. Le rectanglelocation
est en pixels, et peut être plus grand ou plus petit que les tuiles utilisées, afin d’obtenir un effet de zoom.
Implantation AWT
Pour l’implantation, on reste sur la bibliothèque AWT, avec la classe AWTLayer
(les méthodes de l’interface Layer
ne sont pas répétées):
- Les attributs
tileWidth
,tileHeight
,textureWidth
ettextureHeight
définissent la taille des tuiles et celle de l’image texture; - L’attribut
texture
contient l’image texture; - Les tableaux
textures
etlocations
contiennent toutes les informations sur les sprites: tuiles à utiliser et lieux de rendu; - La méthode
draw()
dessine tout le calque.
La plupart des méthodes sont des accesseurs/mutateurs (getters/setters) dont l’implantation est très simple. La méthode draw()
est moins évidente:
public void draw(Graphics graphics) { for (int i = 0; i < locations.length; i++) { if (textures[i] != null) { graphics.drawImage(texture, locations[i].x, locations[i].y, locations[i].x + locations[i].width, locations[i].y + locations[i].height, textures[i].x * tileWidth, textures[i].y * tileHeight, (textures[i].x+textures[i].width) * tileWidth, (textures[i].y+textures[i].height) * tileHeight, null); } } }
Tous les sprites sont parcourus (l. 2), si un sprite est bien défini (l.3), on dessine une image (l. 4-13). Les lignes 5 à 8 définissent le lieu de rendu, en pixels: c’est simplement les coordonnées du coin haut gauche et du coin bas droite du rectangle. Les lignes 9 à 12 définissent un rectangle dans l’image texture: toutes les coordonnées sont multipliées par la taille des tuiles (tileWidth
, tileHeight
).
Exemple d’utilisation
Pour illustrer cette façade avec dessin par tuiles, je propose de considérer deux calques: l’un pour le décor, et l’autre pour les bâtiments. Dans les deux cas, on considère une même taille de tuiles de 16×16 pixel, et une même zone de rendu (le niveau du jeu) de 17×17 tuiles. En outre, un facteur d’échelle (scale) est utilisé pour zoomer les tuiles, la résolution des écrans actuels étant très élevée pour voir des tuiles aussi petites. Ces paramètres sont placés dans des variables:
int scale = 2; int tileWidth = 16; int tileHeight = 16; int levelWidth = 17; int levelHeight = 17;
Pour le calque de fond, chaque sprite n’utilise qu’une seule tuile, et on utilise toujours la même:
Layer backgroundLayer = gui.createLayer(); backgroundLayer.setTileSize(tileWidth,tileHeight); backgroundLayer.setTexture("advancewars-tileset1.png"); backgroundLayer.setSpriteCount(levelWidth*levelHeight); for(int y=0;y<levelHeight;y++) { for(int x=0;x<levelWidth;x++) { int index = x + y * levelWidth; backgroundLayer.setSpriteLocation(index, new Rectangle(scale*x*tileWidth, scale*y*tileHeight, scale*tileWidth, scale*tileHeight)); backgroundLayer.setSpriteTexture(index, new Rectangle(new Point(7,0), new Dimension(1,1))); } }
Les lignes 1 à 4 instancie et initialise le calque. Puis, pour chaque cellule du niveau (l. 5-6), on associe un sprite (l. 7). Pour réaliser cette association, il faut définir un nombre unique pour chaque cellule du niveau: pour ce faire, on numérote les cellules de gauche à droite puis de haut en bas. La zone de rendu de chaque sprite est défini aux lignes 8-9, qui est un simple passage à l’échelle: tout est multiplié par la taille des tuiles tileWidth
, tileHeight
et le zoom global scale
. Enfin, la tuile est sélectionnée aux lignes 10-11, avec pour coordonnée la 8ème colonne de la première ligne de la texture Point(7,0)
et dont la taille est d’une tuile Dimension(1,1)
Le calque suivant est utilisé pour afficher les bâtiments, avec la particularité que ceux-ci ont une taille de deux tuiles de hauteur:
Layer groundLayer = gui.createLayer(); groundLayer.setTileSize(tileWidth,tileHeight); groundLayer.setTexture("advancewars-tileset2.png"); groundLayer.setSpriteCount(2); groundLayer.setSpriteLocation(0, new Rectangle(scale*8*tileWidth, scale*6*tileHeight, scale*tileWidth, scale*2*tileHeight) ); groundLayer.setSpriteTexture(0, new Rectangle(new Point(0,2), new Dimension(1,2))); groundLayer.setSpriteLocation(1, new Rectangle(scale*8*tileWidth, scale*7*tileHeight, scale*tileWidth, scale*2*tileHeight) ); groundLayer.setSpriteTexture(1, new Rectangle(new Point(0,4), new Dimension(1,2)));
Les lignes 1 à 4 instancie le calque et initialise ses propriétés: taille des tuiles, image texture et nombres de sprites (2). Les lignes 6 à 11 définissent les propriétés du premier sprite (index 0): son lieu de rendu est à la cellule (8,6) de la grille (l. 7), sa taille de rendu équivaut à une largeur de tuile et à deux hauteurs de tuiles (l.8), ses tuiles sont aux coordonnées (0,2) dans la texture et on utilise une tuile de largeur et deux tuiles de hauteur (l. 11). Les lignes 13 à 18 définissent les propriétés du deuxième sprite: celui-ci est dessiné sur la cellule juste en dessous, et ses tuiles commencent aux coordonnées (0,4) dans la texture. Si tout se passe bien, le haut du deuxième sprite de bâtiment doit recouvrir le bas du premier:
Enfin, dans la boucle principale du jeu, afficher les calques est aussi simple qu’afficher une image:
if (gui.beginPaint()) { gui.drawLayer(backgroundLayer); gui.drawLayer(groundLayer); gui.endPaint(); }
Le code de cet article peut être téléchargé ici. Quelques améliorations ont été apportées par rapport à la version de l’article précédent: utilisation d’un canvas dans la fenêtre, et limitation du nombre d’images par seconde. Ces améliorations sont purement liées aux problématiques de jeux vidéos, et ne change en rien les concepts présentés précédemment.
Pour compiler, saisissez: javac fr/phtools/awtfacade04/Main.java
Pour lancer, saisissez: java fr.phtools.awtfacade04.Main