1. Introduction

Pour rendre les jeux vidéos plus attractifs visuellement, de nombreux développeurs, si ce n'est tous, font appel à un large panel d'effets spéciaux. Loin des pixel shaders et autre bump mappings, la GameBoy Advance premet la réalisation de certains effets classiques quoiqu'efficaces. L'architecture interne de la GameBoy Advance octroie notamment l'opportunité de réaliser simplement trois de ces effets : le fade-in, le fade-out (ou "fondu au noir" en français) et l'alpha-blending. Ces effets nécessitent certaines conditions particulières pour se voir appliqués. Nous commencerons donc en apprenant à créer à partir de rien les effets de fade-in et de fade-out.

2. Fondue d'effets

Le principe d'un fondu au noir se révèle très simple : il s'agit pour le programmeur de modifier graduellement la couleur de chaque pixel pour l'ammener à prendre la couleur noire. Jusqu'à présent, nous n'avons découvert ensemble que les modes graphiques 3 et 4. Examinons en premier lieu le principe du fondu au noir en mode 3. Nous supposerons dans le cadre du fondu au noir que l'image à faire "disparaître" est déjà affichée à l'écran. Lorsque la console utilise le mode 3, chaque pixel de l'image est représenté en mémoire par une valeur sur 16 bits dont les 15 bits de poids faible codent la couleur du pixel. Pour mémoire voici la définition de la macro RGB employée pour le calcul des couleurs :

 
Sélectionnez

#define RGB(r, g, b) ((r) | (g << 5) | (b << 10))

La technique du fade-out repose sur la décomposition de chaque couleur de l'écran en ses trois composantes rouge, verte et bleue. Ceci fait, la couleur est recalculée après diminution de l'intensité de chacune des composantes. En veillant à ne jamais obtenir un nombre négatif, et en appliquant ce principe 32 fois à toute la surface de l'écran, nous obtiendrons une image totalement noire. Les calculs sont appliqués 32 fois car chacune des composantes possède une valeur en 0 et 31 (inclus). Nous garantissons ainsi que toute couleur a été ramenée vers le noir.

Toutefois, l'algorithme décrit ici nécessite beaucoup de temps machine sur une GameBoy Advance. Le parcours de chaque pixel de l'écran, et ce 32 fois, se révèle d'une efficacité fort limitée. Une fois de plus, le mode graphique 4 vient à notre secours. Le mode 4 désigne un mode graphique dans lequel chaque pixel se voit encodé non pas sous la forme d'une suite de 16 bits mais sous la forme d'un octet. Cette caractéristique offre notamment la chance de faire appel à la technique de double buffering. Un pixel possède donc une valeur désignant un indice dans une palette de couleur à priori fixe. Néanmoins, rien ne nous interdit de modifier celle-ci. Le secret de la rapidité de l'algorithme de fondu au noir réside dans cet aspect. Plutôt que de modifier les composantes de chaque pixel de l'écran, nous allons modifier les composantes de chaque couleur de la palette. Ainsi, ce ne sont plus 32 * 240 * 160 opérations que la machine réalisera mais seulement 32 * 256 (une palette de couleur accueille 256 couleurs différentes). Ce procédé est d'ailleurs si performant que nous devrons le ralentir pour rendre l'effet visible par le joueur. Le listing 1 présente le code complet de la fonction de fondu au noir. La décomposition d'une couleur en trois composantes s'effectue de la sorte :

 
Sélectionnez

r = couleur & 0x1F;
g = (couleur >> 5) & 0x1F;
b = (couleur >> 10) & 0x1F;

La valeur hexadécimale 0x1F correspond à la valeur 31 en décimal. Ceci permet l'application d'un masque binaire servant à ne conserver que les 5 bits de poids faible et ainsi obtenir la valeur de la composante.

 
Sélectionnez

/* Listing 1 */
void fadeOut()
{
  u16 r, g, b;

  for (int i = 0; i < 32; i++)
  {
    for (int j = 0; j < 256; j++)
    {
      r = (palette[j]) & 0x1F;
      g = (palette[j] >> 5) & 0x1F;
      b = (palette[j] >> 10) & 0x1F;

      if (r > 0) r--;
      if (g > 0) g--;
      if (b > 0) b--;

      palette[j] = RGB(r, g, b);
    }
    waitTime(0, 2000 / 32);
  }
}

