Obligement - L'Amiga au maximum

Vendredi 06 juin 2025 - 12:21  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : Assembleur - Scoopex "THREE" - la programmation d'un menu de triche sur Amiga
(Article écrit par Yragael et extrait de www.stashofcode.fr - juillet 2019, mis à jour en décembre 2019)


Sur Amiga, les menus de triche ("trainer") étaient essentiellement un menu enjolivé par un effet et une musique, qui permettait d'activer des options pour tricher dans un jeu : "Vies illimitées: On/Off", et ainsi de suite. Dans bien des cas, les triches ont pu être le seul moyen de parvenir à profiter totalement d'un jeu sans y passer trop de temps, vu la difficulté. Ne termine pas Shadow Of The Beast qui veut...

Dans la continuité d'un programme d'hommages aux différentes figures de la scène, voici Scoopex "THREE", un menu de triche produit pour le fameux StingRay du glorieux groupe Scoopex. Après Scoopex "TWO" rendant hommage aux graphistes, un hommage aux déplombeurs, donc.

programmation d'une cracktro sur Amiga 500
Scoopex THREE : Un menu de triche pour A500 en 2019

Comme on pourra le constater, l'originalité a pris ici le pas sur la technique. Du moins en apparence, car en matière de programmation du matériel en assembleur sur Amiga, tout devient finalement assez technique rapidement !

Code, données et explications dans tout ce qui suit...
  • Mise à jour du 08/12/2019 : le menu a été magnifiquement porté sur Flashtro. C'est ici ! Ce menu n'a toujours pas été utilisé par StingRay, mais comme dans le cas de Scoopex "ONE", j'ai jugé qu'après des mois d'attente, il était nécessaire... de ne plus attendre. A priori, il devrait être utilisé sous peu. On verra bien...
A écouter en lisant l'article : Heavenchord - Time in Space Heavenchord.

Cliquez ici pour télécharger l'archive contenant le code et les données du menu de triche. Précisons d'emblée deux choses pour éviter les déceptions :
  • Le code est celui du menu des triches, et pas des triches à proprement parler, au sens du code qui altère le code du jeu alors qu'il est exécuté.
  • Le lot de tuiles n'est pas compris, mais je donne des instructions à la fin de cet article pour le recréer.
La conception

Depuis le jour où j'ai fait la découverte émerveillée d'Ultima III: Exodus sur l'affichage CGA d'un PC1512, je voue une admiration sans bornes à Richard Garriott, dit "Lord British".

Inutile de dire que cette admiration s'est renforcée avec la découverte, cette fois sur Amiga, de Ultima IV: Quest Of The Avatar puis de Ultima V: Warriors Of Destiny. Je ne peux contempler les cartes en tissus de ces jeux - oui, je ne suis pas un pirate : j'ai les originaux, môssieur ! - sans écraser une larme en souvenir des heures joyeuses passées à explorer les recoins de Britannia. C'est dire.

Il m'a toujours semblé que le potentiel de ces jeux a été sous-estimé, tout particulièrement le quatrième opus. Enfin diable ! Ne présentait-il pas un caractère unique du fait qu'il contraignait le joueur à se plier à une éthique pour gagner ? Par la suite, je n'ai pas rencontré de jeux conçus de la sorte, et j'ai toujours pensé qu'un jour, le jeu vidéo en général trouverait son utilisation en tant qu'outil de propagande. D'ailleurs, j'avais développé ce propos dans un papier programmatique intitulé "Le jeu vidéo au service de la communication", dans lequel il faudrait que je me replonge. Ceci étant, c'était en 2001, avant que l'histoire ne montre que les réseaux sociaux constituent un vecteur bien plus facile à utiliser...

Mais revenons à nos moutons. De toute cette série, c'est Ultima V qui a toujours eu ma préférence. Cela tient non seulement à la richesse du scénario élaboré par Richard Garriott, mais aussi à la beauté des graphismes produits par Denis Loubet et de Doug Wike :

programmation d'une cracktro sur Amiga 500
Ultima V sur Amiga

Pas la musique ? Non, car sur ce point, je dois dire que sur Amiga, nous n'avons pas été gâtés. Je me souviens qu'après avoir attendu des mois, si ce n'est des années, que le jeu sorte enfin sur ma machine préférée, pour en faire l'acquisition à la FNAC - je me vois encore prendre la boîte dans le rayon aux Halles -, j'avais constaté avec dépit que les musiques n'étaient pas celles que j'avais pu écouter sur Atari ST. Au lieu de plusieurs morceaux tous plus géniaux les uns que les autres, il n'y avait qu'un morceau, certes pas mal, mais faisant perdre au jeu une grande part de sa variété. Enfin bref, du moins le jeu n'était-il pas bogué comme sur Atari ST, et j'avais tout de même pu me foutre de la gueule d'un bon copain possédant cette machine quand, après ce temps d'attente qu'il n'avait manqué de me faire sentir comme le signe de la prévalence de sa machine sur la mienne, j'avais pu me vanter d'avoir terminé le jeu, et pas lui. Enfants...

Tout cela pour dire qu'il fallait rendre hommage à Ultima V avant de passer outre.

Or, j'échange un jour avec le fameux StingRay du glorieux groupe Scoopex, et voici qu'il me propose de réaliser un menu de triche. Pour ceux qui l'ignorent, StingRay est l'un de ces brillants codeurs qui contribuent à la préservation du patrimoine de l'Amiga en reprenant méthodiquement les jeux pour en produire des versions qui peuvent être exécutées depuis un disque dur avec WHDLoad, avec menu de triche quand ils en étaient jusqu'alors dépourvus. Par exemple, Ultima V, pas plus tard qu'en avril 2018. Il faut disposer de l'original.

