Obligement - L'Amiga au maximum

Vendredi 29 mars 2024 - 01:38  

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 - réadaptation de Mortal Kombat en AGA
(Article écrit par Fabrice Labrador - mars 2015)


1. Introduction
2. Environnement de développement
3. Ce qu'il y a de neuf avec l'AGA
4. Affichage de l'arène et défilement parallaxe
5. Affichage du sprite du joueur
6. Gestion de la manette et des animations
7. Ajout de la musique et des bruitages
8. Conclusion


Chapitre 1 : Introduction [retour au plan]

Bienvenue pour ce deuxième dossier dédié à la programmation du matériel de l'Amiga. Nous allons nous intéresser cette fois-ci aux spécificités du jeu de composants AGA qui équipe la série des A1200 et des A4000.

Et comme on n'a rien trouvé de mieux qu'un exemple concret, j'ai choisi de m'appuyer sur une réadaptation de Mortal Kombat pour décrire les capacités de ce jeu de composants.

La réadaptation restera assez basique puisque nous nous contenterons d'afficher une seule arène et un seul combattant, mais cela nous suffira pour exploiter les nouveautés de l'AGA ainsi que la gestion de la manette de la CD32 (oui, il faut plein de boutons pour jouer à Mortal Kombat).

Passons de suite à la configuration de votre environnement de développement qui évolue un peu par rapport au dossier précédant.


Chapitre 2 : Environnement de développement [retour au plan]

Pour facilement exploiter les nouveaux processeurs équipant les derniers Amiga, j'ai choisi d'utiliser l'assembleur Devpac dans sa version 3.14. Alors là, le gros souci c'est que malheureusement, je ne pense pas qu'il soit disponible de façon gratuite et légale, donc je vous laisse le récupérer comme vous le voulez. Vous pouvez, et je pense que c'est le plus simple, utiliser un émulateur pour suivre ce dossier, WinUAE étant bien sûr le plus connu mais, encore une fois, FS-UAE fonctionne très bien aussi et à l'avantage d'être multiplate-forme.

Utilisation de WinUAE

Créez-vous une configuration à base d'A1200 (68020/AGA) en veillant bien à cocher la case "Cycle exact" dans l'onglet "Chipset", ajoutez un peu de mémoire Fast (4 Mo) et ajoutez un disque dur de type "Directory" qui hébergera nos sources. Pensez aussi à sélectionner "CD32 pad" dans la partie "Game ports" du panneau de configuration.

Encore une fois, je vous conseille de vous rabattre sur des compilations clé en main comme Amiga Forever ou encore ClassicWB pour éviter la fastidieuse phase d'installation du système.

Une fois votre disque dur en place, copiez le contenu de votre disquette Devpac dans un répertoire "Devpac" sur votre disque dur.

Utilisation d'un Amiga AGA

En principe, vous avez déjà un disque dur, il suffit donc de copier le contenu de la disquette Devpac dans un répertoire du disque dur.

Il ne vous reste plus qu'à récupérer l'archive des sources ici et à tout décompresser à la racine de votre disque dur, ou dans un répertoire dédié.

Pour simplifier la compilation, je vous recommande de changer le répertoire d'include par défaut de Devpac (menu "Settings/Assembler/Controls") en ajoutant le répertoire des sources de notre réadaptation dans le champ "Include". Pensez aussi à définir le processeur sur "68020" dans "Settings/Assembler/Options" avant de lancer la compilation.

réadaptation de Mortal Kombat
Disque dur et lancement de Devpac

réadaptation de Mortal Kombat
L'interface de Devpac 3.14


Chapitre 3 : Ce qu'il y a de neuf dans l'AGA [retour au plan]

Le jeu de composants AGA sorti en 1992 n'a pas été la révolution attendue. Certes, il a apporté des améliorations au niveau du graphisme mais c'est à peu près tout, le son n'a pas évolué, on reste toujours sur 4 canaux PCM, le Copper et le Blitter n'ont pas vraiment bénéficié du passage en complet 32 bits. Bref, on a gagné quelques résolutions supplémentaires, deux plans et des sprites plus large, et voilà (oui, je sais je noircis un peu le tableau mais, mince, on était habitué à des choses extra avec l'Amiga).

Donc, en gros, on se retrouve avec des résolution montant jusqu'à 256 couleurs (toujours en mode plan et pas chunky, dommage), le double champ de jeu ("dual playfield") qui passe en 2x4 plans, les sprites jusqu'à 64 pixels de large, une vraie palette 24 bits, le HAM qui fait du 6+2 et un 68020 en entrée de gamme. Bon, ok, y'a de quoi s'amuser mais, bon sang, pourquoi avoir mis de côté le son et le Copper et je passe sous silence l'hypothétique DSP que l'on nous avait fait miroiter pendant des mois.

De plus, comme le nombre de registres n'a pas énormément augmenté, certaines fonctionnalités s'obtiennent au détriment de la simplicité d'utilisation, comme la palette de 256 couleurs 24 bits qui est une vraie plaie à mettre en place (8 banques de 32 couleurs que l'on charge en deux parties de 12 bits par couleur).

