Développement GameBoy #8 : La couche « Window »

Il s'est passé un bout de temps depuis la sortie du précédent article de cette série. Certains se sont inquiétés et m'ont contacté pour me demander si j'avais abandonné (non) et quand sortirais le prochain article sur le développement GameBoy. Je n'ai malheureusement pas été en mesure de leur donner une réponse juste (désolé). L'enchaînement des événements (portage du début du jeu Evoland sur GameBoy avec la rédaction des articles associés, rédaction d'un article pour le magazine Programmez!, et bien d'autres choses) a fait que je n'ai juste pas eu le temps. Mais ça y est, c'est reparti ! Je reprends donc cette série et je vais essayer de vous apporter environ un nouvel article par mois. On reprend donc là où on s'était laissé : la couche « Window ».

Note

Cet article fait partie d'une série sur le développement GameBoy en C avec le compilateur SDCC et la bibliothèque gbdk-n. Cette série est toujours en cours et de nouveaux articles paraissent de temps à autre.

Articles de la série :

La couche Window est la dernière qui nous restait à traiter. Elle sert généralement à afficher des informations fixes à l'écran (points de vie, timer,...) ou des boîtes de messages.

Capture d'écran des interfaces de différents jeux GameBoy

Exemples d'interfaces implémentées sur la couche Window dans divers jeux GameBoy

Elle est située au-dessus de la couche Background, et peut donc la masquer lorsqu'elle est affichée :

Les différentes couche du système vidéo de la GameBoy

Comme la couche Background, la couche Window prend la forme d'une grille de tuiles, mais contrairement à la couche Background, on ne peut pas la faire défiler. On peut cependant la déplacer pour ne recouvrir que partiellement la couche Background. Son positionnement est toutefois sujet à un certain nombre de contraintes : on ne peut notamment pas l'afficher à des coordonnées négatives (sinon elle est masquée), on ne peut donc la faire déborder qu'en bas ou à droite de l'écran (c'est pour cette raison que la plupart des UI des jeux sont situées en bas de l'écran et non en haut).

Schéma montrant les positions possibles pour la couche Window

Positions possibles pour la couche Window

Schéma montrant les positions non-permises pour la couche Window

Positions non-permises pour la couche Window

Note

Note : Malgré cette dernière contrainte, certains jeux arrivent tout de même à afficher leur interface en haut de l'écran. Pour faire cela, ils superposent la couche Window à la couche Background, et utilisent des interruptions pour masquer la couche Window lorsque l'écran a terminé d'afficher la 8ème ligne de pixels (si l'interface faisait 8 px de haut). On abordera ce sujet dans un futur article.

APIs

Les API de la couche Window sont très similaires à celles de la couche Background, je passerais donc rapidement dessus. À part pour le scrolling, les exemples concernant la couche Background fonctionneront aussi pour la couche Window.

Afficher / masquer la couche Window

Tout comme la couche Background et les Sprites, la couche Window est masquée par défaut. Il faut donc l'afficher avec la macro suivante :

SHOW_WIN;

Et il est possible de la masquer à nouveau en utilisant cette autre macro :

HIDE_WIN;

Avis

ATTENTION : incompatibilité entre la GameBoy et la GameBoy Color

Sur les premiers modèles de GameBoy, la couche Window peut être affichée lorsque la couche Background est masquée. Mais sur la GameBoy Color, la couche Window ne peut pas être affichée si la couche Background est masquée.

Il est donc plus sûr de partir du principe qu'il faut toujours afficher la couche Background (macro SHOW_BKG;) lorsque l'on souhaite rendre la couche Window visible.

Et ne considérez pas pour autant la couche Window masquée si vous masquez uniquement la couche Background, car ça ne sera pas le cas sur la GameBoy classique.

Copier des tuiles dans la mémoire vidéo

GBDK nous fournit la fonction suivante pour copier des tuiles dans la mémoire vidéo de la console :

void set_win_data(UINT8 first_tile, UINT8 nb_tiles, unsigned char *data);

Cette fonction copie les tuiles au même endroit que celles de la couche Background. On peut donc considérer que cette fonction est un alias de set_bkg_data(). C'est pourquoi je vous renvoie vers la documentation de la couche Background pour plus d'informations.

Afficher / relire des tuiles