Je cogite un instant, et il me vient l'idée d'un menu de triche pour le moins original, puisqu'il consisterait à doubler l'inévitable menu non pas d'un effet, mais de toute une animation à base de tuiles d'Ultima V. Il s'agirait de suivre un joueur qui se baladerait gentiment, avant que des streums lui tombent dessus. Là-dessus, il se ferait défoncer grave la gueule, et implorerait pitié pour être trainé par StingRay. Bam ! Il hériterait de la totale, et vêtu des pieds à la tête d'un matos de ouf, il mettrait une rouste aux affreux. C'est le pitch.

L'idée plaît à StingRay pour son originalité, et je me lance dans la réalisation, avec d'autant plus d'intérêt que j'étais en train de développer un jeu à la Ultima à base de JavaScript et de WebGL - que je finirai bien par terminer un jour ? Du moins ce menu de triche m'aura-t-il déjà donné l'occasion de programmer tout un éditeur de cartes à base de tuiles, comme on le verra plus loin.

Le menu

Sur la base de l'expression des besoins de StingRay, le menu de triche peut proposer au joueur deux types de paramètres en pagaïe :
  • Une valeur entière, par défaut comprise dans l'intervalle [0, 255], mais qu'il est possible de borner dans un intervalle quelconque plus petit.
  • Une valeur booléenne.
Par ailleurs, tout paramètre peut avoir une quelconque valeur par défaut.

La petite difficulté était de permettre à StingRay de facilement rédiger le contenu des pages du menu. Le mieux était d'être aussi WYSIWYG que possible, et donc de lui permettre d'entrelacer des lignes de texte, des lignes de paramètres, mais aussi des lignes permettant de naviguer dans le menu : aller à l'éventuelle page précédente, à l'éventuelle page suivante, quitter le menu de triche.

Pour cette raison, les lignes du menu sont déclarées dans les données sous la forme de structures :
  • <text>, 0, PARAM_NONE : simple ligne de texte
  • <text>, 0, PARAM_BOOL, <value> : ligne d'un paramètre booléen (0 : vrai, -1: faux).
  • <text>, 0, PARAM_INT, <value>, <min>, <max> : ligne d'un paramètre entier borné.
  • <text>, 0, PARAM_PREV : ligne d'un lien vers la page précédente.
  • <text>, 0, PARAM_NEXT : ligne d'un lien vers la page suivante.
  • <text>, 0, PARAM_QUIT : ligne d'un lien pour quitter le menu de triche.
  • 0, PARAM_NONE : ligne vide.
Par exemple, pour la première page - chaque page se termine par un -1 et l'ensemble des pages se termine par un -1 supplémentaire :

	DC.B 0,PARAM_NONE
	DC.B "  .oO Idea & Opcodes: Yragael Oo.",0,PARAM_NONE
	DC.B "oO. Paintings: Loubet &| Wike .Oo",0,PARAM_NONE
	DC.B "  .Oo    Bells & Horns: JMD   oO.",0,PARAM_NONE
	DC.B 0,PARAM_NONE
	DC.B "The menu may contain lines of",0,PARAM_NONE
	DC.B "text (32 chars), possibly empty,",0,PARAM_NONE
	DC.B "even between params.",0,PARAM_NONE
	DC.B 0,PARAM_NONE
	DC.B "There may be up to 255 params.",0,PARAM_NONE
	DC.B "A param is either BOOL or INT:",0,PARAM_NONE
	DC.B 0,PARAM_NONE
	DC.B "BOOL parameters:",0,PARAM_BOOL,-1
	DC.B "INT parameters:",0,PARAM_INT,0,0,10
	DC.B 0,PARAM_NONE
	DC.B "The menu may run on several pages.",0,PARAM_NONE
	DC.B "Just add PREV/NEXT option as",0,PARAM_NONE
	DC.B "required:",0,PARAM_NONE
	DC.B 0,PARAM_NONE
	DC.B "Next page",0,PARAM_NEXT
	DC.B 0,PARAM_NONE
	DC.B "And don't forget a QUIT option:",0,PARAM_NONE
	DC.B 0,PARAM_NONE
	DC.B "Quit",0,PARAM_QUIT
	DC.B -1

Au démarrage du menu de triche, les données qui décrivent ainsi le menu se trouvent à l'adresse menuData. Elles sont interprétées par la routine _menuSetup, qui génère une représentation du menu facile à manipuler à l'adresse menuPages, sous la forme d'une suite de structures de données dans le détail de laquelle il est inutile de rentrer. En particulier, ces structures sont utilisées par la routine _menuPrintPage pour afficher le contenu d'une page.

Une page du menu mobilise les plans de bits 5 et 6. Sur Amiga, le plan de bits 6 est particulier, car le bit 5 qu'il introduit dans le codage de l'indice de la couleur d'un pixel ne permet pas d'afficher ce pixel dans une couleur qui peut être contrôlée. Le matériel considère que les couleurs 32 à 63 sont nécessairement des versions "half-bright" - moitié moins lumineuses - des couleurs 00 à 31. Ce fonctionnement est idéal pour le menu, car cela permet de l'afficher sur un fond qui laisse entrevoir les plans de bits de la première scène cinématique ("cut scene") en semi-transparence sans avoir à intervenir dessus.

programmation d'une cracktro sur Amiga 500
Le menu gère les booléens et les entiers bornés (ou non)

S'il est donc parfait que la luminosité des couleurs des pixels de la première scène soit atténuée, il ne faut pas oublier que le plan de bits 6 affecte la luminosité des autres pixels, c'est-à-dire ceux du texte du menu. C'est pourquoi la routine _menuPrintPage, après avoir rempli de bits 1 le fond du menu dans le plan de bits 6, écrit chaque caractère du menu dans le plan de bits 5, et l'inverse de ce caractère dans le plan de bits 6.

La souris

De toutes les difficultés auxquelles le codeur du matériel de l'Amiga peut être confronté, la gestion de la souris est l'une des plus inattendues. C'est qu'autant il est simple de tester si le bouton gauche de la souris est pressé ou relâché - tester le bit 6 de $BFE001 -, autant il est ardu de tester la même chose pour le bouton droit.