Cela reste mon avis mais avec un mode chunky, un DSP du genre de celui du Falcon (de la série des 56000), quatre canaux sonores supplémentaires, un Copper deux fois plus rapide (donc plus précis au final) et un Blitter avec des effets de zoom et de rotation, l'Amiga aurait pu tenir encore longtemps la comparaison avec le PC et nous offrir toujours plus de rêve. Mais bon, le marketing en a décidé autrement et il faut faire avec.


Chapitre 4 : Affichage de l'arène et défilement parallaxe [retour au plan]

Commençons par la partie assez simple de la réadaptation, l'affichage de l'arène et l'effet de défilement parallaxe qui va avec. Pour cela, il nous faut un bon nombre d'images, en gros, autant que de niveau de parallaxe souhaité. En ce qui nous concerne, l'arène se découpe en six niveaux, tout en haut nous aurons la barre de vie du joueur, elle reste statique et est à l'avant-plan, c'est l'image "mk_score.iff" que nous utiliserons. Puis, en dessous, nous avons une montagne, elle aussi statique, vous pouvez la voir dans le fichier "mk_sky.iff". Puis vient se superposer le trône du tyran, nous avons deux images pour celui-ci, "mk_trone1.iff" et "mk_trone2.iff", car il utilise deux palettes distinctes. Au-dessous du trône, nous trouvons les moines "mk_monk.iff", ils vont bénéficier d'une animation en plus de leur défilement. Ensuite, nous trouvons les gardes et le ring "mk_ring.iff".

Techniquement, notre écran va faire 304x220 en double champ de jeu (2x4 plans), alors pourquoi 304 et pas 320 ? Eh bien, c'est simple, je voulais sauvegarder l'ensemble des sprites matériels pour les utiliser pour les combattants, à raison de quatre sprites par combattant. Il faut donc sacrifier un peu de DMA plan de bits pour garder les DMA du dernier sprite tout en ayant un défilement horizontal. Vous verrez à l'usage, on ne voit quasi pas la différence avec un écran de 320 pixels.

Pour obtenir l'effet de parallaxe, nous ferons défiler nos éléments à des vitesses différentes. Comme je disais, le score et la montagne restent statiques, le trône défile à la vitesse de 1 pixel par VBL, les moines à 2 pixels, les gardes à 3 pixels et le ring à 4 pixels par VBL.

Ouvrez à présent le fichier "mk_01.s" nous allons en détailler le contenu. Si vous avez suivi le précédant dossier, vous ne serez par surpris car le squelette du programme est le même que celui utilisé pour la réadaptation de Shadow Of The Beast. On commence donc par inclure les définitions des registres, quelques constantes et des macros (lignes 8 à 18), on définit les canaux DMA et les interruptions à activer (lignes 17 à 21) et enfin le niveau de burst souhaité (ligne 24). Pour notre réadaptation, on n'a pas de burst pour les plans de bits mais on à un burst x4 pour les sprites, ce qui permet d'avoir des sprites de 64 pixels de large.

S'en suit une longue définition de constantes pour décrire nos champs de jeu (lignes 32 à 177) ainsi que les paramètres du défilement parallaxe (lignes 179 à 187). L'amplitude du défilement est de 112 pixels pour le niveau du trône, on le prendra comme niveau de référence et le décalage des autres niveau sera calculé à partir de celui-là.

La première étape, juste après la sauvegarde du système, consiste à décompresser nos images et à les copier dans nos différents tampons mémoire d'écran. Cette fois-ci, nous n'allons pas inclure nos fichiers directement dans notre programme mais nous allons les charger depuis le disque avant de les décompresser. Pour cela, nous utiliserons une nouvelle fonction, "LoadFile", qui se trouve dans le fichier "System.s" et qui charge un fichier dont le nom est passé en paramètre dans un tampon mémoire. Le tampon mémoire sera alloué dynamiquement en utilisant les fonctions de la bibliothèque Exec que l'on a déclaré dans les macro "ALLOCMEM" et "FREEMEM" du fichier "Macro.s".

Intéressons-nous donc à la fonction "InitArena" (lignes 279 à 306), on commence par allouer notre tampon mémoire (ligne 280) pour charger les fichiers. Un seul suffit car une fois le fichier chargé et décodé, il est copié dans son champ de jeu. On charge les fichiers dans l'ordre en commençant par le score (lignes 310 à 339), on utilisera toujours la même trame pour les autres fichiers. On commence par appeler la fonction "LoadILBMFile" (lignes 599 à 612) qui va charger le fichier dans le tampon mémoire et le décompacter à l'aide des fonctions du fichier "IFFTool.s", puis on recopie l'image dans son champ de jeu à l'aide de la fonction "CopyGraphics". Il faut ensuite installer la palette de l'image dans la liste Copper, c'est le rôle de la fonction "LoadCopperPal" et enfin on charge les adresses de nos plans de bits dans la liste Copper grâce à la fonction "LoadCopperBpl". On répète l'opération pour chaque morceau de l'arène, avec un traitement spécial pour le niveau des moines qui propose une animation de huit trames qui sera réalisée en changeant l'adresse de base du champ de jeu affiché en fonction de la frame à rendre.

