Sprites et clavier
Gameboj – Étape 10

1 Introduction

Le but de cette étape est d'une part de terminer le composant simulant le contrôleur LCD, afin de lui ajouter la gestion des sprites, et d'autre part d'écrire le composant simulant le clavier du Game Boy.

Comme cela a déjà été dit à plusieurs reprises, un sprite — parfois appelé lutin en français — est une petite image qui peut être placée n'importe où à l'écran et qui est utilisée pour les éléments mobiles des jeux : personnages, projectiles, etc. Le contrôleur LCD du Game Boy peut gérer un total de 40 sprites de 8×8 ou 8×16 pixels, dont les caractéristiques sont stockées dans une mémoire dédiée, décrite ci-dessous.

1.1 Mémoire d'attributs d'objets

En plus de la mémoire vidéo présentée à l'étape précédente, le contrôleur LCD possède une seconde mémoire vive, nommée mémoire d'attributs d'objets (object attribute memory ou OAM). Le nom de cette mémoire provient du fait que la documentation officielle du Game Boy nomme les sprites des objets (objects). Autant que possible, nous utiliserons ici le terme de sprite, d'une part car il est plus standard, et d'autre part car il prête moins à confusion.

1.1.1 Contenu

La mémoire d'attributs d'objets contient 160 octets, visibles sur le bus entre les adresses FE0016 et FEA016 (exclu). Ces 160 octets permettent de stocker les attributs de 40 sprites, numérotés de 0 à 39. Les attributs d'un sprite sont donc constitués de 4 octets qui, dans l'ordre dans lequel ils apparaissent en mémoire, sont :

  • la coordonnée y du sprite à l'écran,
  • la coordonnée x du sprite à l'écran,
  • l'index de la tuile à utiliser pour l'image du sprite,
  • un octet contenant quatre caractéristiques binaires, décrites plus bas.

Les coordonnées du sprite sont stockées avec un décalage, de sorte que pour obtenir la coordonnée à l'écran du pixel haut-gauche du sprite, il faut soustraire 8 de la coordonnée x donnée, et 16 de la coordonnée y. Cette convention peut paraître étrange mais elle garantit que toutes les coordonnées sont positives, même lorsque les sprites ne sont que partiellement visibles à l'écran, ce qui évite d'avoir à se préoccuper de problèmes de signe.

L'index de la tuile désigne toujours une des 256 tuiles situées dans la plage comprise entre les adresses 800016 et 900016 (exclu). L'index 0 correspond à la tuile dont l'image commence à l'adresse 800016, l'index 1 à la tuile dont l'image commence à l'adresse 801016, et ainsi de suite. En d'autres termes, cet index est interprété exactement comme celui des tuiles composant l'image de fond et la fenêtre lorsque le bit TILE_SOURCE du registre LCDC vaut 1. Dès lors, les 128 tuiles dont l'image se trouve dans la plage comprise entre les adresses 900016 et 980016 (exclu) ne sont pas utilisables pour les sprites.

Le dernier octet des attributs ne contient que 4 bits utiles, qui décrivent différentes caractéristiques du sprite. Leur signification est résumée dans la table suivante :

Bit Nom Signification
4 PALETTE Palette à utiliser pour le sprite (OBP0 ou OBP1)
5 FLIP_H Inversion horizontale de l'image du sprite
6 FLIP_V Inversion verticale de l'image du sprite
7 BEHIND_BG Plan auquel appartient le sprite (arrière ou avant)

Les quatre bits de poids faible, d'index 0 à 3, sont inutilisés.

Le bit PALETTE indique la palette à utiliser pour transformer les couleurs du sprite. S'il vaut 0, alors la palette contenue dans le registre OBP0 du contrôleur LCD est utilisée, sinon c'est celle contenue dans le registre OBP1 qui l'est.

Les bits FLIP_H et FLIP_V indiquent respectivement si l'image du sprite doit être inversée horizontalement ou verticalement. Si un de ces bits vaut 1, alors l'image est inversée dans la direction correspondante, sinon elle ne l'est pas.

Finalement, si le bit BEHIND_BG vaut 1, le sprite est à l'arrière-plan, c-à-d que son image apparaît derrière celle de l'image de fond. Les autres sprites sont à l'avant-plan, et leur image apparaît devant celle de fond.

1.1.2 Copie rapide

Pour afficher des sprites à l'écran, il est nécessaire de remplir la mémoire d'attributs d'objets avec les données leur correspondant. Ce remplissage peut se faire de différentes manières, entre autres en copiant une zone de 160 octets depuis une mémoire quelconque — p.ex. la mémoire morte de la cartouche — vers la mémoire d'attributs d'objets.