La lecture de cette section de l'Amiga Hardware Reference Manual permet de supposer que le bouton droit ne se gère pas comme le bouton gauche, car il n'est pas relié à une circuiterie du même genre : il est connecté à une broche analogique et non numérique.

De fait, à la lecture de cette annexe, il apparaît que lire l'état du bouton droit sur cette broche ne consiste pas simplement tester un bit dans un registre. S'il est bien possible de tester le bit 10 (DATLY) de POTGOR, ce n'est qu'après avoir demandé au matériel de traiter la broche comme numérique. Pour cela, il faut écrire dans un autre registre, POTGO. D'où la séquence suivante :

	move.w #$8400,POTGO(a5)	;OUTRY=1, DATLY=1
	btst #10,POTGOR(a5)		;Bit 10 % 8 = 2 de l'octet de poids fort, donc DATLY

L'annexe semble préciser qu'il faut en théorie attendre jusqu'à 300 ms entre les deux opérations. Toutefois, il n'apparaît pas que ce soit véritablement nécessaire. En tout cas, le code du menu de triche s'en dispense.

Et pour suivre le mouvement de la souris ? Comme bien expliqué ici, déplacer la souris entraîne l'incrémentation d'un compteur quand elle est déplacée vers le haut, et la décrémentation du même compteur quand elle est déplacée vers le bas. Il s'agit d'un compteur 8 bits, dont la valeur peut être lue dans JOY0DAT quand la souris est branchée sur le port 1 - ce qui généralement le cas.

Les précisions apportées ici permettent de comprendre qu'arrivé à saturation, le compteur reboucle. Le menu de triche en tient compte pour éviter de produire cet effet indésirable où le mouvement de la souris vers le bas est interprété comme un mouvement vers le haut, et inversement. Par exemple, suite à un mouvement vers le bas, le passage de -126 à -127 permettrait bien de détecter une variation de -1 du compteur, assimilée à un mouvement vers le bas en tant qu'elle est négative. Toutefois, au mouvement suivant toujours vers le bas, le passage de -127 à 0 du compteur conduirait à détecter une variation de +127, assimilée à un mouvement vers le haut, en tant que cette variation est positive.

Dans le code du menu de triche, sachant que le compteur est lu à chaque trame - 1/50e de seconde -, et qu'un mouvement ne peut raisonnablement faire reboucler le compteur s'il démarre à 0, le compteur est réinitialisé à 0 à chaque fois qu'il est lu. Par ailleurs, un mouvement n'est détecté que si le compteur dépasse le seuil MENU_MOUSESENSITIVITY - qui en pratique est fixé au minimum. La réinitialisation s'effectue en écrivant dans un autre registre, JOY0TEST :

	move.w JOY0DAT(a5),d1
	and.w #$FC00,d1			;Les deux bits de poids faibles doivent être ignorés
	beq _menuMoveSelectorExit
	bgt _menuMoveSelectorDown
	cmpi.w #-MENU_MOUSESENSITIVITY,d1
	bgt _menuMoveSelectorDone
	;Rajouter ici le code pour gérer un mouvement vers le haut
	bra _menuMoveSelectorDone
_menuMoveSelectorDown:
	cmpi.w #MENU_MOUSESENSITIVITY,d1
	blt _menuMoveSelectorDone
	;Rajouter ici le code pour gérer un mouvement vers le bas	
_menuMoveSelectorDone:
	move.w #0,JOYTEST(a5)
_menuMoveSelectorExit:

Il peut paraître curieux d'ignorer les deux bits de poids faible du compteur, car cela apparaît comme une perte de précision. C'est que comme expliqué ici, ces deux bits ont un comportement très particulier pour permettre de lire dans JOY0DAT aussi bien l'amplitude d'un mouvement de la souris, que la direction d'une poussée sur une manette. Par conséquent, le mieux est de les ignorer.

Cliquez ici pour récupérer le code minimal permettant de prendre le contrôle du matériel pour afficher un écran sur un plan de bits, et exécuter une boucle qui teste la pression des boutons de la souris, les mouvements de cette dernière, et le relâchement d'une touche (Échap) pour quitter.

Les scènes cinématiques ("cut scenes")

Comme je l'ai mentionné, cela fait longtemps que j'essaie de produire un jeu à la Ultima en JavaScript et WebGL - mais "Je l'aurais un jour ! Je l'aurais !", c'est sûr. Ce projet personnel m'a donné l'occasion de développer pas mal d'outils - c'est d'ailleurs bien le problème, cette perpétuelle digression... -, et d'acquérir une très bonne connaissance de JavaScript et de certaines des API du navigateur, dont WebGL et Canvas. Partant, je savais que si je me lançais dans le développement d'un éditeur de Scènes cinématiques pour le menu de triche, je n'aurais aucune difficulté pour le terminer rapidement.

Or, composer le décor et décrire l'animation d'une scène cinématique à l'aide de codes dans un éditeur de texte allait assurément vite se révéler d'autant plus fastidieux, que je n'étais pas du tout certain de la scène à créer, si bien qu'il faudrait très probablement me livrer à de nombreuses retouches. Bref, disposer d'un éditeur m'est vite apparu essentiel pour travailler confortablement.

J'ai donc produit cela :

programmation d'une cracktro sur Amiga 500
L'éditeur de scènes cinématiques de Scoopex THREE

Ce gentil petit éditeur permet de créer facilement une scène cinématique. A la base, il charge un fichier JSON - oubliez XML pour toujours -, qui contient simplement un tableau repérant les tuiles dans le fichier PNG qui les regroupe. Un exemple d'entrée :

{"id":0, "name":"Grass", "type":0, "u":4, "v":0, "nbFrames":1}