Une fois notre arène en place, il nous reste à générer les tables de défilement pour chaque niveau de parallaxe, c'est la fonction "InitScrollTable" qui va s'en charger (lignes 681 à 711). Comme je le disais au-dessus, nous allons partir sur un défilement de 112 pixels d'amplitude, on aura donc 112 positions à calculer pour chaque niveau, on utilisera bien sûr la même table de référence que celle de la réadaptation de Shadow Of The Beast, pas de raison de changer une technique qui marche. On prend donc pour chaque niveau la position à calculer, que l'on multiplie par le pas d'incrément du niveau et on va chercher dans la table le décalage de l'adresse de départ et la valeur du décalage à faire. Nos éléments pour le défilement sont prêts, on peut passer à la suite.

On peut à présent installer nos interruptions et notre liste Copper, puis initialiser les canaux DMA (lignes 207 à 226) avant de rentrer dans notre boucle principale. Mais avant ça, il faut que je décrive la liste Copper qui est un peu plus complexe que celle utilisée pour la réadaptation de Shadow Of The Beast, allons voir ça (ligne 1010). On démarre de façon classique en définissant la fenêtre d'affichage, sauf que pour le coup, on déclare un écran de 304x220, ce qui nous donne des valeurs peu habituelles, puis vient la déclaration des adresses des sprites, on fait ensuite une petite pause jusqu'à la ligne $28, on indique nos valeurs de "fetching", si on avait gardé un écran de 320, il aura fallu mettre $30 au lieu de $38 pour démarrer le "fetch" plus tôt à cause du défilement, ce qui nous aurai interdit l'utilisation du dernier sprite. On passe ensuite en mode double champ de jeu sur 8 plans dans BPLCON0, au passage on active le bit ECSENA pour profiter des nouvelles fonctionnalités comme le "border blank". Pour le haut de l'écran, on ne touche pas au décalage ou à la priorité des champs de jeu (BPLCON1 et BPLCON2) qui restent à 0.

Bien, en mode double champ de jeu, les palettes des deux champs de jeu sont dissociées, le premier champ de jeu utilise les 16 premières couleurs et le second champ de jeu utilise les 16 dernières, mais si on laisse le paramétrage par défaut, ce n'est pas vraiment ce que l'on obtient car, comme pour le double champ de jeu version OCS, le second champ de jeu utilise les couleurs 8 à 23. Du coup, on perdrait 8 couleurs pour le premier champ de jeu, ce qui n'est pas vraiment le but. Pour gérer cela, il suffit de paramétrer la palette utilisée par le deuxième champ de jeu dans le registre BPLCON3. Encore plus fort, on peut aussi définir la palette que les sprites utiliserons. Par défaut, ils utilisent les couleurs 16 à 31 mais on peut spécifier une autre palette à utiliser grâce au registre BPLCON4. Donc, pour notre réadaptation, les sprites vont utiliser les couleurs 32 à 47, soit la seconde banque de couleurs. Cela explique le "CMOVE $3060,BPLCON3" qui va donc sélectionner la seconde banque de couleurs, tout en indiquant que le second champ de jeu doit utiliser les couleurs 16 à 31 et, grâce à l'instruction "CMOVE $0022,BPLCON4", on indique que les sprites utiliserons les couleurs 32 à 47. Il ne reste plus alors qu'à charger les couleurs dans leurs registres. On en profite également pour activer le "border blank" ce qui nous permet d'utiliser la couleur du fond comme une vraie couleur sans qu'elle ne déborde sur les côtés.

Il nous reste ensuite à définir les valeurs de décalage des champs de jeu ainsi que les adresses des plans de bits. On répète le processus pour chaque niveau de parallaxe et on obtient notre liste Copper. Il suffit de jouer avec les codes de priorité pour rendre l'effet de superposition. Pour information, le premier champ de jeu contient le trône, les gardes et le ring alors que le second champ de jeu contient le score, la montagne et les moines. Par défaut, les sprites sont prioritaires sur les deux champs de jeu sauf au niveau du score ou le sprite vient s'intercaler entre le ciel et le score.

Nous voici revenus dans notre boucle principale. Comme d'habitude, on commence par attendre le passage de la VBL, puis on traite l'animation des moines en appelant la fonction "AnimateMonk" (lignes 733 à 750), pour cela, on utilise une table, "MonkAnimTable" au format relativement simple puisque le premier mot indique pendant combien de VBL on affiche la frame et le long mot suivant contient l'adresse de base du champ de jeu correspondant à la frame à afficher. On parcours ensuite cette table à chaque VBL pour rendre l'animation.