Comme pour la couche Background, il faut charger une tilemap dans la mémoire vidéo de la console pour afficher quelque chose. Cela se fait à l'aide de la fonction suivante :

void set_win_tiles(UINT8 x, UINT8 y, UINT8 w, UINT8 h, unsigned char *tiles);

Et toujours, comme pour la couche Background, il est possible de relire les tuiles composant la tilemap à l'aide de la fonction suivante :

void get_win_tiles(UINT8 x, UINT8 y, UINT8 w, UINT8 h, unsigned char *tiles);

L'API étant totalement identique à celle de la couche Background, je vous renvoie cette fois encore vers la documentation de cette dernière.

Déplacer la couche Window

Pour déplacer la couche Window, GBDK nous fournit deux fonctions :

  • scroll_win(), qui déplace la couche relativement à sa position actuelle,
  • move_win(), qui la déplace de manière absolue aux coordonnées demandées (les coordonnées que l'on fournit correspondent au coin en haut à gauche de la couche).
void scroll_win(INT8 x, INT8 y);
  • x, y : déplacement, exprimé en pixels, à appliquer (par exemple +1, -1,...).
void move_win(UINT8 x, UINT8 y);
  • x : Coordonnée horizontale du coin en haut à gauche de la couche Window (en pixel). Il s'agit d'un nombre entier compris entre 0 et 166, toute autre valeur masque la couche.
  • y : Coordonnée verticale du coin en haut à gauche de la couche Window (en pixel). Il s'agit d'un nombre entier compris entre 0 et 143, toute autre valeur masque la couche.

Petite subtilité dans les coordonnées de la couche Window : le coin en haut à gauche de l'écran a pour coordonnées (7, 0). Il y a donc un décalage de -7 pixels sur l'axe x :

Décalage sur l'axe x dans les coordonnées de la couche Window

Exemple

À présent qu'on a vu la théorie, on va passer à la pratique. On va pour cet exemple afficher une interface affichant les points de vies et l'argent du joueur en bas de l'écran et faire défiler un motif sur la couche Background pour bien se rendre compte que la couche Windows reste statique au-dessus.

Pour cet exemple, on utilisera le tileset suivant :

Tileset utilisé pour l'exemple

La tuile en haut à gauche servira pour le Background, les autres pour afficher l'interface sur la couche Window.

Voici donc le code de l'exemple :

#include <gb/gb.h>

#include "tileset.h"
#include "window_tilemap.h"

void main(void) {
    UINT8 background_tilemap[32*32];
    UINT16 i;

    // On copie le tileset dans la mémoire vidéo
    set_bkg_data(0, TILESET_TILE_COUNT, TILESET);

    // On génère une tilemap pour la couche Background
    // et on la copie dans la mémoire vidéo
    for (i = 0 ; i < 32*32 ; i++) {
        background_tilemap[i] = 0;  // tile #0 (motif pour la couche Background)
    }
    set_bkg_tiles(0, 0, 32, 32, background_tilemap);

    // On copie la tilemap de la couche Window
    // dans la mémoire vidéo
    set_win_tiles(0, 0, WINDOW_TILEMAP_WIDTH, WINDOW_TILEMAP_HEIGHT, WINDOW_TILEMAP);

    // On déplace la couche Window pour l'afficher
    // en bas de l'écran
    move_win(7, 128);

    // On rend visible les couches Background et Window
    SHOW_BKG;
    SHOW_WIN;

    while (1) {
        // On fait défiler la couche Background histoire
        // de se rendre compte que la couche Window
        // reste bien statique
        scroll_bkg(-1, -1);

        // On attend que l'écran soit rafraîchi
        wait_vbl_done();
    }
}

Et voici ce que ça donne une fois lancé dans un émulateur :

Apperçu animé du programme d'example de la couche Window

Note

Comme d'habitude, vous retrouverez le code source complet de l'exemple sur Github :

La couche Window n'est certainement pas la plus passionnante, et même si on peut s'en passer dans bien des cas, elle se montre tout de même utile lorsque l'on veut afficher un morceau d'interface qui reste fixe même lorsque le Background défile.

On en a presque terminé avec la partie graphique de cette série d'articles, je vous retrouve le mois prochain avec un article qui abordera les palettes.