Ce tableau permet de mettre à disposition les tuiles à déposer à la souris dans chaque trame de la scène.

Pour simplifier la vie, l'éditeur est notamment doté des fonctionnalités suivantes :
  • Trois couches (le décor, les personnages, les objets) pour permettre de travailler facilement sur des entités d'un certain type sans ruiner ce qui a déjà été produit sur les autres.
  • Copier-coller, effacement et remplissage de l'intégralité d'une trame limité aux couches actives.
  • Fonction "annuler" infinie pour toutes les opérations sur les tuiles dans une trame, et aussi pour toutes celles de gestion de l'animation.
  • Chargement et exportation de la scène cinématique au format JSON, et exportation au format DC.B utilisé par le menu de triche.
  • Lecture de l'animation à partir d'une trame quelconque et retour à cette trame à la fin ou en cas d'interruption.
Ce n'est pas très gros : au plus 2500 lignes, JavaScript, HTML et CSS - mais de HTML et de CSS, il n'y en a presque pas - confondus. Je mettrai à disposition le code aussitôt que j'aurais produit une première version de ce jeu à la Ultima évoqué.

Pour l'animation, il ne fallait pas chercher midi à quatorze heures : c'est du différentiel. Autrement dit, exception faite de la trame 0, une trame se réduit à la liste des tuiles qu'elle remplace dans la trame précédente. Cette technique permet de limiter la taille du fichier décrivant l'animation, mais aussi les opérations graphiques requises pour transformer une trame en la suivante.

Par exemple, dans son format d'exportation DC.B, la trame 1 de la seconde scène se résume aux octets suivants :

;Frame 1

DC.B 2,9,14,29,9,15,75

Ce qui se lit ainsi. Il faut modifier deux tuiles :
  • La tuile (9, 14) devient la tuile d'indice 29 dans la palette des tuiles.
  • La tuile (9, 15) devient la tuile d'indice 75 dans la palette des tuiles.
...ce qui revient à déplacer le personnage d'une case vers le haut, révélant le sentier sur lequel il est censé marcher. Bref, pour passer de la trame 0 à la trame 1, il suffit de redessiner deux tuiles, plutôt que de toutes les redessiner.

La technique est économique, mais elle présente un inconvénient majeur lors de l'édition. En effet, une tuile n'est qu'une tuile, c'est-à-dire une instance d'une tuile d'une palette de tuiles, et non une instance d'une entité comme un personnage, un objet, ou un élément de décor, qui aurait une existence au-delà de la trame. Pour le dire autrement, une tuile est à une trame de la scène cinématique ce qu'un pixel est à une trame d'une animation : elle n'est rien de plus qu'une image, elle ne comporte aucune information autre que celle qu'il permet de l'afficher.

Cela pose une difficulté particulière pour animer les entités que les tuiles représentent, car il n'y a aucun lien entre une instance de tuile dans une trame et l'entité qu'il représente - aucune information dans l'instance y renvoie. Par conséquent, il est impossible de dire automatiquement - c'est-à-dire dans le code - si une tuile en (x0, y0) dans la trame N représente la même entité qu'une tuile en (x1, y1) dans la trame N+1, voire en (x0, y0) dans cette trame. Quand bien même c'est un dragon qui est représenté aux deux positions dans les trames, comment affirmer que c'est le même dragon ? Partant, comment dire que l'image du dragon dans la trame N+1 doit être l'image de l'animation d'un dragon qui suit celle utilisée dans la trame N dans cette animation ?

L'animation des tuiles ne pouvant être déduite de celle des entités qu'elles représentent, cela fait qu'à chaque trame, il faut modifier à la main la tuile représentant l'entité pour produire l'animation de l'entité en question. C'est assez lourd. De plus, si jamais une trame est supprimée ou insérée, il faut modifier à la main les tuiles représentant cette entité dans toutes les trames suivantes, sinon la continuité de l'animation de l'entité est rompue. Et cela, pour toutes les entités. Autant dire qu'il vaut mieux n'animer les entités entre les trames qu'à la fin, une fois que la liste des trames qui composent la scène est définitive.

Il est clair qu'au regard de cette contrainte, créer les scènes par animation différentielle sans éditeur aurait été un pur cauchemar.

Sur la manière dont une scène est jouée en assembleur, quelques précisions.

Il n'y a pas de double tampon mémoire, pour deux raisons. Tout d'abord, il est pénible de gérer le double tampon mémoire quand il s'agit de jouer du différentiel : à chaque trame, il faut faire progresser la trame que contient le tampon courant de deux trames dans la scène et non d'une. Ensuite, ce n'est pas la modification de quelques tuiles qui va conduire à ce que le raster rattrape le processeur, produisant le scintillement que le double tampon mémoire permet d'éviter, comme je l'ai expliqué ici.

La seule partie un peu délicate du code, et sans doute la plus longue, est la routine _drawText, qui affiche le texte dans une bulle positionnée par rapport à un angle d'une tuile désigné dans les données de la trame, sur le modèle suivant :

DC.B 24,9,14,1,"I must find",$0A,"the princess"

Ce qui se lit ainsi : afficher une chaîne de 24 caractères - comprenant éventuellement des retours à la ligne - par rapport à un angle de la tuile (9, 14). L'angle, et la position de la bulle par rapport à cet angle, est 1. Il y a quatre valeurs possibles :

programmation d'une cracktro sur Amiga 500
Positionnement d'une bulle de texte par rapport à une tuile

La routine calcule les dimensions de la bulle, et combine ces informations avec son positionnement relatif pour en déduire les coordonnées où l'afficher.

C'est ici que le code devient un peu subtil, car la bulle n'est pas dessinée dans les plans de bits. Elle est dessinée dans le plan de bits de sprites qui sont accolés pour former un super-sprite. Pourquoi ? Parce que c'est drôle, et parce que cela permet d'éviter d'avoir à gérer de la restauration.