Une fois l'animation des moines traitée, il nous reste à gérer le défilement des niveaux, c'est le rôle de la fonction "ScrollBackground" (lignes 754 à 812). Pour cela, on se base sur la variable "ScrollPosition" qui contient la valeur du défilement du premier niveau, comprise entre 0 et 111, puis grâce aux tables de défilement créées à l'initialisation, nous allons déterminer pour chaque niveau l'adresse de base du champ de jeu ainsi que le décalage à prendre en compte. Si vous vous souvenez du dossier précédent, vous vous rappelez peut-être que cette valeur de décalage est spécifique à chaque champ de jeu mais est déclarée dans le même registre BPLCON1. Il faut donc bien veiller à réinjecter cette valeur lorsque l'on traite le second champ de jeu, c'est pourquoi la fonction sauve à chaque fois la dernière valeur du décalage pour pouvoir la réinjecter dans le niveau suivant.

Pour mieux voir le résultat, on va s'amuser à incrémenter la valeur de position du défilement dans la VBL en la faisant boucler sur ses 112 positions (lignes 835 à 841). Pour arrêter, il suffit d'appuyer sur le bouton gauche de la souris, on appelle alors la restauration du système et on quitte le programme.

C'est bon, on peut assembler le tout et admirer le résultat. Prochaine étape : afficher le sprite du joueur.

réadaptation de Mortal Kombat
L'arène est prête


Chapitre 5 : Affichage du sprite du joueur [retour au plan]

Bien, l'arène est en place, on peut y amener les combattants. Pour voir ça, vous pouvez déjà jeter un oeil au fichier "mk_cagesprite.iff", alors pour le moment, on ne s'intéresse qu'aux sept premiers sprites, mais pas d'inquiétude à la fin du dossier on jouera avec tous les sprites de cette image.

Jetons maintenant un oeil au fichier "mk_02.s". On reprend, bien sûr, la majorité de ce que nous avions fait au chapitre précédent. On commence par ajouter le canal DMA des sprites à notre variable d'activiation des canaux DMA (ligne 18) et quelques constantes concernant notre sprite (lignes 190 à 203). De suite, après l'initialisation de l'arène, nous allons initialiser les sprites du joueur. Pour cela, on appelle la fonction "InitPlayer".

La première action de cette fonction va être d'allouer un tampon mémoire suffisamment grand pour y charger le fichier des sprites et le décompresser, c'est le rôle de la fonction "InitCageSprite" (lignes 652 à 670) qui va, en plus, initialiser la banque de sprites. Nous reprenons ici la même technique que celle utilisée pour la réadaptation de Shadow Of The Beast avec une petite amélioration, la table contenant comme avant les coordonnées de chaque sprite de l'image et, en plus, elle contient les dimensions du sprite. En effet, nous aurons affaire à des sprites qui peuvent dépasser les 64 pixels de large, il faudra donc en tenir compte dans notre affichage. Seules les coordonnées des sprites sont transformées en adresse, les dimensions restent inchangées dans la table. Il ne faut pas oublier, enfin, de définir les adresses de nos tampons mémoire de sprite (lignes 624 à 640) dans notre liste Copper. Nous avons réservé quatre tampons mémoire en mémoire Chip pour gérer deux paires de sprites de 64 pixels (lignes 1323 à 1343), il faut bien penser à aligner ces tampons mémoire sur des adresses multiples de 64 bits car nous sommes en burst x4 pour les sprites.

On met ensuite en place notre VBL, notre liste Copper et on active les DMA et les interruptions. On saute dans la boucle principale et on appelle la fonction "AnimatePlayer1Sprite" qui va jouer l'animation du sprite du joueur.

Nous allons conserver les bonnes habitudes et utiliser une table pour décrire l'animation du joueur (lignes 1231 à 1246) dont le format reste classique, un premier mot pour décrire le nombre de trames de l'animation, puis pour chaque frame, un mot indiquant pendant combien de VBL on doit jouer la frame et un long mot contenant l'adresse du sprite à afficher. En plus de cela, nous réservons quelques variables pour gérer l'animation (lignes 1212 à 1225), à savoir deux variables pour conserver la position X et Y du joueur (qui ne bouge pas pour le moment), deux autres variables pour conserver les dimensions du sprite à afficher, un compteur de VBL pour la frame en cours, un pointeur sur l'adresse de la frame en cours et enfin un compteur du nombre de trames à afficher. Allons voir comment exploiter tout cela.

