Les ressources
Un jeu, ce n'est pas que du code : ce sont aussi des images, des polices, des cartes, des sons, des tables de valeurs. Ce chapitre montre comment Simple8 les fait entrer dans le programme, les convertit au format de chaque machine, et les range en mémoire.
Qu'est-ce qu'une ressource ?
Une ressource est une donnée du programme qui n'est pas du code : une police de caractères, des tuiles, une palette, une carte de niveau, un son, une table calculée d'avance. Elle vient d'un fichier du projet, ou elle est fabriquée au moment de la compilation.
Nous en utilisons une depuis le chapitre 2 : la police de la PC Engine.
const int POLICE = addResource("font.bin");
addResource fait trois choses :
- à la compilation, il lit le fichier (ou fabrique les données) ;
- il range les données dans la cartouche, avec le programme ;
- il donne un identifiant, ici
POLICE, grâce auquel le programme retrouvera ces données pendant qu'il tourne.
Pour transformer les données au passage, on passe par une ResourceFactory, une « fabrique » de ressources : elle reçoit un fichier ou des paramètres, et produit les octets prêts à l'emploi.
const int VAGUE = addResource(ResourceFactory("TrigoMaker",
"function=sin&count=32&litude=10&offset=12"));
Le premier argument est le nom de la fabrique, le second ses paramètres, séparés par
des &, comme dans une adresse web.
Trois genres de ressources
Les ressources se distinguent par leur rapport à la machine : certaines sont les mêmes partout, d'autres s'adaptent à chaque machine, d'autres encore n'existent que pour une seule.
1. Les ressources génériques
Ce sont des données pures, qui ne dépendent d'aucun matériel : toutes les machines peuvent s'en servir telles quelles. Une table de sinus en est l'exemple type : 32 nombres entre 2 et 22, que ce soit pour un C64 ou une PC Engine.
| Fabrique | Ce qu'elle produit |
|---|---|
RawBinary | un fichier, tel quel. C'est ce que fait addResource("fichier") sans fabrique. |
TrigoMaker | une table de sinus ou de cosinus, calculée à la compilation |
TrackerSong | une chanson écrite dans un fichier texte, à la manière d'un tracker |
Les paramètres de TrigoMaker décrivent la table :
| Paramètre | Rôle | Ici |
|---|---|---|
function | sin ou cos | sin |
count | le nombre de valeurs, pour un tour complet | 32 |
amplitude | l'écart maximal autour du centre | 10 |
offset | le centre | 12 : les valeurs vont de 2 à 22 |
2. Les ressources multi-architectures
Ici, une même source est convertie au format de chaque machine. Une image
ne s'affiche pas de la même façon sur un C64 et sur une PC Engine : nombre de couleurs,
codage des pixels, palettes, tout diffère. La fabrique détecte l'architecture du
projet, celle de la ligne architecture, et lance la conversion qui lui
correspond.
| Fabrique | Source | Ce qu'elle produit |
|---|---|---|
SquareDot | un projet de l'éditeur SquareDot (.sqd) | les tuiles, les palettes et les cartes, au format de la machine : 4 bits par pixel sur PC Engine, 2 plans sur NES et Game Boy, un octet par ligne sur C64… |
BuildPaletteFromImageFile | une image | la palette de ses couleurs principales, au format de la machine |
const int TUILES = addResource(ResourceFactory("SquareDot", "niveaux.sqd&part=tiles"));
const int COULEURS = addResource(ResourceFactory("SquareDot", "niveaux.sqd&part=palettes"));
const int CARTE = addResource(ResourceFactory("SquareDot", "niveaux.sqd&part=map&map=Carte 1"));
Ces trois lignes restent les mêmes d'une machine à l'autre : seul le résultat change. Une conversion qui n'existe pas encore pour une machine est signalée :
ResourceFactory("SpriteMotionGenerator", …): not implemented for sega.megadrive yet (available: nec.pce)
3. Les ressources spécifiques
Certaines ressources n'ont de sens que pour une machine, parce qu'elles visent un circuit qu'elle seule possède :
| Fabrique | Machine | Ce qu'elle produit |
|---|---|---|
PSGSample | PC Engine | un son numérisé pour le circuit sonore de la console |
Vgm | Mega Drive | une musique pour le lecteur du Z80 |
MixerSample | Mega Drive | un bruitage pour le pilote son du Z80 |
ResourceFactory("PSGSample", …): for nec.pce only, not for commodore.c64
En résumé. Générique : les mêmes octets partout. Multi-architecture : une source, un format par machine. Spécifique : une seule machine.
Toutes les machines n'ont pas addResource
addResource range ses données dans la cartouche de la PC
Engine et de la Mega Drive, ou dans l'image de l'Amiga.
Sur les autres machines, le compilateur s'arrête, et indique la solution :
addResource: the resources are placed in the cartridge of the PC Engine (nec.pce) and of the
Mega Drive (sega.megadrive), or the Amiga image (commodore.amiga), not on commodore.c64;
put the data in a const array (generated by a script, in a unit)
Sur C64, NES, Game Boy et Amstrad CPC, les données vont donc dans des tableaux
constants, dans une unit. Les outils de la suite Simple8 les écrivent eux-mêmes :
SquareDot exporte, pour ces machines, une unit (.s8u) qui contient les
tuiles et les cartes, que le programme ajoute par addunit. 3DSpriteMaker fait
de même pour ses sprites, sur PC Engine et Game Boy.
// Carte.s8u, exporté par SquareDot pour le C64
unit Carte {
const int W = 80;
const int H = 25;
const ubyte CHARS[56] = { … }; // les caractères dessinés
…
}
Le genre de la ressource ne change pas : une table de sinus reste générique, qu'elle
soit produite par TrigoMaker ou écrite dans un tableau.
Lire une ressource
Sur PC Engine, le processeur ne voit qu'une petite partie de la cartouche à la fois. Une
ressource n'est donc pas directement lisible : il faut d'abord rendre visible la banque qui
la contient. La bibliothèque sys.resource s'en charge :
| Fonction | Rôle |
|---|---|
resource.mapToMPR2(id) | rend visible la banque de la ressource, dans la fenêtre $4000-$5FFF |
resource.getOffset(id) | sa position dans la banque |
resource.getLength(id) | sa taille en octets |
resource.getBank(id) | le numéro de sa banque |
resource.mapToMPR2(.VAGUE); // la banque de VAGUE à $4000
uint debut = 0x4000 + resource.getOffset(.VAGUE); // l'adresse de ses données
ubyte premiere = readMem(debut); // sa première valeur : 12
Sur Mega Drive et Amiga, plus simples, l'identifiant est directement l'adresse des
données sur 32 bits : on le range dans un ulong, et on lit avec
readMem.
Choisir des noms clairs. L'identifiant d'une ressource devient un nom de
l'assembleur. Un nom comme A, X ou Y se confond
avec un registre du processeur, et l'assemblage échoue. Préférez POLICE,
TUILES_NIVEAU1…
L'organisation en mémoire : la HuCard
Sur une machine à cartouche, comment ranger des centaines de kilo-octets de ressources dans une mémoire que le processeur ne voit que par petits morceaux ? Prenons la PC Engine.
Des banques de 8 Ko
La HuCard est découpée en banques de 8 Ko. Le compilateur y range le
code résident, puis les contextes, puis les ressources. Une règle s'impose : une
ressource de 8 Ko au plus ne chevauche jamais deux banques, pour qu'un seul
mapToMPR2 suffise à la lire en entier. Une ressource plus grande commence au
début d'une banque et continue dans les suivantes ; elle se lit banque par banque.
Les tables d'index
Pour retrouver chaque ressource, le compilateur écrit trois tables dans la ROM, où
l'identifiant sert d'indice : la banque de chaque ressource, sa
position dans la banque, et sa taille.
getBank, getOffset, getLength et
mapToMPR2 ne font que lire ces tables.
C'est ce qui permet d'optimiser la place : puisque le programme retrouve toujours une ressource par ses tables, le compilateur est libre de la ranger n'importe où, sans suivre l'ordre des déclarations. Il en profite pour combler les trous :
- les ressources de plus de 8 Ko sont rangées d'abord, chacune au début d'une banque ;
- les autres sont prises de la plus grande à la plus petite, et chacune va dans la première banque où il reste assez de place, y compris la fin d'une grande ressource ;
- une nouvelle banque n'est ouverte que si aucune n'a la place.
- Rangées dans l'ordre de déclaration, elles laissent des trous : C ne tient pas après A et B, et la fin de BIG reste vide. Il faut 4 banques.
- En comblant les trous, C et D se logent dans la fin de BIG : 3 banques suffisent, et la cartouche est plus petite d'une banque.
Voici les tables que le compilateur écrit pour cet exemple, dans l'ordre des identifiants A, B, C, D, BIG :
_resource_banks: // la banque de chaque ressource
.db RESOURCE_FIRST_BANK+2, RESOURCE_FIRST_BANK+2, RESOURCE_FIRST_BANK+1,
RESOURCE_FIRST_BANK+1, RESOURCE_FIRST_BANK+0
_resource_offsets: // sa position dans la banque
.dw $0000, $1388, $0EE0, $1A98, $0000
RESOURCE_FIRST_BANK est la première banque après le code résident et les
contextes. Pour C, l'identifiant 2, les tables donnent la banque
RESOURCE_FIRST_BANK+1 et la position $0EE0 (3 808) : juste après
la fin de BIG. Le programme n'a rien à savoir de ce rangement : il appelle
mapToMPR2(.RES_C), comme avant.
Ressources et contextes
Sur une machine à cartouche
Sur PC Engine, une ressource déclarée dans une unit de contexte va dans la même table que les autres, après les banques des contextes. Elle se lit de la même façon, depuis n'importe quel contexte.
unit Niveau {
const int VAGUE = addResource(ResourceFactory("TrigoMaker", "function=sin&count=32&litude=10&offset=12"));
initContext() {
uint debut;
resource.mapToMPR2(.VAGUE);
debut = 0x4000 + resource.getOffset(.VAGUE);
// copier en mémoire vidéo, lire des valeurs…
}
…
}
Cela fonctionne parce que les deux fenêtres sont séparées : le code du contexte est
visible à partir de $6000, la banque d'une ressource à $4000. Le
contexte peut donc lire ses données sans se masquer lui-même. Et rien n'est copié au
chargement : les données restent dans la ROM, où on les lit au besoin.
Le bon moment pour copier une ressource en mémoire vidéo est
initContext() : les interruptions y sont masquées, la copie n'est pas
interrompue (chapitre 8).
Sur une machine à disquette
Sur C64 et Amstrad CPC, un contexte est un fichier chargé depuis la disquette. Ses tableaux constants font partie de ce fichier : ils arrivent en mémoire avec lui, et disparaissent quand un autre contexte prend sa place. Les données d'un niveau ont donc intérêt à être déclarées dans le contexte de ce niveau : elles n'occupent la mémoire que lorsqu'on y joue.
| Cartouche (PC Engine) | Disquette (C64, CPC) | |
|---|---|---|
| Les données d'un contexte | restent dans la ROM, toujours disponibles | sont chargées avec le fichier du contexte |
| Pour les lire | rendre visible leur banque (mapToMPR2) | directement : elles sont en mémoire |
| La limite | la taille de la cartouche | la place du contexte en mémoire |
Un exemple : une vague de sinus
Ce programme dessine une vague à l'écran : une étoile par colonne, à la hauteur donnée par une table de sinus. Sur PC Engine, la table et la police sont deux ressources génériques.
// Les ressources : une vague de sinus (PC Engine)
architecture nec.pce;
addlib sys.resource;
addlib vce.base;
addlib vdc.base;
addlib vdc.screentext;
unit {
// deux ressources génériques : un fichier tel quel, et une table calculée à la compilation
const int POLICE = addResource("font.bin");
const int VAGUE = addResource(ResourceFactory("TrigoMaker",
"function=sin&count=32&litude=10&offset=12"));
main() {
int source;
int length;
uint debut;
// la police : rendue visible à $4000, puis copiée en mémoire vidéo
vdc.setBATSize(vdc.BAT_32x32);
vdc.initScreen();
resource.mapToMPR2(.POLICE);
source = 0x4000 + resource.getOffset(.POLICE);
length = resource.getLength(.POLICE);
vdc.writeToVRAM(source, 0x1000, length);
vce.setPalette(0, 0, RGB(0, 0, 2));
vce.setPalette(0, 1, RGB(7, 7, 7));
vce.setPalette(1, 1, RGB(2, 6, 7));
vdc.clearScreen();
vdc.setFontColor(0);
vdc.setCursor(7, 1);
vdc.print("UNE VAGUE DE SINUS");
// la table : rendue visible à son tour, puis lue case par case
resource.mapToMPR2(.VAGUE);
debut = 0x4000 + resource.getOffset(.VAGUE);
vdc.setFontColor(1);
for (ubyte x = 0; x < 32; x++) {
vdc.setCursor(x, (ubyte)(readMem(debut + x) + 3)); // la hauteur : la case x, sous le titre
vdc.putChar('*');
}
}
}
- Les deux ressources se lisent de la même façon :
mapToMPR2, puis l'adresse$4000plus la position. - La police remplit à elle seule une banque (8 192 octets) : la table de sinus commence donc au début de la banque suivante.
- Une seule banque est visible à la fois à
$4000: on a fini de copier la police avant de rendre visible la table.
TrigoMaker.La version Commodore 64
Le C64 n'a pas addResource : les mêmes 32 valeurs sont rangées dans un
tableau constant. La ressource reste générique ; seule sa façon d'entrer dans le
programme change.
// Les ressources : une vague de sinus (Commodore 64)
architecture commodore.c64;
addlib vic.base;
addlib vic.screentext;
unit {
// pas de addResource sur C64 : les mêmes 32 valeurs, dans un tableau constant
const ubyte VAGUE[] = {
12, 14, 16, 18, 19, 20, 21, 22, 22, 22, 21, 20, 19, 18, 16, 14,
12, 10, 8, 6, 5, 4, 3, 2, 2, 2, 3, 4, 5, 6, 8, 10
};
main() {
vic.initScreen();
vic.setBorderColor(vic.BLACK);
vic.setBackgroundColor(vic.BLUE);
vic.clearScreen();
vic.setFontColor(vic.WHITE);
vic.setCursor(11, 0);
vic.print("UNE VAGUE DE SINUS");
vic.setFontColor(vic.CYAN);
for (ubyte x = 0; x < 32; x++) {
vic.setCursor((ubyte)(x + 4), .VAGUE[x]); // la hauteur : la case x du tableau
vic.putChar('*');
}
}
}
À retenir
- Une ressource est une donnée du programme, lue ou fabriquée à la compilation par
addResource, au besoin à travers uneResourceFactory. - Trois genres : générique (les mêmes octets partout), multi-architecture (une source, un format par machine), spécifique (une seule machine).
addResourceexiste sur PC Engine, Mega Drive et Amiga ; ailleurs, les données vont dans des tableaux constants, souvent exportés par les outils.- Sur PC Engine, des tables d'index (banque, position, taille) retrouvent chaque ressource : le compilateur peut donc les ranger où il veut, et comble les trous des banques de 8 Ko.
- Une ressource se lit en rendant sa banque visible (
mapToMPR2), y compris depuis un contexte ; le bon moment pour la copier estinitContext().
À vous de jouer
- Doublez l'amplitude de la vague. Tient-elle encore dans l'écran ? Sinon, quelle est la plus
grande vague possible, et que faut-il changer avec l'amplitude ?
Voir la solutionMasquer la solution
Commençons par faire l'essai :
amplitude=20, sans rien changer d'autre. Les valeurs devraient aller de 12 − 20 = −8 à 12 + 20 = 32. Deux problèmes apparaissent à l'écran :- la table est faite d'octets sans signe, qui ne descendent pas sous 0.
TrigoMakerremplace les valeurs négatives par 0 : le haut de la vague est écrasé en un long plateau (on le vérifie dansasm/resources.asm, où la table contient neuf$00de suite) ; - le bas, jusqu'à la ligne 32 + 3 = 35, sort de l'écran, qui n'a que 28 lignes visibles.