Pour ralentir l'effet, la fonction invoque ici la procédure waitTime(). Les deux paramètres de cette procédure sont le nombre de secondes et le nombre de millisecondes durant lesquelles le programme doit s'interrompre. L'opération 2000 / 32 conduit à un effet dont la durée totale se veut de deux secondes. La procédure waitTime() présentée dans le listing 2 fait appel aux timers et à leur propriétés de débordement (ou de cascade).

 
Sélectionnez

/* Listing 2 */
void waitTime(int seconds, int milliSeconds)
{
  REG_TM2CNT = FREQUENCY_256 | TIMER_ENABLE;
  REG_TM3CNT = TIMER_CASCADE | TIMER_ENABLE;
  REG_TM2D = 0;
  REG_TM3D = 0;

  while (REG_TM3D < seconds);

  REG_TM2D = 0;
  while (REG_TM2D / (65536 / 1000) < milliSeconds);

  REG_TM2CNT = 0;
  REG_TM3CNT = 0;
  REG_TM2D = 0;
  REG_TM3D = 0;
}

3. Fade-in

L'effet inverse du fondu au noir porte le doux nom de "fade-in" et consiste en l'apparition, depuis un écran noir, d'une image. Le procédé, bien que similaire à celui du fondu au noir, repose sur l'utilisation de deux palettes de couleurs. En effet, nous devons dans le cadre d'un fade-in conduire la palette de l'écran depuis le noir vers une autre palette, celle de l'image finale. Ainsi, avant d'exécuter les opérations nécessaires à cet effet, la procédure du fade-in doit impérativement recopier une image sur l'écran. Si la palette de couleur ne contient bien que des zéros, ceci n'aura aucune incidence sur l'affichage, l'écran restera noir. De ce fait, les effets de fade-out et de fade-in se voient souvent employés l'un après l'autre. Par exemple, un fade-out est utilisé pour faire disparaître un écran titre et un fade-in provoque aussitôt l'apparition du menu d'options.

La procédure fadeIn() du listing 3 nécessite deux paramètres dont l'un correspond à la palette de couleurs de l'image à afficher et l'autre aux données de l'image. Les boucles itératives imbriquées restent les mêmes, seul les tests effectués au coeur de ces dernières changent. Nous constatons notamment une décomposition en composantes rouge, verte et bleue des deux palettes. Ici encore, l'effet se voit ralenti par l'entremise de la procédure waitTime() pour étaler la transition sur une durée totale de deux secondes.

 
Sélectionnez

/* Listing 3 */
void fadeIn(const u16 picture[], const u16 pictpal[])
{
  for (int i = 0; i < 256; i++)
    palette[i] = 0;

  REG_DMA3SAD = (u32) picture;
  REG_DMA3DAD = (u32) videoBuffer;
  REG_DMA3CNT = SCREEN_HEIGHT * SCREEN_WIDTH / 2 | DMA_16NOW;

  u16 r, g, b;
  for (int i = 0; i < 32; i++)
  {
    for (int j = 0; j < 256; j++)
    {
      r = (palette[j]) & 0x1F;
      g = (palette[j] >> 5 ) & 0x1F;
      b = (palette[j] >> 10) & 0x1F;

      if (r < ((pictpal[j]) & 0x1F)) r++;
      if (g < ((pictpal[j] >> 5 ) & 0x1F)) g++;
      if (b < ((pictpal[j] >> 10) & 0x1F)) b++;

      palette[j] = RGB(r, g, b);
    }
    waitTime(0, 2000 / 32);
  }
}

4. Alpha-blending

La technique graphique intitulée alpha-blending caractérise les effets de transparence. Un programmeur pourra par exemple représenter un fantôme en affichant un sprite à demi-transparent au dessus du décor du jeu. La GameBoy Advance propose plusieurs mécanismes dédiés à la réalisations d'effets de transparence, mais également de fade-in et de fade-out. Pour calculer la couleur d'un pixel supposé transparent voici la méthode à employer :

 
Sélectionnez