La fonction "AnimatePlayer1Sprite" (lignes 829 à 856) est elle aussi dérivée de ce que nous avions fait pour la réadaptation de Shadow Of The Beast, avec une petite différence tout de même, nous avons séparé la fonction de copie du sprite dans son tampon mémoire de la partie de calcul des mots de contrôle du sprite. Pourquoi ? C'est très simple, pour avoir un rendu fluide de l'animation, nous devons pouvoir repositionner notre sprite à chaque VBL (dans le cas bien sûr d'un mouvement). Or, il n'est pas forcément nécessaire de copier un nouveau sprite à chaque VBL, par exemple dans l'animation de déplacement vers l'avant le premier sprite de mouvement doit être affiché pendant 4 VBL mais il doit aussi se déplacer de 4 pixels vers la droite. Or, si on veut mettre à jour ces données à chaque VBL, il n'est pas nécessaire pour autant de copier le sprite dans son tampon mémoire à chaque VBL mais uniquement à la première étape de la frame. Si, au contraire, on ne mettait à jour les coordonnées du sprite qu'à chaque frame, on aurait un mouvement saccadé puisque mis à jour que toutes les 4 VBL. Détaillons cela dans la pratique.

Nous commençons donc par tester si on doit afficher une nouvelle frame de l'animation (lignes 830 et 831) si, tel est le cas, il faut s'assurer que nous ne sommes pas à la fin de l'animation (lignes 832 et 833) auquel cas il nous suffit de la recharger dans notre registre, de mettre à jour le compteur de trames et de sauvegarder notre pointeur sur l'animation (lignes 838 à 837). Pour démarrer une nouvelle frame, on commence par récupérer le pointeur d'animation (ligne 840) puis on charge le compteur de changement de frame, on récupère l'adresse du sprite à afficher et sa taille (lignes 841 à 846) que l'on conserve dans une variable pour la suite avant d'appeler la fonction de copie du sprite "CopyPlayer1Sprite" (lignes 863 à 928).

La copie du sprite va se dérouler en deux étapes, en fonction de la largeur de ce dernier, s'il fait plus de 64 pixels de large, alors nous commençons par recopier la seconde partie du sprite (les 64 derniers pixels) dans les tampons mémoire des sprite 2 et 3. Il faut également tenir compte de la hauteur du sprite en pensant bien à mettre les derniers mots de données à 0 (lignes 890 à 898), puis nous passons à la copie de la première partie du sprite, qui est assez similaire à la partie précédente, mais qui va alimenter les tampons mémoire des sprites 0 et 1.

Comme je l'expliquais au-dessus, cette copie du sprite n'intervient que lorsque l'on change de frame dans l'animation, par contre la mise à jour des coordonnées du sprite intervient à chaque VBL, c'est le rôle de la fonction "SetupPlayer1SpriteControl". Il faut d'abord lui fournir les coordonnées d'affichage du sprite (qui ne bouge pas pour le moment) et ses dimensions (lignes 851 à 854). Comme pour la copie, nous allons d'abord tester si notre sprite fait plus de 64 pixels de large (lignes 937 à 940), dans ce cas, nous mettons à jour les mots de contrôle des sprites 2 et 3, le calcul des mots de contrôle reprend la fonction utilisée pour la réadaptation de Shadow Of The Beast, le résultat est retourné dans d0. Si notre sprite fait 64 pixels, il ne faut pas oublier de mettre les mots de contrôle des sprites 2 et 3 à 0, sinon nous allons afficher un bout de sprite non désiré (lignes 960 à 970). Nous passons finalement au calcul des mots de contrôle des sprites 0 et 1 (lignes 972 à 979). Pour information, dans un sprite de 64 pixels, nous avons quatre longs mots pour définir les mots de contrôle mais seuls le premier et le 8e mot sont pris en compte pour l'affichage du sprite. Ils correspondent au premier et second mot de contrôle d'un sprite de 16 pixels.

Voilà, notre combattant est arrivé, il va falloir l'animer à présent, et là, ça commence à devenir un peu plus complexe.

réadaptation de Mortal Kombat
Welcome mister Cage


Chapitre 6 : Gestion de la manette et des animations [retour au plan]

Attention, gros morceau !

Ok, cette partie est sans doute la plus complexe de notre réadaptation, pas forcément au niveau gestion du matériel mais surtout au niveau algo. Je vais la découper en deux parties, gestion de la manette avec analyse des mouvements et gestion des animations du joueur.

Commençons par ouvrir le fichier "mk_03.s". Là encore, on repart du fichier précédent et on ajoute les constantes pour la gestion des mouvements (lignes 190 à 212). Vous pouvez voir que l'on a attribué un décalage pour chaque animation du joueur, il va nous servir à déterminer l'animation à jouer par rapport à une table des animations.

Mais avant cela, il faut commencer par tester la manette afin de déterminer le mouvement fait par le joueur. Lorsqu'il est sorti sur Amiga, Mortal Kombat a souffert d'un gros handicap comparé à sa version arcade : c'est le manque de coups différents qui nous étaient proposés, ceci étant principalement dû au fait, qu'à l'époque, les manettes ne comportaient qu'un seul, au mieux deux, boutons. Il fallait donc faire avec. Heuresement, nous avons, depuis la sortie de la CD32, eu droit à une manette avec pas moins de sept boutons. Je vous propose donc d'en profiter et pour cela, nous allons ajouter une fonction de test de la manette. Ouvrez le fichier "Input.s", vous remarquerez de suite que nous avons ajouté quelques constantes pour la gestion de la manette ainsi qu'une fonction "CheckGamepad1" (lignes 133 à 173) qui contrôle la manette branchée sur le port 1 et nous retourne son état dans d0. Je ne vais pas détailler cette fonction, elle vient de la trousse de développement logiciel de la CD32. Sachez simplement que les boutons de la manette agissent un peu comme un potard et qu'en fait ils correspondent à un certain seuil électrique. Le principal, c'est que cela fonctionne (testé sur un vrai matériel). Pour notre réadaptation, nous allons utiliser quasiment tous les boutons de la manette (autant se faire plaisir) :
  • Le bouton rouge correspondra au coup de poing haut.
  • Le jaune au coup de poing central.
  • Le vert au coup de pied haut.
  • Le bleu au coup de pied central.
  • Le "rewind" au blocage.
  • La croix servira évidemment à déplacer le sprite du joueur.
La position de la manette sera contrôlée dans la VBL mais pas à chaque VBL, en effet, il est inutile de tester un changement de position 50 fois par seconde car, même si vous êtes très rapide, il me semble juste impossible de faire 50 mouvements différents par seconde. Nous allons donc tempérer ce contrôle en le réalisant que toutes les huit VBL (c'est la constante MOVE_CHECK qui fixe cette valeur, vous pouvez la modifier à loisir). Cela vous laisse le loisir de changer six fois de positions par seconde, ça reste largement acceptable. Cette temporisation a un autre but, pour gérer les enchainements de coups (pour les coups spéciaux ou les finish), nous allons utiliser une pile de coups joués (ligne 1504). Ainsi, nous pourrons simplement tester si un motif de coups a été effectué par le joueur pour déclencher l'animation correspondante. Si nous testions la manette à chaque VBL, cela voudrait dire que pour un coup spécial de quatre mouvements, il faudrait les enchaîner en quatre VBL, soit en gros 80 ms, autant dire c'est juste impossible. En réduisant la fréquence de test, cela laisse au joueur huit fois plus de temps pour exécuter son enchaînement, soit ici 640 ms, ce qui semble plus réaliste au final. Notre VBL se contente donc de vérifier la temporisation et d'appeler la fonction "CheckGamepadPlayer1" qu'une fois sur 8 (lignes 1237 à 1243).

La fonction "CheckGamepadPlayer1" (lignes 1250 à 1273) commence par appeler la fonction de test de la manette puis charge l'adresse de la pile des coups. On regarde ensuite si le joueur a changé de position depuis le dernier test et on stocke le résultat dans la variable "Player1SwitchMove". On fait cela car dans le jeu, si le joueur reste appuyé sur un bouton de coup, il ne faut pas que l'on rejoue le coup à chaque contrôle mais uniquement la première fois. Cette variable va nous aider à tester cette situation. Il faut ensuite empiler la nouvelle position sur la pile des coups joués (lignes 1258 à 1263).

On peut, à présent, déterminer le prochain coup du joueur. Pour cela, la bonne vieille technique de la table de coups sera utilisée, cela nous évitera un long processus d'enchaînement de tests qui risque de vite devenir ingérable pour peu que l'on souhaite ajouter de nouveaux coups. Donc, nous créons une table "Player1MoveTable" qui va contenir deux données pour chaque mouvement possible du joueur. La première étant la position du manette et la seconde la fonction à appeler pour gérer le mouvement du joueur. Notre fonction va parcourir cette table et tester si la dernière position de la manette correspond à une position décrite dans la table (lignes 1266 à 1270). Si c'est le cas, on appelle la fonction associée (ligne 1271), si on ne trouve pas de position correspondante, alors on reste sur la position précédente. Il faut tout de même faire attention à ce que cette table ne soit pas trop longue pour rester sur un temps de traitement raisonnable.

Je vais détailler deux fonctions représentatives de gestion des mouvements du joueur. Tout d'abord, la fonction "P1MoveLeft" (lignes 1280 à 1290) qui est exécutée lorsque le joueur déplace la manette vers la gauche. Pour ce mouvement, nous devons gérer deux cas, un appui simple effectuera un déplacement rapide à gauche alors qu'un appui prolongé jouera une séquence complète de déplacement. Il faut donc tester au départ si nous ne sommes pas déjà en train de joueur l'animation de déplacement vers la gauche. Si tel est le cas, il faut jouer la séquence longue, sinon, on joue la séquence rapide. Le mouvement en cours est stocké dans la variable "Player1CurrentAnim" par la routine d'animation du joueur que nous verrons plus tard. Nous testons donc s'il correspond à un mouvement de déplacement vers la gauche et, en fonction, nous alimentons la variable "Player1NextAnim" qui correspond à la prochaine animation à jouer avec la bonne valeur de déplacement. Voyons à présent la fonction "P1HighPunch" (lignes 1327 à 1335) qui gère le coup de poing haut. Comme je l'expliquais, il ne faut pas que l'animation se joue en boucle si l'on reste appuyé sur le bouton de la manette, on commence donc par tester si le joueur a changé de position depuis le dernier contrôle (ligne 1328) à l'aide de la variable "Player1SwitchMove". Si c'est le cas, alors nous lançons l'animation du coup de poing haut, sinon on lance l'animation de la position d'attente, simple, non ?

J'espère que vous avez bien saisi le principe de la gestion de la manette, il faut voir que ce traitement est totalement dissocié de la gestion des animations du joueur. En effet, les positions de la manette sont quasiment les mêmes quel que soit le personnage sélectionné (à l'exception des coups spéciaux), alors que les animations sont spécifiques à chaque personnage. Il faut donc bien séparer les deux traitements si on ne veut pas avoir à coder une gestion de la manette par personnage du jeu, ce qui deviendrait vite ingérable.

Dans un souci de clareté, j'ai choisi d'isoler l'ensemble des données d'animation d'un personnage dans un fichier à part. Ouvrez le fichier "cage_anim.s", nous allons l'étudier. En premier lieu, nous trouvons la table des animations du personnage qui regroupe simplement les adresses des différentes animations du personnage à jouer en fonction des mouvements du joueur, dans l'ordre définis par les constantes MOVE_ (lignes 8 à 28). Puis, nous avons les descriptions de chacune des animations du personnage, elles sont toutes basées sur la même structure. Étudions la première, "CageWaitAnimTable" (lignes 30 à 54). Le premier mot indique le nombre de trames de l'animation, le deuxième mot indique si l'animation peut être interrompue par une autre animation (par exemple, lorsque le joueur se déplace vers la gauche, il doit pouvoir à tout moment déclencher un coup de pied), enfin, le long mot suivant contient l'adresse du son associé à l'animation (nous gérerons cela dans le prochain chapitre), "0" voulant dire pas de son associé. Ensuite, nous retrouvons la structure classique de l'animation, un mot pour indiquer le nombre de VBL que doit durer la frame, puis l'adresse du sprite à afficher et, petite nouveauté, les décalages de déplacement du sprite au cours de l'animation. Il suffit de répéter ce schéma pour chaque animation du joueur et c'est réglé. Pour terminer, nous avons notre banque de sprites qui s'est un peu étoffée depuis le dernier chapitre. Idéalement, si nous devions réellement réaliser un jeu, il nous faudrait des outils pour gérer ces structures de données plutôt que de tout créer à la main.

Passons maintenant au coeur du programme, la fonction "AnimatePlayer1Sprite" (lignes 856 à 899) qui n'est pas nouvelle mais que l'on a pas mal amélioré depuis le dernier chapitre. Cette dernière est toujours appelée à chaque VBL afin d'avoir une animation fluide du personnage. On commence donc de façon classique par tester si l'on doit afficher la frame suivante de l'animation (lignes 857 et 858), puis on regarde si l'animation est arrêtable (on peut donc interrompre une animation entre deux trames). Si c'est le cas, il faut regarder si on doit jouer une nouvelle animation (lignes 859 à 863) ou continuer l'animation en cours en passant à la frame suivante et en s'assurant que l'on n'est pas arrivé à la fin de l'animation (lignes 865 et 866).

Pour déterminer la prochaine animation à jouer, on récupère la valeur de la variable "Player1NextAnim" puis on recherche, grâce à la table des animations "Player1AnimTable", l'adresse de l'animation à jouer (lignes 869 à 871). On peut alors initialiser nos variables avec les données de l'animation, d'abord le compteur de trames, puis le drapeau indiquant si l'animation est arrêtable et enfin le son à jouer avec l'animation (lignes 873 à 875). On sauve ensuite le pointeur pour le traitement suivant. Si on doit jouer un son, on appelle la fonction "PlaySoundFx" (on implémentera cette fonction au chapitre suivant). On déroule ensuite le traitement classique de gestion d'une nouvelle frame avec en plus la récupération du décalage de déplacement du sprite que l'on va stocker dans la variable "Player1Movement" et on recopie le nouveau sprite dans ses tampons mémoire (lignes 881 à 890).

On utilise ensuite la variable "Player1Movement" pour calculer la nouvelle position du sprite en appelant la fonction "CalculPlayer1Position" (lignes 990 à 1008). On se contente en fait d'ajouter les décalages X et Y à la position courante de notre sprite que l'on a stocké dans la variable "Player1Position". On en profite aussi pour tester si notre sprite a atteint la limite gauche ou droite de l'écran, et dans ce cas, on en profites pour faire défiler l'écran en appelant soit "MoveBGLeft", soit "MoveBGRight" et finalement on sauvegarde la nouvelle position du sprite dans sa variable. La dernière étape de notre fonction d'animation consiste à mettre à jour les mots de contrôle de nos sprite en fonction de leurs nouvelles positions et c'est le rôle de la fonction "SetupPlayer1SpriteControl" qui n'a pas changé depuis le chapitre précédent.

C'en est fini pour la gestion de nos animations, vous pouvez assembler le fichier et l'exécuter. N'oubliez pas votre manette et on se retrouve pour le chapitre suivant.

réadaptation de Mortal Kombat
Tiens, prends ça !

réadaptation de Mortal Kombat
Et ça !


Chapitre 7 : Ajout de la musique et des bruitages [retour au plan]

Bien, j'espère que vous avez bien digéré le chapitre précédent, nous allons attaquer une partie moins dure pour ce chapitre. On va dire qu'il s'agit des finitions puisque cela concernera la musique et les bruitages.

Ouvrez le fichier "mk_04.s", nous y avons ajouté peu de choses car quasiment tout était prêt depuis la dernière version. On commence par les constantes liées au son (lignes 215 à 219). Pour cette réadaptation, je me suis contenté de deux sons, un pour les coups de poing et un pour les coups de pied (si, si, vous verrez, il y a une nuance dans le cri). Le tout sera bien sûr joué en plus de la musique en utilisant la même routine que celle de la réadaptation de Shadow Of The Beast.

La première étape consiste à initialiser nos sons. Pour cela, nous appelons la fonction "InitSoundFx" (lignes 770 à 785), qui se contente de réserver un tampon mémoire pour stocker un fichier et qui appelle respectivement les fonctions "InitPunchSound" et "InitKickSound" qui vont s'occuper de charger le fichier sonore, de le décompresser si besoin et d'initialiser la structure sonore qui nous servira à rejouer le son. Bien sûr, cette structure se trouve en mémoire Chip car elle contient l'échantillon à jouer. Son format est très simple, les trois premiers mots contiennent respectivement la taille de l'échantillon en mots, la période de l'échantillon et enfin le volume de l'échantillon. Directement après, on trouve l'échantillon lui-même (lignes 1813 à 1821).

Je vais rapidement passer sur le comment on lance la musique car c'est rigoureusement identique à ce que l'on a fait pour la réadaptation de Shadow Of The Beast (lignes 295 à 304). Intéressons-nous plutôt à la façon dont on gère les effets sonores. Rappelez-vous, au chapitre précédent, nous avions vu que dans la structure de la table d'une animation, nous avions stocké l'adresse du son à jouer pour l'animation en question, puis dans la fonction "AnimatePlayer1Sprite", nous appelions la fonction "PlaySoundFx" pour jouer ce son (dans le cas où l'adresse du son n'était pas nulle). Allons voir notre fonction (lignes 1374 à 1381), elle est excessivement simple, on se contente de récupérer les données stockées dans notre structure sonore et on les passe simplement à la fonction "mt_soundfx" et voilà, c'est réglé !

Amusez-vous à mettre quelques coups de poing ou de pied pour entendre Johnny Cage poussez un cri. Notre réadaptation est à présent terminée, il est temps de passer à la conclusion.


Chapitre 8 : Conclusion [retour au plan]

J'espère que vous avez apprécié ce nouveau dossier et que l'AGA a maintenant moins de secrets pour vous. Comme vous l'avez peut-être remarqué, le fichier source contient quelques fonctions non achevées qui pourraient laisser deviner une future suite avec, très certainement, l'apparition d'un second joueur. Ce n'est pas impossible. De plus, cela nous permettrait de mettre en pratique une autre fonctionnalité de l'Amiga, à savoir la gestion des collisions.

Encore une chose, sur la dernière version du programme "mk_04.s", j'ai ajouté une constante DEBUG_MODE qui va nous permettre de voir simplement le temps processeur utilisé pour réaliser cette animation. En la passant à 0, vous aller voir apparaître des lignes horizontales de couleur en haut de l'écran. A chaque couleur correspond une étape du rendu de la frame, le rouge pour la gestion du manette et du mouvement du joueur (qui est tellement rapide qu'elle n'apparaît même pas dans l'espace visible de l'écran), la verte pour la gestion et l'affichage de l'animation du joueur, la cyan pour la gestion de l'animation des moines et la bleu pour la gestion du défilement. Vous remarquerez que ces traitements prennent au plus une dizaine de lignes raster, j'ai même dû mettre une temporisation d'attente au début sinon elles seraient aussi apparues dans la partie non visible de l'écran. Tout cela pour dire qu'il nous reste énormément de temps processeur de disponible pour gérer un second joueur et les interractions entre les joueurs. Bref, je cherche toujours à comprendre comment ils ont fait pour ne pas avoir une version originale fluide de ce jeu sur Amiga (et je ne parle pas non plus de la conversion de Street Fighter 2).

Je vous encourage vivement à prendre possession du source et à rajouter vos propres créations comme des sons en plus ou de nouvelles animations, la structure du programme a été pensée pour simplifier ce genre d'ajouts.

On se retrouve prochainement pour un nouveau dossier et, d'ici là, bon développement à tous.


[Retour en haut] / [Retour aux articles]