Pour ne pas s'emmerder outre-mesure, la bulle est tout de même dessinée dans des mini-plans de bits hors écran, et le contenu de ces mini-plans de bits est ensuite recopié, colonne de seize pixels par colonne de seize pixels, dans les plans de bits des sprites utilisés. Il aurait été beaucoup trop pénible que le code traçant le cadre, remplissant le fond et écrivant les caractères, fasse de sauts des plans de bits d'un sprite à ceux d'un autre sprite tous les seize pixels...

Comme expliqué ici, le matériel de l'Amiga 500 peut afficher huit sprites en quatre couleurs, dont une transparente, d'une largeur de seize pixels et sur toute la hauteur de l'écran. Ainsi, la largeur maximale d'une bulle est de 16*8=128 pixels, ce qui fait quinze caractères en police 8x8 par ligne, quatre pixels étant réservés de part et d'autre pour le bord de la bulle. Cela suffit amplement pour les besoins du menu de triche : les personnages ne déclament pas du Dostoïevski.

La bannière

La bannière est affichée durant la seconde scène exclusivement. Y défilent quelques messages qui n'ont rien de subliminal - on ne me fera pas le même procès qu'au générique du journal de 20 heures sur Antenne 2 en son temps !

programmation d'une cracktro sur Amiga 500
La bannière, et dump Trump the dumb !

Toujours écrit avec la volonté farouche d'éviter toute restauration dans les plans de bits où la scène cinématique est affichée, le code dessine la bannière dans les plans de bits 5 à 6 où la scène n'est pas affichée. Le démarrage de l'animation de la bannière conduit donc à réactiver l'affichage de ces plans de bits...

;Show bitplanes 5 and 6

movea.l copperList,a0
move.w #(DISPLAY_DEPTH<<12)!$0200,10(a0)

...que la fin de l'animation de la bannière conduit à désactiver :

;Hide bitplanes 5 and 6

movea.l copperList,a0
move.w #((DISPLAY_DEPTH-2)<<12)!$0200,10(a0)

Les instructions précédentes tapent dans la liste Copper, où elles modifient la valeur qu'une instruction MOVE écrit dans le registre BPLCON0, lequel permet de notamment de contrôler le nombre de plans de bits affichés.

Tout le code de gestion de la bannière a été factorisé pour resservir en d'autres occasions. Ainsi, il adopte un modèle avec lequel ceux qui ont lu le code de Scoopex "TWO" sont désormais familiers, à savoir :
  • Une routine de configuration (_bnrSetup) s'appuyant sur une structure de données (bnrSetupData) dont les décalages des différents champs sont identifiés par des constantes (OFFSET_BANNERSETUP_*).
  • Une routine d'étape (_bnrStep) s'appuyant sur une structure de données (bnrState) dont les décalages des différents champs sont identifiés par des constantes (OFFSET_BANNER_*).
  • Une routine de réinitialisation (_bnrReset).
  • Une routine de finalisation (_bnrEnd).
Moins pas souci d'économiser de l'espace en mémoire que de ne pas compliquer la conception, la bannière utilise une police de caractères 16x16 qui n'est autre que la police 8x8 du menu et du défilement haute résolution dont les dimensions sont doublées. C'est une solution du même genre que celle déjà mise en oeuvre ici, pour produire une police de même taille. D'ailleurs, c'est le même bout de code qui est utilisé pour créer cette police 16x16 à l'initialisation du menu de triche.

La transition

L'animation cyclique de carrés déphasés, c'est un peu une marque de fabrique. J'ai utilisé cet effet dans presque toutes mes cracktros à l'époque, non seulement parce que je trouvais cela élégant, mais aussi et surtout parce que je ne pouvais recourir aux services d'un graphiste. Quand on ne sait pas dessiner, mieux vaut faire simple : c'est toute ma théorie de la conception.

Le menu de triche m'a donné l'occasion de recycler cet effet, que j'avais repris en 2016 quand je m'étais remis à programmer en assembleur le matériel de l'Amiga. L'idée était alors de produire un certain nombre d'effets, que j'assemblerai un jour dans une démo. Finalement, ils me servent petit à petit pour produire des productions plus modestes, mais des productions tout de même, comme ce menu de triche. Les cimetières sont plein de bien meilleurs codeurs qui seront ignorés à jamais, car ils n'ont jamais sorti quelque chose. Et du moins, ce code ne sera-t-il ainsi pas perdu pour tout le monde.

L'effet est trivial. L'écran est découpé en carrés. Chaque carré est animé : en quelques images, ses dimensions se réduisent à presque rien. A chaque trame - ou plus selon la vitesse qu'on souhaite donner à l'effet -, l'animation de chaque carré progresse d'une image, rebouclant au début si besoin. L'astuce consiste à déphaser les animations des carrés, c'est-à-dire à les faire démarrer toujours à partir de la trame 0 - totalement vide -, mais à des instants différents. Cela permet de produire un motif général, comme ici où l'écran semble se gonfler et se dégonfler :

programmation d'une cracktro sur Amiga 500
Une animation cyclique de carrés déphasés

Plutôt que d'attendre avant de débuter l'animation d'un carré, pourquoi ne pas simplement faire démarrer cette animation avec celle des autres, mais à une trame différente ? Parce que s'il en allait ainsi, la première trame de la transition serait composée de carrés contenant des carrés de différentes tailles. Ce serait trop brutal ; mieux vaut qu'au démarrage de son animation, un carré soit représenté par un carré de côté minimal.

Dans le menu de triche, l'effet permet de ménager une transition entre la fin de la seconde scène et sa reprise, histoire de marquer la fin :

programmation d'une cracktro sur Amiga 500
Une transition à la fin de la seconde scène

Les trames de l'animation du carré ont été générées à l'aide d'un petit outil HTML5 développé pour l'occasion :

programmation d'une cracktro sur Amiga 500
Outil HTML5 de génération des bitmaps de l'animation d'un carré