Une telle copie peut bien entendu être faite par le programme exécuté par le processeur, au moyen d'une boucle copiant les octets les uns après les autres. Toutefois, une boucle de ce type nécessite plus de 10 cycles par octet copié, ce qui est coûteux.

Pour accélérer une telle copie, les concepteurs du Game Boy ont donc intégré au contrôleur LCD un mécanisme dédié à la copie rapide de 160 octets depuis une adresse (presque) quelconque vers la mémoire d'attributs d'objets. Ce mécanisme peut copier un octet par cycle et est donc plus de 10 fois plus rapide qu'une boucle équivalente exécutée par le processeur.

Afin obtenir ces excellentes performances, le contrôleur LCD accède directement à la mémoire — au bus, en réalité —, pour y lire les données à copier. Pour cette raison, on nomme ce mécanisme un accès direct à la mémoire (direct memory access ou DMA).

Pour demander au contrôleur LCD de démarrer une copie par accès direct à la mémoire, il suffit d'écrire une valeur dans son registre DMA. Cette valeur donne les 8 bits de poids fort de l'adresse depuis laquelle les données doivent être copiées, les 8 bits de poids faible valant implicitement 0. Le contrôleur LCD copie alors 1 octet par cycle jusqu'à ce qu'il en ait copié 160 et rempli ainsi totalement la mémoire d'attributs d'objets.

Par exemple, si à un cycle donné, la valeur 4116 est écrite dans le registre DMA, alors le contrôleur LCD va copier les 160 octets compris entre l'adresse 410016 et 41A016 (exclu) dans la mémoire d'attributs d'objets, c-à-d aux adresses FE0016 à FEA016.

Il faut noter que sur le Game Boy réel le bus est totalement monopolisé par le contrôleur LCD lorsqu'une telle copie est en cours, et le processeur n'y a plus accès. Il ne peut donc même plus lire les instructions qu'il doit exécuter, sauf si celles-ci proviennent de sa mémoire haute, car il peut accéder à cette dernière sans passer par le bus. Les programmes utilisant le DMA pour effectuer des copies exécutent donc durant ce temps un programme placé dans la mémoire haute et qui ne fait généralement rien d'autre qu'attendre durant 160 cycles.

Cette limitation de l'accès au bus n'a aucune influence sur notre simulation, et nous ne tenterons donc pas de la reproduire.

1.2 Dessin des sprites

Comme nous l'avons vu, le contrôleur LCD calcule l'image à afficher à l'écran ligne par ligne. Le contenu de chaque ligne est déterminé en composant l'image de fond, la fenêtre et les sprites. Chacune de ces composantes peut être activée ou non par un bit dans le registre LCDC, qui dans le cas des sprites est le bit OBJ (1).

Pour comprendre la manière dont les sprites sont dessinés puis combinés avec l'image de fond et la fenêtre, il est bon de considérer que chaque ligne affichée est le résultat de la superposition de trois lignes calculées indépendamment :

  1. la ligne des sprites d'arrière-plan,
  2. la ligne de l'image de fond et de la fenêtre,
  3. la ligne des sprites d'avant-plan.

La manière dont la ligne de l'image de fond et la fenêtre est calculée a été décrite à l'étape précédente ; la manière dont les lignes de sprites le sont est décrite ci-dessous.

Avant d'examiner le calcul des lignes de sprite, il faut toutefois noter qu'en raison d'une limitation matérielle, un maximum de 10 images de sprites peuvent être combinées pour une ligne donnée. Dès lors, s'il y a plus de 10 sprites dont l'image intersecte une ligne, seuls les 10 premiers — dans l'ordre de leur index, et indépendamment du fait qu'ils soient à l'arrière- ou à l'avant-plan — sont pris en compte.

1.2.1 Calcul des lignes de sprites

La ligne des sprites d'un plan donné — arrière- ou avant-plan — est calculée par superposition des images de tous les sprites de ce plan. Bien entendu, avant d'être superposées, les images des sprites sont placées horizontalement sur la ligne en fonction de leur coordonnée x, et la palette spécifique au spriteOBP0 ou OBP1, en fonction du bit PALETTE — est appliquée à leurs couleurs. Cela fait, elles sont empilées selon l'ordre donné par :

  1. la coordonnée x du sprite, puis, en cas d'égalité,
  2. l'index du sprite,