Amplitude 20 : le sommet est aplati, le bas est hors de l'écran. Il faut donc aussi déplacer le centre (
offset) pour que la plus petite valeur reste positive. Mais cela ne suffit pas : une vague d'amplitude 20 s'étend sur 2 × 20 + 1 = 41 lignes, plus que les 28 lignes de la PC Engine et les 25 du C64. Une vague deux fois plus haute, à raison d'une ligne par unité, ne peut pas tenir dans l'écran. Ce qu'on peut faire, c'est l'agrandir autant que l'écran le permet. La règle :offsetau moins égal àamplitude(pas de valeur négative), etoffset + amplitude, plus le décalage sous le titre, au plus égal à la dernière ligne.Sur PC Engine, les lignes 3 à 27 sont libres sous le titre, soit 25 lignes : une amplitude de 12 avec un centre à 12 donne des valeurs de 0 à 24, dessinées des lignes 3 à 27.
// amplitude 12, centre 12 : de 0 à 24, soit les lignes 3 à 27 une fois sous le titre const int VAGUE = addResource(ResourceFactory("TrigoMaker", "function=sin&count=32&litude=12&offset=12"));
Amplitude 12 : la plus grande vague qui tient sous le titre. Sur Commodore 64, le titre occupe la ligne 0 et il reste les lignes 1 à 24 : une amplitude de 11 avec un centre à 12 (valeurs de 1 à 23) tient. Faute de
TrigoMaker, il faut recalculer soi-même les 32 valeurs, 12 + 11 × sin(2π × x / 32) arrondies :// amplitude 11, centre 12 : de 1 à 23, toutes les lignes sous le titre const ubyte VAGUE[] = { 12, 14, 16, 18, 20, 21, 22, 23, 23, 23, 22, 21, 20, 18, 16, 14, 12, 10, 8, 6, 4, 3, 2, 1, 1, 1, 2, 3, 4, 6, 8, 10 };C'est là l'avantage d'une table fabriquée à la compilation : sur PC Engine, changer un paramètre suffit ; sur C64, il faut refaire le calcul et recopier les nombres.
- la table est faite d'octets sans signe, qui ne descendent pas sous 0.
- Dessinez une deuxième vague, un cosinus, d'une autre couleur.
Voir la solutionMasquer la solution
Sur PC Engine, on déclare une deuxième ressource
TrigoMaker, avecfunction=coset les mêmes autres paramètres. Une nouvelle couleur demande une nouvelle palette : la palette 2 reçoit un orange dans sa couleur 1. Le cosinus se dessine ensuite comme le sinus, après avoir rendu sa banque visible avecmapToMPR2.const int VAGUE = addResource(ResourceFactory("TrigoMaker", "function=sin&count=32&litude=10&offset=12")); const int VAGUE2 = addResource(ResourceFactory("TrigoMaker", "function=cos&count=32&litude=10&offset=12")); // la même, en cosinusvce.setPalette(1, 1, RGB(2, 6, 7)); vce.setPalette(2, 1, RGB(7, 5, 1)); // palette 2 : orange, pour le cosinus// le cosinus : sa banque est rendue visible avant de le lire resource.mapToMPR2(.VAGUE2); debut = 0x4000 + resource.getOffset(.VAGUE2); vdc.setFontColor(2); for (ubyte x = 0; x < 32; x++) { vdc.setCursor(x, (ubyte)(readMem(debut + x) + 3)); vdc.putChar('*'); }
Le sinus en bleu, le cosinus en orange : la même courbe, décalée d'un quart de tour. Ici, les deux petites tables sont rangées dans la même banque (le compilateur comble les trous), et le deuxième
mapToMPR2ne change donc rien. Mais le programme n'a pas à le savoir : appelermapToMPR2avant de lire une ressource est la règle sûre, quel que soit le rangement.Sur Commodore 64, inutile d'écrire une seconde table : un cosinus est un sinus en avance d'un quart de tour. Avec 32 valeurs par tour, il suffit de lire la même table 8 cases plus loin, en revenant au début après la fin grâce au modulo.
for (ubyte x = 0; x < 32; x++) { vic.setCursor((ubyte)(x + 4), .VAGUE[x]); // la hauteur : la case x du tableau vic.putChar('*'); } // le cosinus : la même table, lue un quart de tour (8 cases) plus loin vic.setFontColor(vic.YELLOW); for (ubyte x = 0; x < 32; x++) { vic.setCursor((ubyte)(x + 4), .VAGUE[(x + 8) % 32]); vic.putChar('*'); } }Les deux écrans se ressemblent : la table
TrigoMakerdu cosinus contient exactement les valeurs du sinus décalées de 8 cases (22, 22, 21, 20…). Là où les deux vagues se croisent, la seconde, dessinée après, recouvre la première. - Faites défiler la vague : à chaque image, décalez d'une case l'indice lu dans la table
(
(x + decalage) % 32).Voir la solutionMasquer la solution
Pour faire défiler la vague, on la redessine à chaque image, en lisant la table à partir d'une case de plus. Trois ingrédients :
- une horloge : l'interruption de l'image du chapitre 8 (
irq1sur PC Engine,irqsur C64) augmente.images, et la boucle principale attend qu'elle ait changé ; - un
decalagequi augmente de 1 à chaque image, ramené à 0 après 31 par le modulo ; - un effacement : avant de dessiner l'étoile d'une colonne, on efface celle de l'image précédente, en écrivant une espace à la hauteur lue avec l'ancien décalage. Sans cela, les étoiles s'accumuleraient et l'écran se remplirait.
Chaque colonne n'a qu'une étoile, et elle n'est touchée que par son propre tour de boucle : effacer puis dessiner colonne par colonne ne risque donc pas d'effacer une étoile qui vient d'être posée.
Sur PC Engine
addlib event.irq1; unit { // deux ressources génériques : un fichier tel quel, et une table calculée à la compilation const int POLICE = addResource("font.bin"); const int VAGUE = addResource(ResourceFactory("TrigoMaker", "function=sin&count=32&litude=10&offset=12")); ubyte images; // compté par l'interruption ubyte decalage; // le début de la lecture dans la table// la table : rendue visible à son tour, puis lue case par case resource.mapToMPR2(.VAGUE); debut = 0x4000 + resource.getOffset(.VAGUE); vdc.setFontColor(1); while (1) { .images = 0; while (.images == 0) { // attendre l'image suivante } for (ubyte x = 0; x < 32; x++) { // effacer l'étoile de l'image précédente... vdc.setCursor(x, (ubyte)(readMem(debut + (x + .decalage) % 32) + 3)); vdc.putChar(' '); // ... et dessiner la nouvelle, une case plus loin dans la table vdc.setCursor(x, (ubyte)(readMem(debut + (x + .decalage + 1) % 32) + 3)); vdc.putChar('*'); } .decalage = (ubyte)((.decalage + 1) % 32); } } interrupt -> irq1 { .images++; } }La table est rendue visible une seule fois, avant la boucle : rien d'autre ne change la banque visible à
$4000ensuite.Sur Commodore 64
vic.setFontColor(vic.CYAN); while (1) { .images = 0; while (.images == 0) { // attendre l'image suivante } for (ubyte x = 0; x < 32; x++) { // effacer l'étoile de l'image précédente... vic.setCursor((ubyte)(x + 4), .VAGUE[(x + .decalage) % 32]); vic.putChar(' '); // ... et dessiner la nouvelle, une case plus loin dans la table vic.setCursor((ubyte)(x + 4), .VAGUE[(x + .decalage + 1) % 32]); vic.putChar('*'); } .decalage = (ubyte)((.decalage + 1) % 32); } } interrupt -> irq { .images++; } }Mesuré dans l'émulateur,
decalageavance bien d'une case par image sur les deux machines : la vague glisse vers la gauche et fait un tour complet en 32 images, soit environ 0,5 seconde sur PC Engine (60 images par seconde) et 0,64 seconde sur C64 (50 images par seconde). Le redessin des 32 colonnes tient largement dans le temps d'une image. Pour un défilement plus lent, il suffit d'attendre 2 ou 3 images par pas, commewhile (.images < 3). - une horloge : l'interruption de l'image du chapitre 8 (
- Ajoutez trois ressources de 3 000, 4 000 et 5 000 octets, et cherchez dans
asm/main.asmles banques que le compilateur leur a données.Voir la solutionMasquer la solution
On crée trois fichiers de 3 000, 4 000 et 5 000 octets (leur contenu n'a pas d'importance ici), et on les déclare dans la unit du programme de la vague :
// deux ressources génériques : un fichier tel quel, et une table calculée à la compilation const int POLICE = addResource("font.bin"); const int VAGUE = addResource(ResourceFactory("TrigoMaker", "function=sin&count=32&litude=10&offset=12")); // trois fichiers de 3 000, 4 000 et 5 000 octets const int DONNEES3000 = addResource("donnees3000.bin"); const int DONNEES4000 = addResource("donnees4000.bin"); const int DONNEES5000 = addResource("donnees5000.bin");Le programme compile sans erreur, et
asm/main.asmcontient les tables d'index. Elles sont dans l'ordre des identifiants :POLICE(0),VAGUE(1),DONNEES3000(2),DONNEES4000(3),DONNEES5000(4).; Resource Tables (5 resources) ; ======================================== _resource_banks: .db RESOURCE_FIRST_BANK+0, RESOURCE_FIRST_BANK+1, RESOURCE_FIRST_BANK+1, RESOURCE_FIRST_BANK+2, RESOURCE_FIRST_BANK+1 _resource_offsets: .dw $0000, $1F40, $1388, $0000, $0000 _resource_lengths: .dw $2000, $0020, $0BB8, $0FA0, $1388 _resource_count = 5Le fichier
asm/resources.asm, qui contient les données elles-mêmes, le dit plus lisiblement, banque par banque :Banque Ressources (position) Occupé RESOURCE_FIRST_BANK+0POLICE($0000)8 192 octets : pleine RESOURCE_FIRST_BANK+1DONNEES5000($0000),DONNEES3000($1388 = 5 000),VAGUE($1F40 = 8 000)8 032 octets RESOURCE_FIRST_BANK+2DONNEES4000($0000)4 000 octets C'est exactement la règle du chapitre, de la plus grande à la plus petite :
- la police, 8 192 octets, remplit la première banque ;
- 5 000 octets : la première banque est pleine, une deuxième est ouverte ;
- 4 000 octets : il ne reste que 3 192 octets dans la deuxième, une troisième est ouverte ;
- 3 000 octets : ils tiennent dans les 3 192 octets restants de la deuxième banque, derrière les 5 000 ;
- la vague, 32 octets, se glisse dans les 192 octets qui restent encore dans la deuxième banque.
Le compilateur n'a pas suivi l'ordre des déclarations : la deuxième banque commence par les 5 000 octets, déclarés en dernier. Avec ces tailles-là, remplir les banques dans l'ordre de déclaration aurait d'ailleurs aussi demandé trois banques (la vague, 3 000 et 4 000 dans la deuxième, 5 000 dans la troisième) : le gain dépend des tailles, comme dans l'exemple du chapitre où il économise une banque entière. Le programme, lui, n'a rien à savoir :
mapToMPR2(.DONNEES3000)lit les tables et trouve la bonne banque.