r = source1.r * eva + source2.r * evb
g = source1.g * eva + source2.g * evb
b = source1.b * eva + source2.b * evb

Les structures source1 et source2 correspondent aux pixels des deux sources ayant un rôle dans l'effet de transparence. Une transparence agit entre deux objets, par exemple entre un sprite et un background ou entre deux backgrounds. La formule précédente mélange les couleurs (c'est pour cela que nous utiliserons dans nos programmes la macro BLEND) entre les deux sources et remplace le pixel correspondant à la superposition des deux sources. Une opération d'alpha-blending peut se voir paramétrée sur 16 niveaux de transparence. La valeur eva agit sur la première source tandis que le coefficient evb agit sur la seconde source.

Le paramétrage d'un effet d'alpha-blending demande l'utilisation du registre REG_BLDMOD définit de la sorte :

 
Sélectionnez

#define REG_BLDMOD *(u16*) 0x4000050

Ce registre contient 16 bits permettant de définir les sources pour les effets ainsi que le type de l'effet. Les six premiers bits sélectionnent la première source choisie parmi les backgrounds 0 à 3 (nous reviendrons sur leur nature et leur utilisation par la suite), les objets (les sprites) et le "backdrop" ou fond de l'écran, zone située en dessous de chacune des sources précédentes. Les bits 8 à 13 du registre d'effets spéciaux caractérisent la seconde source, de la même manière que la première. Notez que nous pouvons sélectionner plusieurs entités pour une source donnée. Par exemple, nous pourrions choisir les backgrouns 2 et 3 en tant que première source. Enfin le sixième et le septième bit définissent la nature de l'effet spécial à appliquer aux sources. L'encadré 1 référence les valeurs possibles.

Note 1
00 : aucun effet 01 : alpha-blending 10 : fade-out 11 : fade-in

Pour terminer, la GameBoy Advance utilise deux autres registres pour définir les niveaux des effets :

 
Sélectionnez

#define REG_COLEV *(u16*) 0x4000052
#define REG_COLY *(u16*) 0x4000054

Le premier de ces registres, REG_COLEV, sert à paramétrer le niveau de transparence des sources. Les cinq premiers bits désignent la valeur du coefficient eva tandis que les bits 8 à 12 définissent la valeur de evb. La macro BLEND qui suit simplifie la définition des taux de transparence des sources, le paramètre a correspondant à eva et b à evb :

 
Sélectionnez

#define BLEND(a, b) (a | (b << 8))

Les cinq premiers bits du registre REG_COLY seront utilisés par les effets fade-in et fade-out. Le programme d'exemple présent sur le CD-Rom reprend la réalisation sur les sprites du mois précédent et ajoute, outre les effets de fondu, la possbilité de faire varier le taux de transparence du sprite par le biais des touches L et R. La suite de notre exemple se base sur la définition du mode graphique de cette manière :

 
Sélectionnez

REG_DISPCNT = MODE_4 | BG2_ENABLE;

Nous employons un mode 4 au sein duquel le background 2 a été activé. L'exécution de l'effet d'alpha-blending s'ordonne de la sorte :

 
Sélectionnez

alpha = 8;
REG_COLEV = BLEND(16, alpha);
REG_BLDMOD = TARGET1_OBJ | TARGET2_BG2 | SFX_ALPHA;

Dans cette situation, le sprite, situé sur la source OBJ, apparaîtra opaque derrière une image à moitié transparente. La fonction getInput() fait varier la valeur de alpha entre 0 et 16 suivant la touche pressée. Quand la première source de transparence désigne les objets, tous les sprites présents à l'écran sont affectés par l'effet d'alpha-blending. Cette contrainte peut se révèler malheureuse dans un jeu, si le fantôme est transparent, le héros n'a pas lieu de l'être. Pour remédier à ce problème, il suffit de ne sélectionner aucune source primaire ! Dans ce cas, seuls certains sprites particuliers pourront être affichés en semi-transparence. Voici la ligne à modifier pour activer l'effet sur un sprite donné :

 
Sélectionnez