Plus généralement, cet outil permet de générer ce type d'animation sur un certain nombre de plans de bits, et sous la forme de données RAW - les plans de bits d'une trame les uns après les autres -, ou RAWB - les plans de bits d'une trame entrelacés à chaque ligne. Les raisons d'être de ces formats ont été déjà été expliquées ici.

Le motif produit par les animations déphasées des carrés a été élaboré dans un outil Excel, ici encore développé pour l'occasion. En associant une couleur à l'indice de la trame de départ de l'animation d'un carré, cet outil permet de se faire facilement une idée de ce que sera l'effet produit à l'échelle de l'écran :

programmation d'une cracktro sur Amiga 500
Conception des motifs du découpage dans Excel

Les carrés de la transition font 16x16. Leur motif est inversé, en ce sens où dans une trame de son animation, le carré est un carré de bits 0 sur un fond de bits 1. Cela permet de masquer progressivement le décor tandis que l'animation des carrés s'achève, la dernière trame de cette animation étant simplement un fond de bits 1. Pour produire ce masquage, les carrés sont dessinés dans le plan de bits 5. Ainsi, à la fin de la transition, la palette des pixels à l'écran ne s'étend plus des couleurs 00 à 15 issues de la palette des tuiles, mais des couleurs 16 à 31, toutes fixées au noir.

La condition d'arrêt de la transition est que tous les carrés soient opaques. Comme l'animation de chaque carré démarre avec ou sans retard sur celle de la transition, il est difficile de calculer à l'avance la durée totale de la transition, exprimée en trames. Elle dépend de nombreux paramètres. Le fichier cutter.s contient des commentaires qui donnent plus de détails sur l'algorithme et ce sujet.

Tout comme le code de la bannière, celui de la transition a été factorisé pour resservir en d'autres occasions. Il est regroupé dans le fichier cutter.s :
  • _cutSetup : routine d'initialisation s'appuyant sur une structure de données (cutSetupData) dont les décalages des différents champs sont identifiés par des constantes (OFFSET_CUTTERSETUP_*).
  • _cutStep : routine d'étape s'appuyant sur une structure de données (cutState) dont les décalages des différents champs sont identifiés par des constantes (OFFSET_CUTTER_*).
  • _cutReset : routine de réinitialisation.
  • _cutEnd : routine de finalisation.
Le clavier

La programmation d'un pilote du clavier sur Amiga n'est pas la chose la plus aisée. J'ai déjà exposé ici dans le détail comment s'y prendre pour simplement récupérer le code d'une touche pressée puis relâchée, non seulement par interruption, mais aussi par interrogation continuelle. Dans ce menu de triche, c'est la seconde solution qui est retenue.

Le pilote gère les pressions suivies de relâchements des touches suivantes :
  • Durant le menu, la touche "Espace" fait basculer sur la seconde scène.
  • Durant le menu, une touche correspondant à un chiffre - la rangée de touches au-dessus des lettres, pas celles du pavé numérique - fait jouer un morceau donné du module.
  • Durant la seconde scène, la touche "Espace" fait basculer sur le menu sauf durant l'affichage de la bannière et la transition.
Ce qu'il convient de plus de préciser, c'est que le pilote du clavier n'est pas toujours actif. En particulier, presser puis relâcher la touche "Espace" lorsque lors de l'animation de la bannière durant la seconde scène, ou lors de la transition à la fin de cette dernière, ne produit rien. Pourtant, le joueur pourrait s'attendre à être renvoyé vers le menu, comme c'est le cas lorsqu'il presse puis relâche cette touche à n'importe quel autre moment durant cette scène. Alors, pourquoi couper le clavier ?

La raison, c'est que cela simplifie la gestion de la sortie de ces deux états de la scène. Plutôt que d'avoir à gérer un cas où il faut en sortir alors que ni la bannière ni la transition n'est en cours, un cas où la bannière est en cours, et un cas où la transition est en cours, il suffit de gérer le premier cas. C'est que sortir de la bannière ou de la transition implique une série d'opérations pour restaurer les plans de bits utilisés du menu en l'état. Mieux vaut s'épargner cette complication au prix d'une neutralisation du clavier que le joueur ne remarquera sans doute jamais, la bannière et la transition ne durant que peu de temps.

Or, il ne suffit pas de ne plus appeler le pilote pour couper le clavier. En effet, chaque fois que le joueur presse et relâche une touche, le code de cette dernière s'accumule dans un tampon mémoire du matériel. Dès lors, lorsque le pilote est réactivé, il se met à gérer chaque touche dont le code se trouve dans le tampon mémoire. Cela peut produire cet effet particulièrement indésirable de prise en compte à retard de la pression suivie du relâchement d'une touche.

Il faut donc vider ce tampon mémoire avant de réactiver le pilote. C'est le rôle de ce petit bout de code, factorisé dans une macro pour être répété facilement aux divers endroits où il s'avère nécessaire, c'est-à-dire durant l'animation de la bannière et la transition. En l'espèce :

EMPTY_KEYBOARD:	MACRO
_keyboardFlush\@:
	btst #3,$BFED01
	beq _keyboardEmpty\@
	bset #6,$BFEE01
	WAIT_RASTER 2
	bclr #6,$BFEE01
	WAIT_RASTER 16		;Time required by CIA to move 8 bits from 10 keys
keyboard buffer to SDR and set the interrupt bit in ICR
	bra _keyboardFlush\@
_keyboardEmpty\@:
	ENDM

La musique

Suite à la publication d'une petite annonce sur Wanted pour trouver des comparses afin de produire ma série d'hommages, l'excellent JMD m'a contacté pour me proposer ses services de musicien.

JMD est particulièrement versé dans l'art de la musique chip, comme chacun pourra le constater en écoutant quelques-unes de ses productions sur Bandcamp, ou plus exhaustivement AMP. D'ailleurs, quelle ne fut pas ma surprise de constater que dans la liste des jeux auxquels il a participé, on trouve Despot Dungeon, qui a toute l'apparence d'un jeu à la Ultima !