la première se trouvant au sommet de la pile, c-à-d devant les autres.

Par exemple, admettons que les images des sprites d'index 0 à 15 aient une intersection avec la ligne à dessiner. En raison de la limitation susmentionnée, seuls les 10 premiers sont pris en compte, c-à-d ceux ayant les index 0 à 9 (inclus). Admettons maintenant que ces 10 sprites aient les caractéristiques suivantes — où le type est soit B (background) pour les sprites d'arrière-plan, soit F (foreground) pour ceux d'avant-plan :

index 0 1 2 3 4 5 6 7 8 9
coordonnée x 50 40 30 20 20 30 40 50 20 20
type B F F B B F F F B B

En triant ces sprites selon l'ordre décrit ci-dessus — coordonnée x d'abord, index ensuite — on obtient les index : 3, 4, 8, 9, 2, 5, 1, 6, 0, 7. Une fois ordonnés, ces index peuvent être séparés en deux listes ordonnées contenant :

  1. les index de sprites d'arrière-plan : 3, 4, 8, 9, 0,
  2. les index de sprites d'avant-plan : 2, 5, 1, 6, 7.

Le sprite d'index 3 est donc celui qui est au sommet de la pile des sprites d'arrière-plan, c-à-d que son image se trouve devant celle des autres sprites de ce plan. Sous cette image se trouve celle du sprite d'index 4, et ainsi de suite. Pour les sprites d'avant-plan, celui d'index 2 est au sommet de la pile, donc devant les autres, et ainsi de suite.

1.2.2 Sprites d'arrière-plan

La manière dont la ligne des sprites d'arrière plan est combinée avec celle de l'image de fond est subtile et mérite d'être détaillée.

Jusqu'à présent, nous avions (implicitement) supposé que tous les pixels de l'image de fond/fenêtre étaient opaques, car aucune autre image ne pouvait être affichée derrière elle. Or les sprites d'arrière-plan changent la donne, puisqu'ils sont justement derrière l'image de fond.

La question se pose donc de savoir comment composer l'image de fond et les sprites d'arrière-plan, en particulier dans le cas où les deux contiennent des pixels transparents superposés.

La règle utilisée sur le Game Boy est la suivante : les pixels opaques des sprites d'arrière-plan (et eux seuls) sont visibles à travers les pixels transparents de l'image de fond. En d'autres termes, les pixels transparents de l'image de fond ne le sont réellement que si au moins un pixel opaque d'un sprite d'arrière-plan se trouve derrière eux, sans quoi ils sont opaques.

Par exemple, admettons qu'un pixel de l'image de fond ait, avant application de la palette, la couleur 0. Comme nous l'avons vu à l'étape 8, ces pixels sont toujours ceux qui sont considérés transparents sur le Game Boy. Si derrière un tel pixel se trouve un pixel opaque d'un sprite d'arrière plan, disons de couleur 2, alors le pixel résultant de leur combinaison aura la couleur 2, qui sera celle visible à l'écran. Par contre, si derrière ce pixel transparent de l'image de fond ne se trouve aucun pixel opaque d'un sprite d'arrière-plan, alors le pixel résultant de leur combinaison sera celui de l'image de fond, considéré opaque ! Sa couleur finale sera donc celle résultant de la transformation de la couleur 0 par la palette de l'image de fond.

1.2.3 Hauteur des sprites

Les images des sprites du Game Boy font généralement 8×8 pixels, mais il est aussi possible de faire en sorte qu'elles aient une taille de 8×16 pixels, en mettant à 1 le bit OBJ_SIZE du registre LCDC.

Lorsque les sprites font 16 pixels de haut, leur image est constituée de deux tuiles superposées : celle du haut est celle dont l'index est donné dans les attributs du sprite, qui doit être pair, celle du bas est celle d'index suivant.

Par exemple, si le bit OBJ_SIZE du registre LCDC vaut 1 et qu'un sprite utilise la tuile d'index 20, alors la partie supérieure de son image est celle de la tuile 20, tandis que sa partie inférieure est celle de la tuile 21.

1.3 Clavier

Le clavier du Game Boy, nommé joypad en anglais, est très simple et composé de 8 boutons :

  • quatre boutons de direction (haut, bas, gauche et droite), organisés en une croix directionnelle,
  • deux boutons d'action nommés A et B,
  • deux boutons nommés Select et Start.

Au niveau électronique, ces boutons sont organisés en une matrice de deux lignes de quatre boutons chacune, de la manière suivante :

  0 1 2 3