xwing->oam->attribute0 = COLOR_256 | MODE_ALPHA | ROTATION_FLAG | SQUARE | xwing->y;

Le MODE_ALPHA active la semi-transparence en modifiant le mode objet du sprite. Les modes possibles sont le mode normal (dixème et onzième bits à zéro sur l'attribut OAM numéro 0), le mode semi-transparent (valeur 01) et le mode fenêtré (valeur 10). Tous les effets proposés par le registre REG_BLDMOD agissent par défaut sur l'ensemble de l'écran. Ces effets peuvent néanmoins se voir restreints à une portion donnée de l'écran en employant les fenêtres.

5. Les fenêtres

Le code source présenté sur le CD-Rom accompagnant le magazine délivre un fichier nommé windows.h relatif à la gestion des fenêtres graphiques de la GBA. Toutefois, le programme d'exemple n'utilise pas du tout son contenu. Nous allons expliquer la nature et l'emploi de ces fenêtres et nous vous conseillons d'essayer de les utiliser par vous-mêmes à titre d'exercice.

La GameBoy Advance propose deux fenêtres graphiques particulières. Ces fenêtre permettent de définir des zones d'affichages au sein desquelles les effets spéciaux sont ou non permis. Une fenêtre peut également activer l'affichage ou non des backgrounds et des sprites. Ces paramètrages peuvent se voir aussi définis à l'extérieur des fenêtres. L'activation des fenêtre a lieu lors de la déclaration du mode graphique. Les bits 13, 14 et 15 du registre REG_DISPCNT activent respectivement les fenêtres 0 et 1 ainsi que la fenêtre spéciale OBJ. Ainsi pour afficher la première fenêtre en mode 4 :

 
Sélectionnez

REG_DISPCNT = MODE_4 | BG2_ENABLE | WIN0_ENABLE;

Chaque fenêtre se voit liée à deux registres déterminant leur position et leur taille.

 
Sélectionnez

#define REG_WIN0H *(u16*) 0x4000040
#define REG_WIN0V *(u16*) 0x4000044

Nous définissons ici la première fenêtre. Le registre sur 16 bits REG_WIN0H code sur les 8 bits de poids faible la coordonnée x du point bas-gauche de la fenêtre. Les 8 derniers bits spécifient la coordonnée x du coin haut-droit. Le registre REG_WIN0V définit de la même manière les coordonnées y des points bas-gauche et haut-droit de la fenêtre. Lors d'un recouvrement des deux fenêtres, la fenêtre 0 possède une priorité supérieure à la fenêtre 1.

Le contrôle des éléments affichés à l'intérieur et en dehors des fenêtre a lieu par l'intermédiaire de deux registres supplémentaires sur 16 bits :

 
Sélectionnez

#define REG_WININ *(u16*) 0x4000048
#define REG_WINOUT *(u16*) 0x400004A

La définition des sources affichées à l'intérieur de la première fenêtre a lieu sur les cinq premiers bits du registre REG_WININ. Ce registre se scinde en deux octets, un pour chaque fenêtre. L'octet commençant au neuvième bit (le bit 8) caractérise la seconde fenêtre. Ces cinq premiers bits activent ou désactivent l'affichage des backgrounds 0 à 3 et des objets. Enfin, le sixième bit active ou désactive les effets spéciaux. Ces derniers sont calculés et affichés lorsque le bit vaut 1.

Le registre REG_WINOUT se décompose également en deux octets. Pourtant le premier octet agit sur les deux fenêtres à la fois. Les 8 bits de poids fort de ce registre définissent l'affichage à l'intérieur de la fenêtre OBJ. L'utilisation de cet octet est la même que pour les précédents.

La prochaine étape de notre rubrique consacrée à la GameBoy Advance concernera une implémentation effective des fenêtres ainsi que la découverte des fameux backgrounds déjà si souvent évoqués. Ceux-ci nous permettrons d'afficher du texte, des décors complexes... Et bien sûr, nous apprendrons quels effets spéciaux peuvent agir sur ces nouvelles entités.

6. Téléchargements