Grâce soit rendue à JMD, car je dois dire qu'il m'a composé un module aux petits oignons. Qu'on y songe : ce dernier comporte presque une dizaine de mélodies sur mesure, d'après les thèmes que je lui avais indiqués. Du travail de pro !

D'ailleurs, constatant la qualité du travail fourni, j'ai tout de suite regretté que le joueur ne puisse finalement qu'en écouter de brefs passages en regardant la scène cinématique. Comme le pilote du clavier était tout programmé, ne convenait-il pas de rajouter la possibilité de presser les touches numériques - pas celles du pavé, mais celles qui figurent au-dessus des caractères - pour écouter à volonté chacune des mélodies ?

Aussitôt dit, aussitôt fait ! Il suffisait de rajouter quelques lignes. Le joueur est donc invité à presser les touches 1 à 8 pour écouter les divers morceaux dans leur intégralité.

J'en profite pour saluer phx, toujours actif sur Amiga, pour la routine de lecture de modules ProTracker - retouchée à la marge par StingRay. On oublie toujours de le remercier, alors que sa routine doit être aussi utilisée dans les productions de la scène, que Forbid (). Frank Wille - car c'est lui - a donné cette entrevue en 2016. Elle permet de constater l'ampleur de sa contribution.

Le texte défilant

J'allais oublier le texte défilant ! Il serait dommage de ne pas l'évoquer, car il permet d'illustrer comment il est possible d'utiliser le Copper pour modifier la résolution de l'écran à n'importe quelle ligne de ce dernier. En effet, le texte défilant est en haute résolution (640x256) alors que tout le reste est en basse résolution (320x256).

J'avais déjà programmé un texte défilant en haute résolution autrefois. En fait, il en apparaît un en bas de la première - si je me souviens bien - cracktro que j'ai produite :

programmation d'une cracktro sur Amiga 500
Texte défilant en haute résolution dans la cracktro de Flashback

Et dans cette autre cracktro, j'avais utilisé la possibilité de modifier la résolution de l'écran à partir d'une certaine ligne - en passant de plus en entrelacé :

programmation d'une cracktro sur Amiga 500
Changement de résolution dans la cracktro d'Ishar II

Dans le menu de triche, le texte défilant est assez optimal, pour deux raisons :
  • Il s'appuie sur le défilement matériel.
  • À chaque étape, un caractère est recopié, un autre est écrit.
Le défilement est matériel. A la hauteur du texte défilant, le contenu du fameux registre BPLCON1 - celui-là même qui a été détourné pour réaliser du zoom matériel - est modifié pour décaler l'image sur la gauche. Toutefois, en haute résolution, le matériel n'interprète pas les valeurs des bits PF2H3-0 et PF1H3-0 comme en basse résolution. En haute résolution, les valeurs possibles pour PFxH3-0 ne vont pas de 0 à 15, mais de 0 à 7, et chaque incrément de 1 décale les plans de bits concernés de deux pixels haute résolution (un pixel basse résolution).

Les tableaux suivants permettent de s'y retrouver. Pour une amplitude de défilement donné, ils donnent la valeur des bits PFxH3-0 à écrire dans BPLCON1 et le décalage à écrire dans BPLxPTH/L, et de là la formule qui permet de trouver les valeurs des PFxH3-0 à partir de celle de l'amplitude :

programmation d'une cracktro sur Amiga 500

De l'analyse des tableaux précédents, il ressort que :
  • En basse résolution, la valeur de PFxH3-0 est (~(amplitude-1))&$F.
  • En haute résolution, la valeur de PFxH3-0 est (~((amplitude>>1)-1))&$7.
A chaque étape, un caractère est recopié, un autre est écrit. Si le texte défilant est une bande de l'écran qui défile sur la gauche, cette bande a nécessairement une fin. Cela implique qu'à un moment donné, il va falloir reboucler sur le début de cette bande. Comme cela va entraîner l'affichage de caractères qui ont depuis longtemps défilés sur la gauche, il convient donc de recopier la fin de la bande à son début avant de reprendre le défilement à ce début.

La technique qui vient spontanément à l'esprit consiste à utiliser une bande dont la largeur est simplement accrue d'un caractère sur la droite. Dans cet exemple, l'écran fait trois caractères de large :

programmation d'une cracktro sur Amiga 500
Le fonctionnement du texte défilant en simple largeur

Or, cette technique présente un inconvénient. Quand le moment est venu d'écrire un nouveau caractère à la fin de la bande, il faut d'abord recopier les trois caractères finaux au début de la bande. Bilan : quatre caractères manipulés.

Une meilleure solution consiste à utiliser une bande dont la largeur est accrue d'autant de caractères que la largeur de l'écran peut en afficher. Pour reprendre l'exemple précédent, la bande fait donc six caractères de large :

programmation d'une cracktro sur Amiga 500
Le fonctionnement du texte défilant en double largeur

Ici, quand le moment est venu d'écrire un nouveau caractère, le dernier caractère écrit - qui est alors pleinement affiché à droite de l'écran - est d'abord recopié à gauche de l'écran. Bilan : deux caractères manipulés, seulement.

La combinaison du défilement matériel et de la bande de double largeur fait que dans le code du menu de triche, le bloc de mémoire utilisé pour afficher le texte défilant, scrollFrontBuffer, a une largeur de ((2*SCROLL_DX+16)>>3) octets, SCROLL_DX valant 640 pixels. La raison pour laquelle le défilement matériel nécessite seize pixels supplémentaires ne sera pas détaillée ici. C'est fort bien expliqué là, dans l'Amiga Hardware Reference Manual.