0 droite gauche haut bas
1 A B Select Start

Cette organisation influence la manière dont l'état du clavier — c-à-d les boutons qui sont actuellement pressés — peut être lu par le programme s'exécutant sur le Game Boy, comme décrit ci-après.

L'état du clavier est encodé sous la forme d'un octet, stocké dans un registre nommé P1 et visible sur le bus à l'adresse FF0016. Les différents bits de ce registre ont la signification suivante :

Bit Signification
0 État de la colonne 0
1 État de la colonne 1
2 État de la colonne 2
3 État de la colonne 3
4 Sélection de la ligne 0
5 Sélection de la ligne 1
6 aucune, vaut toujours 1
7 aucune, vaut toujours 1

Notez que tous les bits de ce registre ont une signification inversée par rapport aux conventions habituelles, comme nous allons le voir ci-dessous. Cela signifie que ce qui est généralement représenté par un 1 l'est par un 0 dans ce registre, et inversément.

Les bits d'état (0 à 3) sont accessibles en lecture seule depuis le bus, c-à-d que leur valeur est préservée lors d'une écriture à l'adresse du registre P1.

Les bits de sélection (4 et 5) sont accessibles en lecture et en écriture depuis le bus, et déterminent la ou les lignes dont les boutons influencent les bits d'état.

Lorsque le bit de sélection d'une ligne vaut 0, la ligne en question est sélectionnée et les 4 touches appartenant à cette ligne influencent chacune le bit d'état correspondant à sa colonne : celui-ci vaut 0 si la touche en question est pressée, 1 sinon.

Lorsque les deux bits de sélection valent 0, les deux lignes sont sélectionnées et deux touches influencent alors le même bit d'état. Si au moins l'une d'entre elles est pressée, alors ce bit vaut 0, sinon il vaut 1.

Finalement, si aucun bit de sélection ne vaut 0, alors aucune ligne n'est sélectionnée, et tous les bits d'état valent 1.

Par exemple, admettons qu'à un moment donné les touches A et haut sont pressées, et que le bit de sélection de la ligne 0 (bit 4) vaut 1 tandis que celui de la ligne 1 (bit 5) vaut 0. Comme seule la ligne 1 est sélectionnée, seule la touche A influence les bits d'état, qui valent alors 1110 (du poids fort au poids faible). Le contenu complet du registre est donc 11011110.

Lorsque, suite à la pression d'une touche, l'un des bits d'état (bits 0 à 3) passe de 1 à 0, alors le clavier lève l'interruption JOYPAD (4) du processeur.

2 Mise en œuvre Java

La mise en œuvre Java de cette étape consiste à terminer la classe LcdController, écrire la classe Joypad représentant le clavier et finalement compléter la classe GameBoy.

2.1 Classe LcdController

Les ajouts à effectuer à la classe LcdController sont en gros au nombre de quatre :

  1. La méthode attachTo doit être redéfinie comme celle de la classe Cpu, afin que le contrôleur LCD ait la possibilité d'effectuer des lectures depuis le bus, nécessaires lorsqu'une copie par accès direct à la mémoire (DMA) est en cours.
  2. Les méthodes read et write doivent être augmentées afin de donner accès à la mémoire d'attributs d'objets. Comme toujours, l'interface AddressMap contient des constantes désignant l'adresse de début (OAM_START), l'adresse de fin (exclue, OAM_END) et la taille (OAM_RAM_SIZE) de cette mémoire. Notez que write doit également démarrer le processus de copie si une écriture est faite dans le registre DMA.
  3. La méthode cycle doit être augmentée pour copier le prochain octet vers la mémoire d'attributs d'objets lorsqu'une copie par accès direct à la mémoire est en cours.
  4. La méthode computeLine (ou équivalent) doit être augmentée afin de calculer les lignes de sprite d'arrière-plan et d'avant-plan, et de les combiner à la ligne de l'image de fond déjà calculée.

Les trois premiers ajouts ne devraient pas poser de problème particulier, le quatrième mérite quelques conseils, donnés ci-après.

2.1.1 Conseils de programmation

