Il y a quelques semaines, j'ai décidé de travailler sur un jeu pour Game Boy, dont la création m'a fait grand plaisir. Son nom de travail est Aqua and Ashes. Le jeu est open source et est publié sur
GitHub .
Comment cette idée m'est venue à l'esprit
J'ai récemment obtenu un stage en créant un backend en PHP et Python pour le site Web de mon université. C'est un bon travail intéressant, pour lequel je suis très reconnaissant. Mais ... en même temps, tout ce code de développement Web de haut niveau m'a infecté d'un désir insatiable. Et c'était le désir d'un travail de bas niveau avec des bits.
J'ai reçu un résumé hebdomadaire de itch.io sur les jams de jeu par la poste, qui annonçait le début de
Mini Jam 4 . C'était un jam de 48 heures (enfin, en fait un peu plus gros), dans lequel la limitation était de créer des graphismes dans le style de Game Boy. Ma première réaction logique a été de créer un jeu homebrew pour Game Boy. Le thème de la confiture était «saisons» et «flammes».
Après avoir réfléchi un peu à l'intrigue et à la mécanique qui peuvent être mises en œuvre en 48 heures et s'adapter aux limites du sujet, j'ai trouvé un
clone d'une nouvelle interprétation du niveau du jeu SNES 1993 Tiny Toon Adventures: Buster Busts Loose!, Dans lequel le joueur dans le rôle de Baster joue au football américain .
J'ai toujours aimé la façon dont les créateurs de ce niveau ont pris un sport incroyablement complexe, se sont débarrassés de tous les trucs, positions et éléments stratégiques, grâce à l'obtention d'un jeu extrêmement intéressant et facile. De toute évidence, une telle vision simplifiée du football américain ne remplacera pas Madden, tout comme NBA Jam (une idée similaire: seulement 4 joueurs sur un terrain beaucoup plus petit avec un gameplay plus simple que dans un jeu régulier) ne remplacera pas la série 2K. Mais cette idée a un certain charme, et les chiffres de vente de NBA Jam le confirment.
Comment tout cela se rapporte-t-il à mon idée? J'ai décidé de prendre ce niveau de football et de le refaire pour qu'il reste similaire à l'original et en même temps frais. Premièrement, j'ai réduit le jeu à seulement quatre joueurs - un défenseur et un attaquant. Cela a été principalement fait en raison des limitations du matériel, mais en même temps, cela me permettra d'expérimenter un peu avec une IA plus intelligente, non limitée au principe de "courir à gauche et parfois sauter" de jouer sur SNES.
Par souci de conformité avec le sujet, je remplacerai les portes par des colonnes en feu, ou par des feux de joie, ou par quelque chose comme ça (je n'ai pas encore décidé), et un ballon de football avec des torches et des seaux d'eau. Le gagnant sera l'équipe qui contrôle les deux feux de joie, et autour de ce concept simple, vous pouvez facilement trouver un complot. Les saisons sont également prises en compte: j'ai décidé que les saisons changeraient à chaque tour, afin que l'équipe de pompiers obtienne un avantage en été et l'équipe de pompiers en hiver. Cet avantage ressemble à des obstacles sur le terrain qui n'interfèrent qu'avec l'équipe adverse.
Bien sûr, lors de la création de deux équipes, il fallait deux animaux qui aiment et n'aiment pas le feu. Au début, j'ai pensé aux fourmis rouges et à certains insectes aquatiques, aux mantes religieuses et autres, mais après avoir étudié la question, je n'ai trouvé aucun insecte actif en hiver, alors je les ai remplacés par des renards polaires et des geckos. Les renards polaires aiment la neige, les geckos aiment se coucher au soleil, donc tout semble logique. Au final, ce n'est qu'un jeu pour Game Boy.
De plus, au cas où cela serait encore incompréhensible, à la fin du jam, le jeu n'était pas près d'être terminé. Quoi qu'il en soit, c'était amusant de toute façon.
Formation Game Boy
Vous devez d'abord déterminer les exigences. J'ai décidé d'écrire pour DMG (nom interne du modèle Game Boy, abréviation de Dot Matrix Game). Surtout pour répondre aux exigences d'un jam de jeu, mais aussi parce que je le voulais vraiment. Personnellement, je n'ai jamais eu de jeux pour DMG (bien qu'il existe plusieurs jeux pour Game Boy Color), mais je trouve que l'esthétique 2 bits est une limitation très agréable et intéressante pour les expériences. J'ajouterai peut-être des couleurs supplémentaires pour SGB et CGB, mais jusqu'à présent je n'y ai pas pensé.
J'ai également décidé d'utiliser une cartouche avec 32K ROM + sans RAM, juste au cas où je voudrais créer une copie physique du jeu. CatSkull, qui a publié plusieurs jeux Game Boy, tels que Sheep it Up !, a
en vente des cartouches flash de 32 kilo-octets très bon marché qui sont parfaites pour moi. C'est une autre limitation supplémentaire, mais je ne pense pas que dans un avenir proche je puisse surmonter le volume 32K avec un jeu aussi simple. Le plus dur sera avec les graphismes, et si tout est complètement mauvais, je vais essayer de le compresser.
Quant au travail de Game Boy, alors tout est assez compliqué. Cependant, pour être honnête, de toutes les rétro-consoles avec lesquelles je devais travailler, Game Boy était le plus agréable. J'ai commencé avec un
excellent tutoriel (au moins pour la première fois, car il n'a jamais été terminé) par l'auteur de AssemblyDigest. Je savais qu'il est préférable d'écrire en ASM, même si cela peut parfois être douloureux, car le matériel n'est pas conçu pour C, et je n'étais pas sûr que le langage cool Wiz mentionné dans le tutoriel conviendrait à long terme. De plus, je le fais principalement parce que
je peux travailler avec ASM.
Vérifiez le commit 8c0a4eaLa première chose à faire était de faire démarrer le Game Boy. Si le logo Nintendo n'est pas trouvé à un décalage de
$104
et que le reste de l'en-tête n'est pas configuré correctement, l'équipement Game Boy supposera que la cartouche n'est pas insérée correctement et refusera de la charger. Pour résoudre ce problème est très simple, car de nombreux tutoriels ont déjà été écrits à ce sujet.
Voici comment j'ai résolu le problème de cap. Rien ne mérite une attention particulière.
Il sera plus difficile d'effectuer des actions significatives après le chargement. Il est très simple de faire entrer le système dans un cycle occupé infini dans lequel il exécute une ligne de code encore et encore. L'exécution du code commence par le libellé
main
(où le saut à l'adresse
$100
indique), vous devez donc y insérer du code simple. Par exemple:
main: .loop: halt jr .loop
et il ne fait absolument rien d'autre qu'attendre le début de l'interruption, après quoi il revient à l'étiquette
.loop
. (Ci-après, je vais omettre une description détaillée d'ASM. Si vous vous trompez,
consultez la documentation de l'assembleur que j'utilise .) Vous pouvez être curieux de savoir pourquoi je ne reviens tout simplement pas à l'étiquette
main
. C'est parce que je veux que tout ce qui précède le label
.loop
soit l'initialisation du programme, et tout ce qui se passe après chaque image. Ainsi, je n'ai pas à contourner le cycle de chargement des données de la cartouche et à vider la mémoire dans chaque trame.
Faisons un pas de plus. Le package assembleur RGBDS que j'utilise contient un convertisseur d'image. Comme à ce stade, je n'ai pas encore tiré de ressources pour le jeu, j'ai décidé d'utiliser le bouton monochrome de ma page À propos comme bitmap de test. En utilisant RGBGFX, je l'ai converti au format Game Boy et j'ai utilisé la commande assembleur .incbin pour l'insérer après la fonction
main
.
Pour l'afficher à l'écran, j'ai besoin des éléments suivants:
- LCD éteint
- Définir la palette
- Définir la position de défilement
- Effacer la mémoire vidéo (VRAM)
- Charger des graphiques de tuiles dans VRAM
- Télécharger la carte d'arrière-plan des tuiles VRAM
- Rallumez l'écran LCD
LCD éteint
Pour les débutants, cela devient l'obstacle le plus sérieux. Sur le premier Game Boy, il est impossible d'écrire simplement des données sur VRAM à tout moment. Il faut attendre le moment où le système ne dessine rien. Imitant la lueur du phosphore dans les anciens téléviseurs CRT, l'intervalle entre chaque image lorsque la VRAM est ouverte est appelé Vertical-Blank, ou VBlank (dans CRT, c'est une impulsion pour masquer le faisceau du kinéscope lors d'un balayage inversé). (Il y a aussi HBlank entre chaque ligne de l'écran, mais il est très court.) Cependant, nous pouvons contourner ce problème en éteignant l'écran LCD, c'est-à-dire que nous
pouvons enregistrer en VRAM indépendamment de l'emplacement de la "trace de phosphore" de l'écran CRT.
Si vous êtes confus,
cet examen devrait vous expliquer beaucoup de choses . Dans ce document, la question est considérée du point de vue de la SNES, alors n'oubliez pas qu'il n'y a pas de faisceau d'électrons et que les nombres sont différents, mais dans tout le reste, c'est tout à fait applicable. Essentiellement, nous devons définir le drapeau FBlank.
Cependant, l'astuce du Game Boy est que vous ne pouvez éteindre l'écran LCD que pendant VBlank. Autrement dit, nous devons attendre VBlank. Pour ce faire, utilisez des interruptions. Les interruptions sont des signaux que le Game Boy envoie du matériel au CPU. Si le gestionnaire d'interruption est défini, le processeur arrête son travail et appelle le gestionnaire. Game Boy prend en charge cinq interruptions, et l'une d'elles démarre lorsque VBlank démarre.
Les interruptions peuvent être traitées de deux manières différentes. La première et la plus courante est la tâche d'
un gestionnaire d'interruption qui fonctionne comme je l'ai expliqué ci-dessus. Cependant, nous pouvons activer une interruption spécifique et désactiver tous les gestionnaires en définissant l'indicateur d'activation de cette interruption et en utilisant le
di
opcode. Il ne fait généralement rien, mais a pour effet secondaire de quitter l'opcode HALT, qui arrête le CPU avant qu'une interruption ne se produise. (Cela se produit également lorsque les gestionnaires sont activés, ce qui nous permet de quitter le cycle HALT en général.) Si vous êtes intéressé, nous créerons également un gestionnaire VBlank au fil du temps, mais cela dépendra en grande partie de certaines valeurs à certaines adresses. Étant donné que rien n'a été défini dans la RAM jusqu'à présent, une tentative d'appeler le gestionnaire VBlank peut entraîner une panne du système.
Pour définir les valeurs, nous devons envoyer des commandes aux registres matériels de Game Boy. Il existe des adresses mémoire spéciales qui sont directement liées à différentes parties de l'équipement, dans notre cas, le CPU, ce qui vous permet de changer son fonctionnement. Nous sommes particulièrement intéressés par les adresses
$FFFF
(champ de bit d'activation d'interruption),
$FF0F
(champ de bit d'interruption activé mais non géré) et
$FF40
(contrôle LCD). Une liste de ces registres se trouve
sur les pages associées à la section «Documentation» de la liste Awesome Game Boy Development.
Pour désactiver l'écran LCD, nous activons uniquement l'interruption VBlank, en affectant à
$FFFF
valeur
$01
, en exécutant HALT jusqu'à ce que la condition
$FF0F == $01
satisfaite, puis en affectant le bit 7 de l'adresse
$FF40
à 0.
Définition de la palette et de la position du défilement
C'est facile à faire. Maintenant que l'écran LCD est éteint, nous n'avons plus à nous soucier de VBlank. Pour régler la position de défilement, il suffit de mettre les registres X et Y à 0. Avec la palette, tout est un peu plus délicat. Dans Game Boy, vous pouvez attribuer des nuances du premier au quatrième graphisme de l'une des 4 nuances de gris (ou vert marais, si vous le souhaitez), ce qui est utile pour effectuer des transitions et autres. J'ai défini un dégradé simple comme une palette, définie comme une liste de bits
%11100100
.
Effacement de la VRAM et chargement des graphiques en mosaïque
Lors du lancement, toutes les données graphiques et la carte d'arrière-plan ne seront constituées que d'un logo Nintendo défilant, qui s'affiche au démarrage du système. Si j'active les sprites (ils sont désactivés par défaut), ils seront éparpillés sur l'écran. Vous devez vider la mémoire vidéo pour recommencer à zéro.
Pour ce faire, j'ai besoin d'une fonction comme
memset
de C. (J'ai également besoin d'une
memcpy
analogique pour copier les données graphiques.) La fonction
memset
définit le fragment de mémoire spécifié sur un certain octet. Il sera facile pour moi de l'implémenter moi-même, mais le
tutoriel AssemblyDigest a déjà ces fonctions, donc je les utilise.
À ce stade, je peux effacer la VRAM avec
memset
en y écrivant
$00
(bien que le premier commit ait utilisé la valeur
$FF
, qui convenait également), puis charger les graphiques de tuiles dans VRAM en utilisant
memcpy
. Plus précisément, je dois le copier à l'adresse
$9000
, car ce sont des tuiles utilisées uniquement pour les graphiques d'arrière-plan. (Les adresses
$8000-$87FF
sont utilisées uniquement pour les carreaux de sprite, et les adresses
$8800-$8FFF
sont courantes pour les deux types.)
Réglage de la carte des tuiles
Game Boy a une couche d'arrière-plan, divisée en tuiles 8x8. Le calque d'arrière-plan lui-même prend environ 32x32 tuiles, c'est-à-dire qu'il a une taille totale de 256x256. (A titre de comparaison: l'écran de la console a une résolution de 160x144.) J'avais besoin d'indiquer manuellement les tuiles qui composent mon image ligne par ligne. Heureusement, toutes les tuiles ont été organisées dans l'ordre, il me fallait donc remplir chaque ligne avec des valeurs de
N*11
à
N*11 + 10
, où
N
est le numéro de ligne, et les 22 éléments de tuiles restants remplissaient
$FF
.
Allumer l'écran LCD
Ici, nous n'avons pas besoin d'attendre VBlank, car l'écran ne s'allumera toujours pas avant VBlank, je viens donc d'écrire à nouveau sur le registre de contrôle LCD. J'ai également inclus des couches d'arrière-plan et de sprite, et indiqué également les adresses correctes de la carte des tuiles et des graphiques des tuiles. Après cela, j'ai obtenu les résultats suivants. J'ai également réactivé les gestionnaires d'interruption à l'aide de l'opcode
ei
.
À ce stade, pour le rendre encore plus intéressant, j'ai écrit un gestionnaire d'interruption très simple pour VBlank. En ajoutant l'opcode de transition à
$40
, je peux faire du gestionnaire n'importe quelle fonction dont j'ai besoin. Dans ce cas, j'ai écrit une fonction simple qui fait défiler l'écran de haut en bas.
Voici les résultats finis. [Addition: Je viens de réaliser que le GIF n'est pas bouclé correctement, il doit constamment transférer l'image.]
Jusqu'à présent, rien de particulièrement surprenant, mais c'est quand même cool que théoriquement je puisse récupérer mon ancienne Game Boy Color et voir comment mon propre code est exécuté dessus.
Amusez-vous avec des draps à carreaux
Pour dessiner quelque chose à l'écran, j'ai naturellement besoin d'une sorte de sprite. Après avoir étudié le PPU (Picture Processing Unit) de la console Game Boy, j'ai décidé de me concentrer sur les sprites 8x8 ou 8x16. Probablement, j'aurai besoin de la dernière option, mais juste pour sentir la taille, j'ai rapidement griffonné une capture d'écran du jeu à l'échelle 1: 8 sur du papier quadrillé.

Je voulais laisser le haut de l'écran sous le HUD. Il me semblait que cela aurait l'air plus naturel que d'en bas, car quand il est en place, alors si les personnages ont besoin de bloquer temporairement le HUD, comme dans Super Mario Bros, ils peuvent le faire. Ce jeu n'aura pas de plateforme complexe, et en fait le level design aussi, donc je n'ai pas besoin de montrer une vue très générale du terrain. La position des personnages à l'écran et, éventuellement, les obstacles qui apparaissent de temps en temps seront bien suffisants. Par conséquent, je peux me permettre des sprites assez grands.
Donc, si un carré était une tuile 8x8, alors un sprite
ne suffira pas, quelle que soit la taille que je choisis. Cela est particulièrement vrai étant donné qu'il n'y aura presque pas de mouvement vertical dans le jeu, à l'exception des sauts. J'ai donc décidé de créer des sprites à partir de quatre sprites 8x16. L'exception était la queue du renard, qui occupe deux sprites 8x16. Après de simples calculs, il est devenu clair que deux renards et deux geckos occuperont 20 des 40 sprites, c'est-à-dire qu'il sera possible d'ajouter beaucoup plus de sprites supplémentaires. (Les sprites 8x8 manqueraient rapidement de ma limite, ce que je ne veux pas faire dans les premiers stades de développement.)
Pour l'instant, je n'ai besoin que de dessiner des sprites. Vous trouverez ci-dessous des croquis approximatifs sur papier quadrillé. J'ai un sprite en attente, un sprite "pensant" pour choisir de passer ou de courir, comme dans un jeu SNES ... et c'est tout. J'ai également prévu de créer des sprites de personnages en cours d'exécution, de personnages sauteurs et de personnages que les adversaires attraperont. Mais pour commencer, je n'ai dessiné que des sprites d'attente et de réflexion, afin de ne pas compliquer. Je n'ai toujours pas fait le reste, je dois le faire.
Oui, je sais, je ne dessine pas très bien. La perspective est une chose délicate. (Oui, et ce visage de renard polaire est terrible.) Mais cela me convient parfaitement. La conception des personnages n'a pas de caractéristiques spéciales, mais convient à la confiture de jeux. Bien sûr, j'ai utilisé de vrais geckos et renards polaires comme références. Est-ce imperceptible?
Vous ne pouvez pas le dire. (Pour mémoire: après avoir à nouveau regardé ces photos, j'ai réalisé qu'il y a une énorme différence entre les geckos et les lézards. Je ne sais pas quoi faire avec ça, sauf pour me considérer stupide ...) Je pense que vous pouvez deviner que la source d'inspiration pour Blaze the Cat de la série de jeux Sonic a servi de tête au renard.
Au départ, je voulais que les défenseurs et les attaquants de chaque équipe soient de sexes différents et il était plus facile de les distinguer. (J'allais également laisser les joueurs choisir le sexe de leur personnage.) Cependant, cela nécessiterait beaucoup plus de dessin. J'ai donc choisi des geckos mâles et des renards femelles.
Et enfin, j'ai dessiné un écran de démarrage car il y avait de la place sur un morceau de papier quadrillé.
Oui, les poses d'action sont encore loin d'être idéales. Le renard polaire devrait être plus bouleversé et courir, et le gecko devrait avoir l'air menaçant. Defender Fox en arrière-plan - une référence amusante à l'art sur la boîte Doom.
Numérisation des sprites
Puis j'ai commencé à transformer des dessins sur papier en sprites. Pour cela, j'ai utilisé le programme GraphicsGale, qui a récemment été rendu gratuit. (Je sais que vous pouvez utiliser asesprite, mais je préfère GraphicsGale.) Le travail sur les sprites s'est avéré être beaucoup plus compliqué que ce à quoi je m'attendais. Chacun de ces carrés des images-objets ci-dessus prend jusqu'à 4 pixels dans une grille 2x2. Et ces carrés avaient souvent BEAUCOUP plus de détails que 4 pixels. J'ai donc dû me débarrasser de nombreux détails des croquis. Parfois, il était même difficile d'adhérer à une forme simple, car il fallait laisser un endroit acceptable pour les yeux ou le nez. Mais il me semble que tout a l'air bien, même si le sprite est devenu complètement différent.

Les yeux du renard ont perdu leur forme d'amande et se sont transformés en une ligne de deux pixels de haut. Les yeux du gecko ont conservé leur rondeur. La tête du gecko a dû être agrandie, en se débarrassant des larges épaules, et tous les virages que le renard aurait pu être ont été considérablement lissés. Mais honnêtement, tous ces changements faciles ne sont pas si mauvais. Parfois, je pouvais à peine choisir laquelle des variations est la meilleure.

GraphicsGale possède également des fonctions pratiques pour les calques et les animations. Cela signifie que je peux animer la queue du renard séparément de son corps. Cela aide beaucoup à économiser un précieux espace VRAM car je n'ai pas besoin de dupliquer la queue dans chaque image. De plus, cela signifiait que vous pouviez remuer la queue à une vitesse variable, ralentir lorsque le personnage était debout et accélérer en courant. Cependant, cela rend la programmation un peu plus compliquée. Mais je continuerai cette tâche. Je me suis installé sur 4 images d'animation, car cela suffit.
Vous remarquerez peut-être que le renard polaire utilise les trois nuances de gris les plus claires, tandis que le gecko utilise les trois nuances de gris les plus foncées. Sur GameBoy, cela est acceptable, car bien qu'il ne puisse y avoir que trois couleurs dans un sprite, la console vous permet de spécifier deux palettes. J'ai fait en sorte que la palette 0 soit utilisée pour les renards et la palette 1 pour les geckos. C'est tout l'ensemble de palettes disponible, mais je ne pense pas en avoir besoin d'autres.
J'avais également besoin de prendre soin de l'arrière-plan. Je n'ai pas pris la peine de faire ses croquis, car j'avais prévu que ce serait une couleur unie ou un simple motif géométrique. Je n'ai pas encore numérisé l'économiseur d'écran, car je n'ai pas eu assez de temps.
Chargement des sprites dans le jeu
Vérifiez avec le commit
be99d97 .
Après avoir enregistré chaque image individuelle des graphiques des personnages, il était possible de commencer à les convertir au format GameBoy. Il s'est avéré que dans RGBDS il y a un utilitaire très pratique pour cela appelé RGBGFX. Vous pouvez l'appeler avec la commande
rgbgfx -h -o output.bin input.png
et cela créera un ensemble de tuiles compatibles avec GameBoy. (Le commutateur -h spécifie un mode de tuile compatible avec 8x16 afin que la conversion soit effectuée de haut en bas, et non de gauche à droite.) Cependant, il ne fournit pas de liaisons et ne peut pas suivre les tuiles en double lorsque chaque image est une image distincte. Mais nous laisserons ce problème pour plus tard.
Après avoir généré les fichiers de sortie .bin, ajoutez-les simplement dans l'assembleur en utilisant
incbin "output.bin"
. Pour tout garder ensemble, j'ai créé un fichier commun «gfxinclude.z80», qui contient tous les graphiques à ajouter.
Cependant, il était très ennuyeux de régénérer manuellement les graphiques chaque fois que quelque chose changeait. J'ai donc édité le fichier build.bat, en ajoutant la ligne
for %%f in (gfx/*.png) do rgbds\rgbgfx -h -o gfx/bin/%%f.bin gfx/%%f
, qui convertit chaque fichier. png dans le dossier gfx / dans le fichier bin et l'enregistre dans gfx / bin. Cela a grandement simplifié ma vie.
Pour créer des graphiques d'arrière-plan, j'ai utilisé une méthode
beaucoup plus paresseuse. RGBASM a une directive
dw `
. Il est suivi d'une ligne de 8 valeurs de 0 à 4 égales à une ligne de données de pixels. Comme les sprites d'arrière-plan étaient très simples, il s'est avéré plus facile de copier et coller un motif géométrique simple pour créer un motif solide, rayé ou en damier. Ici, par exemple, ressemble à une tuile terrestre.
bg_dirt: dw `00110011 dw `00000000 dw `01100110 dw `00000000 dw `11001100 dw `00000000 dw `10011001 dw `00000000
Il crée une série de rayures décalées avec l'illusion de la perspective. Il s'agit d'une approche simple mais intelligente. Avec l'herbe, c'était un peu plus compliqué. Au départ, c'était un groupe de lignes horizontales de 2 pixels de haut, mais j'ai ajouté manuellement quelques pixels qui ajoutent un peu de bruit qui rend l'herbe plus belle:
bg_grass: dw `12121112 dw `12121212 dw `22112211 dw `11121212 dw `22112211 dw `21212121 dw `12121212 dw `12211222
Rendu graphique
Dans GameBoy, les sprites sont stockés dans une zone appelée OAM, ou Object Attribute Memory. Il ne contient que des attributs (direction, palette et priorité), ainsi que le numéro de tuile.
Il me suffisait de remplir cette zone de mémoire pour afficher les sprites à l'écran.Bien qu'il y ait de petites fonctionnalités. Tout d'abord, vous devez charger les graphiques de la ROM dans la VRAM. GameBoy ne peut rendre que les tuiles stockées dans une zone de mémoire spéciale appelée VRAM. Heureusement, pour copier de la ROM vers la VRAM, il suffit de l'exécuter memcpy
au stade de l'initialisation du programme. Il s'est avéré qu'avec seulement 6 sprites de caractères et 4 tuiles de queue, j'ai déjà pris un quart de la zone VRAM allouée aux sprites. (La VRAM est généralement divisée en zones d'arrière-plan et d'images-objets, et 128 octets leur sont communs.)De plus, l'accès à OAM n'est possible que pendant VBlank. J'ai commencé par dire que VBlank attendait les calculs de sprite, mais j'ai rencontré des problèmes car les calculs de sprite ont duré tout le temps alloué par VBlank et il était impossible de les terminer. La solution ici consiste à écrire dans une zone de mémoire distincte en dehors de VBlank et à les copier simplement dans OAM pendant VBlank.Il s'est avéré que GameBoy a une procédure de copie matérielle spéciale, une sorte de DMA (accès direct à la mémoire, accès direct à la mémoire) qui fait exactement cela. En écrivant dans un registre spécifique et en passant au cycle occupé dans HiRAM (car la ROM n'est pas disponible pendant DMA), vous pouvez copier les données de la RAM vers OAM beaucoup plus rapidement qu'en utilisant la fonctionmemcpy
. Si vous êtes intéressé, des détails juteux peuvent être trouvés ici .À ce stade, je n'ai pu créer qu'une procédure qui détermine ce qui sera finalement écrit dans le DMA. Pour ce faire, j'avais besoin de stocker l'état des objets ailleurs. Au minimum, les éléments suivants étaient requis:- Type (gecko, renard polaire ou objet de transport d'une des équipes)
- Direction
- Position X
- Position Y
- Cadre d'animation
- Minuterie d'animation
Dans la première décision, très délabrée, j'ai vérifié le type de l'objet, et en fonction de lui, je suis passé à une procédure qui dessine de manière sprite ce type d'objet. La procédure du renard polaire, par exemple, a pris une position en X, selon la direction, a ajouté ou soustrait 16, a ajouté deux sprites de queue, puis a monté et descendu le sprite principal.Voici une capture d'écran de l'apparence du sprite dans VRAM lors du rendu à l'écran. La partie gauche est constituée de sprites individuels, de nombres hexadécimaux à côté d'eux, de haut en bas - position verticale et horizontale, pavé et drapeaux d'attribut. Sur la droite, vous pouvez voir comment tout s'est passé après l'assemblage.Avec l'animation de queue, c'était un peu plus compliqué. Dans la première solution, j'ai simplement effectué un incrément du temporisateur d'animation dans chaque image et en ai produit un logique and
avec une valeur %11
pour obtenir le numéro de l'image. Ensuite, vous pouvez simplement ajouter 4 * le numéro de la trame (chaque trame d'animation se compose de 4 tuiles) à la première tuile arrière de la VRAM pour obtenir 4 images différentes stockées dans la VRAM. Cela a fonctionné (en particulier celui avec la recherche de tuiles de queue), mais la queue a remué incroyablement vite, et j'avais besoin de trouver un moyen de la ralentir.Dans la seconde, meilleure solution, j'ai effectué un incrément du temporisateur global dans chaque trame , et lorsque la valeur de l'opération and
avec elleet le degré de deux que j'ai choisi était 0, l'incrémentation de la minuterie d'objet a été effectuée. Ainsi, chaque objet individuel pouvait décompter son minuteur d'animation à la vitesse dont il avait besoin. Cela a parfaitement fonctionné et m'a permis de ralentir la queue à un niveau raisonnable.Des difficultés
Mais si tout était si simple. N'oubliez pas que j'ai géré tout cela dans le code, en utilisant ma propre sous-procédure pour chaque objet, et si vous deviez continuer, vous devez le faire dans chaque cadre. J'ai dû spécifier comment procéder pour le sprite suivant, ainsi que la tuile qui le compose, en manipulant manuellement les registres.C'était un système complètement instable. Pour dessiner une trame, il fallait jongler avec un nombre suffisamment important de registres et de temps CPU. Il était presque impossible d'ajouter un support pour les autres membres du personnel, et même si je réussissais, le support du système serait très douloureux. Croyez-moi, c'était un vrai chaos.J'avais besoin d'un système dans lequel le code de rendu des sprites serait généralisé et simple, de sorte qu'il ne s'agirait pas d'une imbrication de conditions, d'une manipulation de registres et d'opérateurs mathématiques.Comment ai-je résolu ce problème? J'en parlerai dans la prochaine partie de l'article.