Tout comme le code de la bannière et celui de la transition, celui de la transition a été factorisé pour resservir en d'autres occasions. Il est regroupé dans le fichier "scroll.s" :
  • _sclSetup : routine d'initialisation s'appuyant sur une structure de données (sclSetupData) dont les décalages des différents champs sont identifiés par des constantes (OFFSET_SCROLLSETUP_*).
  • _sclStep : routine d'étape s'appuyant sur une structure de données (sclState) dont les décalages des différents champs sont identifiés par des constantes (OFFSET_SCROLL_*).
  • _sclEnd : routine de finalisation.
Par exception, je constate rétrospectivement qu'il n'y a pas de routine _sclReset. J'ai dû avoir la flemme de terminer...

La boucle principale

A ce stade, il ne reste plus qu'à évoquer la boucle principale, tout particulièrement la manière dont les différentes parties du menu de triche s'enchaînent.

La boucle est simplement une série de tests portant sur des drapeaux conservés dans un WORD à l'adresse mainState, qui reflète l'état dans lequel se trouve l'automate du programme à l'instant présent :

STATE_CUTSCENE_RUNNING=$0001
STATE_CUTSCENE_ENDING=$0002
STATE_BANNER_RUNNING=$0004
STATE_MENU_RUNNING=$0010
STATE_KEYBOARD_RUNNING=$0020

Lorsqu'une action doit provoquer un changer d'état de l'automate, elle efface et/ou positionne certains drapeaux. Par exemple, lorsque le menu est affiché - ce qui est l'état de démarrage -, les drapeaux STATE_CUTSCENE_RUNNING, STATE_KEYBOARD_RUNNING, STATE_MENU_RUNNING.

Lors d'une itération de la boucle principale, chaque drapeau est testé et donne lieu à l'exécution d'un bout code associé. Ainsi, dans le cas précédent :
  • STATE_CUTSCENE_RUNNING entraîne l'animation de la scène cinématique dont l'adresse est stockée dans cutScene.
  • STATE_KEYBOARD_RUNNING entraîne le test du clavier en l'exécution de l'action associée à l'éventuel relâchement d'une touche.
  • STATE_MENU_RUNNING entraîne le test de la souris et l'exécution de l'action associée à un éventuel mouvement vertical ou au relâchement d'un bouton.
Certains drapeaux seront effacés quand l'automate rentre dans un certain état. Par exemple, si l'utilisateur presse la touche "Espace" dans l'état précédent - menu de triche affiché sur la première scène -, l'automate bascule dans l'état où c'est la seconde scène qui est jouée, sans qu'aucun menu ne soit affiché. Il ne faut alors plus gérer le menu, si bien que le drapeau STATE_MENU_RUNNING n'est plus positionné dans menuState.

Somme toute, la structure de la boucle principale n'est qu'une imbrication de "switch". Une autre solution aurait été de créer une table de pointeurs de routines, et de limiter la boucle principale à des manipulations sur cette table, tout le code étant autrement factorisé dans les routines en question. Toutefois, sachant qu'il n'y avait somme toute que peu de code, j'ai préféré ne pas pousser jusque-là.

Et voilà !

Non compressé, le menu de triche pèse 91 852 ko. C'est énorme pour un menu de triche. Fort heureusement, un problème de ce type peut être géré sur Amiga grâce à un "cruncher", c'est-à-dire un programme qui peut en compresser un autre, produisant un exécutable contenant tout ce qu'il faut pour décompresser cette version compressée une fois qu'elle a été chargée en mémoire.

En l'espèce, c'est l'excellent Crunch-Mania qui a été retenu, étant aussi élégant qu'efficace. Ce compresseur s'est plié en quatre, ou plutôt il est parvenu à plier en quatre le menu de triche, produisant un exécutable de seulement... 26 416 ko ! Sans doute, cela reste très conséquent pour un menu de triche, mais cela permet de l'utiliser pour de nombreux jeux qui n'occupent pas l'intégralité des 880 ko d'une disquette.

programmation d'une cracktro sur Amiga 500
Crunch-Mania pour pliant le menu de triche en quatre

Et comme d'habitude, une aventure d'Astérix le gaulois se termine toujours autour d'un bon banquet, une production de la sorte doit inévitablement se terminer par les salutations. Je remercie donc tout particulièrement :
  • JMD pour son excellente musique.
  • StingRay pour l'opportunité de produire ce menu de triche.
A bientôt pour la prochaine ? Ce devrait être un hommage aux administrateurs système sous la forme d'une BBS-intro pour le groupe Desire. Je dois bien cela à Ramon B5 pour m'avoir sauvé la mise sur Scoopex "TWO".

Et le lot de tuiles ?

Pour recréer le lot de tuiles, par exemple à partir de celui d'Ultima V que vous trouverez ici, commencez par produire un PNG de 4x21 tuiles. Composez alors votre lot de tuiles selon ce modèle :

programmation d'une cracktro sur Amiga 500

Vous pouvez vous référer au The Codex Of Ultima Wisdom pour vous y retrouver dans les créatures.

Chargez ensuite l'outil BOBsConverter.html qui se trouve dans le répertoire "tools". Attention ! Veillez bien à placer votre PNG dans le répertoire avant d'utiliser l'outil, car les contraintes de sécurité du navigateur vous interdiront de charger un fichier PNG depuis un autre endroit que ce répertoire.

Dans l'outil, chargez votre fichier PNG. Ce dernier doit s'afficher dans "Input". Sélectionnez le format RAWB, puis cliquez sur "Convert!". Le résultat de la conversion doit s'afficher dans "Output". Copiez-collez le code qui apparaît dans la fenêtre "Data" dans un fichier. Chargez ce fichier dans ASM-One, assemblez-le avec la commande "A", puis enregistrez le binaire qui en résulte dans un fichier "tiles.rawb" avec la commande Workbench en spécifiant "start" et "end" comme libellés de départ et de fin.


[Retour en haut] / [Retour aux articles] [Article précédent]