Avant de donner des conseils spécifiques, nous vous rendons attentifs au fait que le code — pas totalement trivial — permettant d'obtenir l'octet correspondant aux 8 bits de poids fort ou faible d'une ligne donnée d'une tuile donnée est quasi-identique pour l'image de fond, la fenêtre et les sprites. Il paraît donc raisonnable de l'extraire dans une méthode séparée.

  1. Calcul des sprites à afficher

    Le calcul des sprites à afficher sur une ligne donnée pourra être confié à une méthode spécifique, nommée p.ex. spritesIntersectingLine et retournant un tableau contenant les index des (au plus 10) sprites dont l'image intersecte la ligne donnée, triés selon l'ordre d'empilement.

    Une manière relativement simple et rapide de calculer ce tableau consiste à construire un premier tableau de 10 éléments de type int. Chacun de ces éléments contient deux informations empaquetées : dans les bits de poids fort, la coordonnée x du sprite, dans les bits de poids faible son index. Ce tableau est rempli avec les informations correspondant aux (au plus 10) premiers sprites ayant une intersection avec la ligne donnée. Ensuite, il est trié en ordre croissant ce qui, étant donné la technique d'empaquetage choisie, équivaut à le trier selon l'ordre d'empilement décrit plus haut. Finalement, un nouveau tableau contenant uniquement les index des sprites, dans l'ordre donné par le tri, est retourné.

    Pour effectuer le tri, notez que la classe Arrays offre une méthode permettant de trier seulement une partie d'un tableau d'entiers.

  2. Calcul de la ligne d'un sprite individuel

    Le calcul de la ligne correspondant à un sprite unique peut se faire très facilement en utilisant de manière judicieuse les méthodes de la classe LcdImageLine. L'idée de base est la suivante :

    • construire une ligne de 160 pixels de long ayant l'image du sprite dans son premier octet — c-à-d positionnée tout à gauche de la ligne —, tous les autres bits valant 0,
    • décaler cette ligne pour placer correctement l'image du sprite dans la ligne, en pensant bien au sens dans lequel effectuer ce décalage (!),
    • transformer les couleurs de la ligne au moyen de la palette appropriée.

    Cette ligne correspondant à un sprite individuelle peut ensuite être superposée à celles des autres sprites appartenant au même plan, dans le bon ordre, afin d'obtenir la ligne correspondant à ce plan-là.

  3. Composition de la ligne des sprites d'arrière-plan

    La manière dont la ligne des sprites d'arrière-plan est composée avec celle de l'image de fond a été décrite à la §1.2.2 et peut sembler complexe. En réalité, elle est très simple à mettre en œuvre si on se rappelle qu'un pixel de la ligne de l'image de fond n'est réellement transparent que si :

    • il l'est « à l'origine » (c-à-d son opacité est fausse), et
    • le pixel de la ligne des sprites d'arrière-plan correspondant est opaque.

    Cette condition s'exprime très facilement au moyen d'opérations bit à bit sur les vecteurs de bits représentant l'opacité des deux lignes en question, qui permettent de calculer l'opacité à utiliser pour effectuer leur superposition. Une fois ce vecteur d'opacité calculé, il suffit de le passer à la variante de la méthode below de la classe LcdImageLine prenant explictement une opacité en argument.

  4. Taille des sprites

    La gestion des sprites de 16 pixels de haut peut sembler difficile à première vue, mais il n'en est rien. En effet, étant donné la manière dont les images des tuiles sont organisées en mémoire, la première ligne de l'image d'une tuile peut aussi être vue comme la neuvième ligne de la tuile précédente, la seconde comme la dixième de la tuile précédente, et ainsi de suite.

    En conséquence, les deux seuls moments auxquels il est nécessaire de tenir compte de la hauteur des sprites dans le code sont :

    1. lors du calcul de l'ensemble des sprites dont l'image intersecte la ligne en cours de dessin,
    2. lors de la gestion du retournement vertical des sprites.

2.2 Classe Joypad

La classe Joypad (ou équivalent), du paquetage ch.epfl.gameboj.component, représente le clavier. Etant donné que cette classe simule un composant attaché au bus, elle implémente l'interface Component. Son constructeur prend en argument le processeur du Game Boy auquel le clavier appartient, dont elle a besoin pour lever des interruptions.

Les méthodes read et write ne font rien d'autre que donner accès au registre P1 dont la valeur est déterminée de la manière décrite plus haut. Notez que l'adresse de ce registre est comme d'habitude définie par une constante de l'interface AddressMap, dans ce cas REG_P1.

En plus de ces méthodes read et write, la classe Joypad offre deux méthodes publiques, nommées p.ex. keyPressed et keyReleased et permettant à ses utilisateurs de simuler la pression et le relâchement d'une touche. Chacune prend en argument la touche concernée, représentée par un élément d'une énumération publique imbriquée dans la classe Joypad, nommée p.ex. Key et contenant un élément par touche (RIGHT, LEFT, UP, DOWN, A, B, SELECT et START).

2.2.1 Conseils de programmation

Le fait que les bits du registre P1 aient la signification inverse de la signification habituelle peut rendre le code difficile à comprendre. Une manière simple de résoudre ce problème consiste à l'ignorer dans la plupart du code, et à inverser (au moyen de Bits.complement8) les bits de la valeur lue/écrite sur le bus.

D'autre part, il faut noter que, d'une manière ou d'une autre, il est nécessaire de stocker l'ensemble des touches pressées dans un ou plusieurs attributs de la classe Joypad, car le contenu du registre P1 ne suffit pas à lui tout seul pour savoir quelles touches sont pressées. Une manière simple de représenter cet ensemble consiste à avoir un entier de type int pour chaque ligne de touches, dans lequel un bit est à 1 si et seulement si la touche dont la colonne correspondante est pressée. La valeur du registre P1 peut très facilement en être déduite.

2.3 Classe GameBoy

La classe GameBoy doit être complétée pour ajouter un clavier (c-à-d une instance de JoyPad) au système. Cela implique de :

  • créer une instance de Joypad dans le constructeur et l'attacher au bus,
  • ajouter une méthode d'accès permettant d'obtenir cette instance de Joypad.

De plus, afin de terminer totalement la classe GameBoy, deux attributs publiques, statiques et finaux doivent y être ajoutés :

  1. le premier de type long donnant le nombre de cycles exécutés par seconde, qui pour mémoire vaut 220,
  2. le second de type double donnant le nombre de cycles exécutés par nanoseconde.

Cela fait, la classe GameBoy est enfin complète, et ne changera plus jusqu'à la fin du projet.

2.4 Tests

Pour tester votre code, vous pouvez utiliser une version modifiée du programme de test de l'étape précédente. Nous vous en proposons une ci-après, libre à vous de l'adapter à vos besoins.

Le programme ci-dessous attend un seul argument sur la ligne de commande, qui est le fichier ROM à utiliser. Il simule d'abord 30 millions de cycles, puis poursuit la simulation durant une seconde avec la touche A pressée, et une dernière seconde sans la touche A pressée. Ce petit scénario convient bien aux deux tests proposés plus bas.

public final class DebugMain3 {
  private static final int[] COLOR_MAP = new int[] {
    0xFF_FF_FF, 0xD3_D3_D3, 0xA9_A9_A9, 0x00_00_00
  };

  public static void main(String[] args) throws IOException {
    File romFile = new File(args[0]);
    long cycles = 30_000_000;

    GameBoy gb = new GameBoy(Cartridge.ofFile(romFile));
    gb.runUntil(cycles);
    gb.joypad().keyPressed(Key.A);
    gb.runUntil(cycles + (1L << 20));
    gb.joypad().keyReleased(Key.A);
    gb.runUntil(cycles + 2 * (1L << 20));

    LcdImage li = gb.lcdController().currentImage();
    BufferedImage i =
      new BufferedImage(li.width(),
			li.height(),
			BufferedImage.TYPE_INT_RGB);
    for (int y = 0; y < li.height(); ++y)
      for (int x = 0; x < li.width(); ++x)
	i.setRGB(x, y, COLOR_MAP[li.get(x, y)]);
    ImageIO.write(i, "png", new File("gb.png"));
  }
}

En exécutant ce programme de test en lui passant le fichier ROM de Flappy Boy, vous devriez voir l'image ci-dessous, sur laquelle plusieurs sprites sont utilisés pour représenter l'oiseau volant au milieu de l'image :

flappyboy-sprite.png

Figure 1 : Flappy Boy

En plus de cela, nous mettons à votre disposition une archive Zip contenant une ROM de test provenant du projet mooneye-gb de Joonas Javanainen. Le programme qu'elle contient, visible sur github, vérifie que les sprites sont dessinés dans le bon ordre. Avec le programme ci-dessus, vous devriez obtenir l'image suivante :

sprite-priority.png

Figure 2 : Test de l'ordre de dessin des sprites

3 Résumé

Pour cette étape, vous devez :

  • écrire la classe Joypad (ou équivalent) et terminer les classes LcdController et GameBoy en fonction des indications données plus haut,
  • tester votre code,
  • documenter la totalité des entités publiques que vous avez définies.

Aucun rendu n'est à faire pour cette étape avant le rendu final. N'oubliez pas de faire régulièrement des copies de sauvegarde de votre travail en suivant nos indications à ce sujet.