La Place des Développeurs Déplacement d'un sprite en assembleur ... ou le résultat de mes derniers travaux
Comme vous le savez, je passe pas mal de temps à faire des tests et des recherches en assembleur.
Je me suis dit qu'il pourrait être bien en l'absence (provisoire ? ) de tutoriel Z80 pour MSX de vous faire partager mes recherches, quitte à ce que les membres les plus éminents de notre communauté améliorent ce que je propose. Pour le code, j'utiliserai la syntaxe d'AsMSX, qui est assez facile à mettre en oeuvre (et surtout ça m'évite de coder un header pour les roms ).
Ici donc, je suis parti de la problématique suivante : déplacement d'un sprite avec le clavier, avec tests des bords de l'écran et modification de sa forme suivant la direction prise.
Pour les néophytes en Z80, il faut vous expliquer que toutes les jolies fonctions et instructions du MSX-Basic telles que PUTSPRITE ou SCREEN n'existent pas en tant que tel, et il faut tout se repalucher pour arriver à quelque chose de concret et d'à peu près correct ! Heureusement, il y aFindus les routines du BIOS qui vont énormément nous aider. Les puristes râleront peut-être à cause du manque de rapidité, mais pour mon exemple, c'est plus que suffisant !
Passons maintenant à l'analyse du problème :
Dans la VRAM, un sprite est caractérisé par ses attributs, qui sont ses coordonnées, son numéro de patron (j'y reviendrai plus tard) et sa couleur. Toutes ces informations sont rangées dans une partie de la VRAM appelée table des attributs de sprites. Suivant le mode d'affichage utilisé, l'adresse de cette table change, pour ça je vous invite à regarder toute la documentation dans les divers ouvrages de la bibliothèque du village. J'ai opté pour un affichage en SCREEN 2, je consulte donc mon « Pratique du MSX2 » et hop miracle je trouve mes adresses des différentes tables dans la VRAM :
- Table des noms : 01800H
- Table des couleurs : 02000H
- Table des formes ou patrons : 00000H
- Table des attributs de sprites : 01B00H
- Table des formes ou patrons de sprites : 03800H
- Table de palette (sur MSX2) : 02020H
Je vous fais un topo rapide sur les différentes tables :
La table des noms est comme une matrice de 32 colonnes sur 24 lignes, dont chaque case contient un octet (entre 0 et 255, ou 0h et Ffh pour faire vrai ). Cet octet correspond à une forme bien particulière, qui est elle définie sur la table des formes ou patrons. Je ne m'étends pas sur le sujet de la division en 3 parties égales de cette table, ce n'est pas l'objet ici. La table des attributs de sprites contient 128 octets, soit des informations pour 32 sprites de taille normale en 8*8, ça tombe bien vu qu'on a 32 plans de sprites, comme quoi sur MSX tout est bien pensé !
Dans l'ordre, c'est l'ordonnée, l'abscisse (révisez vos cours de maths si vous avez oublié à quoi ça correspond ), le numéro de forme et la couleur. Le numéro de forme correspond, à l'instar de ce qui se fait pour la table des noms, à une forme définie dans la table des formes de sprites.
Pour mon exemple, les tables qui m'intéressent sont la table des noms, pour le fond d'écran (que je vais remplir de... vide), la table des patrons de sprites pour y dessiner les formes, et la table des attributs de sprite.
Et maintenant, codons tout ça !
Première étape : préparation des données et variables, directives d'assemblage spécifiques
Pour réaliser mon exemple, je vais prendre le sprite du Metalion (pour les incultes, le vaisseau de Nemesis 2, pas notre concitoyen du village ). Il a 3 formes distinctes : une que je qualifierai de normale, une lorsqu'il se déplace vers le haut, une lorsqu'il se déplace vers le bas. En passant, ce ne sera pas un sprite seul, vu qu'il a 2 couleurs pour chaque forme ! En tout j'aurai donc 6 sprites de 16*16 pixels à coder.
Je vais utiliser des labels pour chaque série de données :
SPR_NEM pour la définition de la position "normale", SPR_UP pour la position vers le haut, SPR_DOWN pour la position vers le bas.
Pour chacun de ces labels, une série de 64 octets, les 32 premiers codant le sprite blanc, les 32 suivant le sprite de couleur bleue ou rouge.
On aura donc ceci à placer dans notre code :
SPR_NEM:
db 000h,080h,0e0h,070h,07fh,07fh,0bfh,0bfh,0bfh,078h,0e0h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,088h,0ffh,0f8h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,0c0h,070h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h
SPR_UP:
db 000h,080h,0e0h,070h,0ffh,01fh,001h,000h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,080h,0f0h,0ffh,000h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,0e0h,0feh,07fh,03fh,01eh,018h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,000h,000h,0fch,0f0h,000h,000h,000h,000h,000h,000h,000h
SPR_DOWN:
db 000h,000h,0b0h,0fch,07fh,07fh,0bfh,0bfh,0bfh,07fh,078h,0e0h,000h,000h,000h,000h
db 000h,000h,000h,000h,080h,018h,08fh,0fch,0e0h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,0e0h,070h,000h,000h,000h,000h,000h,000h,000h,000h,000h
Les labels servent à appeler une adresse, sans avoir à l'entrer manuellement, ce qui est plus qu'utile dans un code long pour se repérer !
Ensuite, il me faut les attributs de sprite initiaux. On va placer le vaisseau au centre de l'écran, avec le sprite de forme normale (le premier blanc, le deuxième rouge). Je crée donc un nouveau label comme suit :
ATR_INITIAL:
db 88,120,0,15
db 88,120,4,8
Il me faut maintenant penser à ce qui est variable, donc me réserver de l'espace en dehors du code pour les données qui sont amenées à changer. Ce qui est amené à être modifié, ce sont les attributs de sprites. 4 octets pour chaque sprite, donc 8 octets à réserver. Je les positionne à partir de l'adresse 0C00H (codée en .page3 par AsMSX), grâce à ds qui permet de réserver un nombre d'octets. Evidemment, pour me repérer plus facilement, j'y mets aussi un label (ici SPRITES). Voici la portion de code correspondante :
.page 3
SPRITES:
ds 8
Il ne me reste plus qu'à indiquer quelques bricoles à AsMSX pour qu'il me compile mon programme bien comme il faut (attention, cette syntaxe est je le répète spécifique à AsMSX) :
Voici le code :
.bios
.page 2
.rom
.start DEBUT
La première ligne indique au compilateur que je vais utiliser les routines du BIOS.
La deuxième que mon code d'assemblage commence en page 2, soit en 08000H.
La troisième ligne indique que le programme sera compilé sous forme de ROM (vous pourrez le lancer via le port cartouche de votre émulateur préféré).
Enfin, la quatrième ligne indique l'adresse à partir de laquelle le programme sera exécuté (remplacée ici par un label, une fois de plus).
Suite au prochain numéro !
Je me suis dit qu'il pourrait être bien en l'absence (provisoire ? ) de tutoriel Z80 pour MSX de vous faire partager mes recherches, quitte à ce que les membres les plus éminents de notre communauté améliorent ce que je propose. Pour le code, j'utiliserai la syntaxe d'AsMSX, qui est assez facile à mettre en oeuvre (et surtout ça m'évite de coder un header pour les roms ).
Ici donc, je suis parti de la problématique suivante : déplacement d'un sprite avec le clavier, avec tests des bords de l'écran et modification de sa forme suivant la direction prise.
Pour les néophytes en Z80, il faut vous expliquer que toutes les jolies fonctions et instructions du MSX-Basic telles que PUTSPRITE ou SCREEN n'existent pas en tant que tel, et il faut tout se repalucher pour arriver à quelque chose de concret et d'à peu près correct ! Heureusement, il y a
Passons maintenant à l'analyse du problème :
Dans la VRAM, un sprite est caractérisé par ses attributs, qui sont ses coordonnées, son numéro de patron (j'y reviendrai plus tard) et sa couleur. Toutes ces informations sont rangées dans une partie de la VRAM appelée table des attributs de sprites. Suivant le mode d'affichage utilisé, l'adresse de cette table change, pour ça je vous invite à regarder toute la documentation dans les divers ouvrages de la bibliothèque du village. J'ai opté pour un affichage en SCREEN 2, je consulte donc mon « Pratique du MSX2 » et hop miracle je trouve mes adresses des différentes tables dans la VRAM :
- Table des noms : 01800H
- Table des couleurs : 02000H
- Table des formes ou patrons : 00000H
- Table des attributs de sprites : 01B00H
- Table des formes ou patrons de sprites : 03800H
- Table de palette (sur MSX2) : 02020H
Je vous fais un topo rapide sur les différentes tables :
La table des noms est comme une matrice de 32 colonnes sur 24 lignes, dont chaque case contient un octet (entre 0 et 255, ou 0h et Ffh pour faire vrai ). Cet octet correspond à une forme bien particulière, qui est elle définie sur la table des formes ou patrons. Je ne m'étends pas sur le sujet de la division en 3 parties égales de cette table, ce n'est pas l'objet ici. La table des attributs de sprites contient 128 octets, soit des informations pour 32 sprites de taille normale en 8*8, ça tombe bien vu qu'on a 32 plans de sprites, comme quoi sur MSX tout est bien pensé !
Dans l'ordre, c'est l'ordonnée, l'abscisse (révisez vos cours de maths si vous avez oublié à quoi ça correspond ), le numéro de forme et la couleur. Le numéro de forme correspond, à l'instar de ce qui se fait pour la table des noms, à une forme définie dans la table des formes de sprites.
Pour mon exemple, les tables qui m'intéressent sont la table des noms, pour le fond d'écran (que je vais remplir de... vide), la table des patrons de sprites pour y dessiner les formes, et la table des attributs de sprite.
Et maintenant, codons tout ça !
Première étape : préparation des données et variables, directives d'assemblage spécifiques
Pour réaliser mon exemple, je vais prendre le sprite du Metalion (pour les incultes, le vaisseau de Nemesis 2, pas notre concitoyen du village ). Il a 3 formes distinctes : une que je qualifierai de normale, une lorsqu'il se déplace vers le haut, une lorsqu'il se déplace vers le bas. En passant, ce ne sera pas un sprite seul, vu qu'il a 2 couleurs pour chaque forme ! En tout j'aurai donc 6 sprites de 16*16 pixels à coder.
Je vais utiliser des labels pour chaque série de données :
SPR_NEM pour la définition de la position "normale", SPR_UP pour la position vers le haut, SPR_DOWN pour la position vers le bas.
Pour chacun de ces labels, une série de 64 octets, les 32 premiers codant le sprite blanc, les 32 suivant le sprite de couleur bleue ou rouge.
On aura donc ceci à placer dans notre code :
SPR_NEM:
db 000h,080h,0e0h,070h,07fh,07fh,0bfh,0bfh,0bfh,078h,0e0h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,088h,0ffh,0f8h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,0c0h,070h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h
SPR_UP:
db 000h,080h,0e0h,070h,0ffh,01fh,001h,000h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,080h,0f0h,0ffh,000h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,0e0h,0feh,07fh,03fh,01eh,018h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,000h,000h,0fch,0f0h,000h,000h,000h,000h,000h,000h,000h
SPR_DOWN:
db 000h,000h,0b0h,0fch,07fh,07fh,0bfh,0bfh,0bfh,07fh,078h,0e0h,000h,000h,000h,000h
db 000h,000h,000h,000h,080h,018h,08fh,0fch,0e0h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h
db 000h,000h,000h,000h,000h,0e0h,070h,000h,000h,000h,000h,000h,000h,000h,000h,000h
Les labels servent à appeler une adresse, sans avoir à l'entrer manuellement, ce qui est plus qu'utile dans un code long pour se repérer !
Ensuite, il me faut les attributs de sprite initiaux. On va placer le vaisseau au centre de l'écran, avec le sprite de forme normale (le premier blanc, le deuxième rouge). Je crée donc un nouveau label comme suit :
ATR_INITIAL:
db 88,120,0,15
db 88,120,4,8
Il me faut maintenant penser à ce qui est variable, donc me réserver de l'espace en dehors du code pour les données qui sont amenées à changer. Ce qui est amené à être modifié, ce sont les attributs de sprites. 4 octets pour chaque sprite, donc 8 octets à réserver. Je les positionne à partir de l'adresse 0C00H (codée en .page3 par AsMSX), grâce à ds qui permet de réserver un nombre d'octets. Evidemment, pour me repérer plus facilement, j'y mets aussi un label (ici SPRITES). Voici la portion de code correspondante :
.page 3
SPRITES:
ds 8
Il ne me reste plus qu'à indiquer quelques bricoles à AsMSX pour qu'il me compile mon programme bien comme il faut (attention, cette syntaxe est je le répète spécifique à AsMSX) :
Voici le code :
.bios
.page 2
.rom
.start DEBUT
La première ligne indique au compilateur que je vais utiliser les routines du BIOS.
La deuxième que mon code d'assemblage commence en page 2, soit en 08000H.
La troisième ligne indique que le programme sera compilé sous forme de ROM (vous pourrez le lancer via le port cartouche de votre émulateur préféré).
Enfin, la quatrième ligne indique l'adresse à partir de laquelle le programme sera exécuté (remplacée ici par un label, une fois de plus).
Suite au prochain numéro !
MSX un jour, MSX toujours !
Deuxième étape : préparation de l'écran
Il nous faut passer en SCREEN 2, indiquer qu'on utilise des sprites en 16*16, passer en COLOR 15,0,0 et enfin effacer l'écran !
Mais avant de commencer, et pour éviter des manipulations fastidieuses pour les adresses des différentes tables qui nous intéressent, on va utiliser quelques EQU !
NAMTBL pour la table des noms, SPRTBL pour la table des sprites, SPRATR pour la table des attributs de sprites. Ainsi, quand j'aurais besoin de ces tables dans mon code, j'utiliserai les noms au lieu des adresses !
Maintenant passons dans le vif du sujet !
Voici la portion de code qui va faire tout ce qu'il faut, je la détaillerai après :
- DEBUT: c'est le label qui indique où mon programme va commencer, rien de bien méchant là-dedans.
- call DISSCR : premier appel à une routine BIOS ! Celle-ci a comme effet d'empêcher l'affichage pendant que je bidouille la VRAM !
- ld hl,0f3e9h : j'utilise une des adresses de la zone de communication : celle qui contient la couleur d'écriture ! Je charge cette adresse dans l'accumulateur 16 bits.
- ld [hl],15 : je place la valeur 15 à cette adresse, la couleur d'écriture passe donc au blanc.
- inc hl
ld [hl],0
inc hl
ld [hl],0 : j'incrémente la valeur de l'accumulateur, ce qui me permet de passer à la couleur de fond (que je mets à 0) en utilisant la même méthode que précédemment. J'enchaîne aussi sur la couleur de bordure.
- call INIGRP : nouvelle routine BIOS, qui me permet de passer immédiatement en SCREEN 2.
- ld bc,06201h
call WRTVDP : j'utilise la routine WRTVDP qui me permet d'écrire une valeur dans un registre du VDP. Cette routine nécessite d'utiliser 2 registres du Z80 : b qui doit contenir la donnée à écrire, et c le numéro du registre à modifier. Pour passer à des sprites en 16*16, je dois modifier le registre 1 du VDP, c prendra donc comme valeur 1. Le registre b du Z80 sera quant à lui mis à la valeur e2h (ou 01100010 en binaire, ce qui va nous permettre entre autres de mettre les sprites en 16*16)
- ld hl,NAMTBL
ld bc,768
xor a
call FILVRM : Appel à la routine FILVRM, qui remplit la VRAM sur une longueur d'octets donnée par le double registre bc (ici 768, la longueur de la table des noms) avec la donnée contenue dans l'accumulateur a. Comme j'utilise un xor a, j'annule la valeur de l'accumulateur. En conclusion, je mets rien dans les 768 cases de la table des noms, donc j'efface l'écran !
Je fais ensuite une petite manipulation qui me sera fort utile pour le reste du code : je copie le contenu des attributs de sprites initiaux dans la partie "variable" de mon code :
ld hl,ATR_INITIAL
ld de,SPRITES
ld bc,8
ldir
La première ligne, met dans le registre double hl l'adresse de départ.
La deuxième met dans le registre de l'adresse d'arrivée (c'est là qu'on voit toute l'utilité des labels ).
La troisième met dans le registre double bc le nombre d'octets.
La dernière fait appel à l'instruction ldir, qui permet la copie d'un bloc de RAM vers une adresse.
Enfin, je peux réactiver l'affichage, avant d'attaquer le fond du problème : le déplacement !
call ENASCR (routine BIOS qui annule le call DISSCR du début).
Il nous faut passer en SCREEN 2, indiquer qu'on utilise des sprites en 16*16, passer en COLOR 15,0,0 et enfin effacer l'écran !
Mais avant de commencer, et pour éviter des manipulations fastidieuses pour les adresses des différentes tables qui nous intéressent, on va utiliser quelques EQU !
Code ASM :
NAMTBL equ 1800h SPRTBL equ 3800h SPRATR equ 1b00h
NAMTBL pour la table des noms, SPRTBL pour la table des sprites, SPRATR pour la table des attributs de sprites. Ainsi, quand j'aurais besoin de ces tables dans mon code, j'utiliserai les noms au lieu des adresses !
Maintenant passons dans le vif du sujet !
Voici la portion de code qui va faire tout ce qu'il faut, je la détaillerai après :
Code ASM :
DEBUT: call DISSCR ld hl,0f3e9h ld [hl],15 inc hl ld [hl],0 inc hl ld [hl],0 call INIGRP ld bc,06201h call WRTVDP ld hl,NAMTBL ld bc,768 xor a call FILVRM
- DEBUT: c'est le label qui indique où mon programme va commencer, rien de bien méchant là-dedans.
- call DISSCR : premier appel à une routine BIOS ! Celle-ci a comme effet d'empêcher l'affichage pendant que je bidouille la VRAM !
- ld hl,0f3e9h : j'utilise une des adresses de la zone de communication : celle qui contient la couleur d'écriture ! Je charge cette adresse dans l'accumulateur 16 bits.
- ld [hl],15 : je place la valeur 15 à cette adresse, la couleur d'écriture passe donc au blanc.
- inc hl
ld [hl],0
inc hl
ld [hl],0 : j'incrémente la valeur de l'accumulateur, ce qui me permet de passer à la couleur de fond (que je mets à 0) en utilisant la même méthode que précédemment. J'enchaîne aussi sur la couleur de bordure.
- call INIGRP : nouvelle routine BIOS, qui me permet de passer immédiatement en SCREEN 2.
- ld bc,06201h
call WRTVDP : j'utilise la routine WRTVDP qui me permet d'écrire une valeur dans un registre du VDP. Cette routine nécessite d'utiliser 2 registres du Z80 : b qui doit contenir la donnée à écrire, et c le numéro du registre à modifier. Pour passer à des sprites en 16*16, je dois modifier le registre 1 du VDP, c prendra donc comme valeur 1. Le registre b du Z80 sera quant à lui mis à la valeur e2h (ou 01100010 en binaire, ce qui va nous permettre entre autres de mettre les sprites en 16*16)
- ld hl,NAMTBL
ld bc,768
xor a
call FILVRM : Appel à la routine FILVRM, qui remplit la VRAM sur une longueur d'octets donnée par le double registre bc (ici 768, la longueur de la table des noms) avec la donnée contenue dans l'accumulateur a. Comme j'utilise un xor a, j'annule la valeur de l'accumulateur. En conclusion, je mets rien dans les 768 cases de la table des noms, donc j'efface l'écran !
Je fais ensuite une petite manipulation qui me sera fort utile pour le reste du code : je copie le contenu des attributs de sprites initiaux dans la partie "variable" de mon code :
ld hl,ATR_INITIAL
ld de,SPRITES
ld bc,8
ldir
La première ligne, met dans le registre double hl l'adresse de départ.
La deuxième met dans le registre de l'adresse d'arrivée (c'est là qu'on voit toute l'utilité des labels ).
La troisième met dans le registre double bc le nombre d'octets.
La dernière fait appel à l'instruction ldir, qui permet la copie d'un bloc de RAM vers une adresse.
Enfin, je peux réactiver l'affichage, avant d'attaquer le fond du problème : le déplacement !
call ENASCR (routine BIOS qui annule le call DISSCR du début).
MSX un jour, MSX toujours !
3ème étape : le programme
En analysant ce que j'avais à faire, j'ai pu déterminer mon programme :
1) Préparation des attributs et formes initiaux des sprites
2) lecture du clavier : en fonction de la ou des touches enfoncées, partir vers un sous-programme qui gère le déplacement dans la direction voulue, en modifiant la forme au besoin et en testant les bords d'écran.
3) affichage du sprite ainsi modifié
4) retour au 1)
C'est bien entendu la partie 2 qui sera la plus délicate à traiter !!
Je commencerai donc par le commencement : la préparation des attributs et formes initiaux des sprites :
Comme vous l'aurez remarqué, je commence par un nouveau label LOOP. Celui-ci me sert d'indicateur pour ma boucle de programme, c'est par là que je recommencerai.
Ensuite, je mets dans le registre double hl l'adresse de mes 2 premiers patrons de sprite dits normaux. J'appelle ensuite CHOIX_SPRITE, qui n'est certes pas une routine BIOS, mais une routine que j'expliciterai ci-après et qui me permet de remplir la table des patrons de sprite avec les données qui vont bien. Les deux dernières lignes serviront à mettre le deuxième sprite à la bonne couleur.
Voici à présent la fameuse routine CHOIX_SPRITE :
Cette routine fait appel à la routine BIOS LDIRVM, qui permet la copie d'un bloc de RAM vers une position de la VRAM. Ici, hl contiendra l'adresse de début des données de patrons de sprites (SPR_NEM, SPR_UP ou SPR_DOWN), de l'adresse de la VRAM où les informations seront envoyées et enfin bc la longueur en octets du bloc à transférer. Enfin, le ret me permet de retourner au programme principal !
Nous pouvons maintenant attaquer la partie la plus complexe !
Gestion du clavier
Pour gérer le clavier, nous avons une routine BIOS fort utile qui s'appelle GTSTCK. En fonction de la valeur de l'accumulateur a (0 pour le clavier, 1 pour le joystick du port 1, 2 pour le joystick du port 2), elle renvoie le numéro de direction (voir STICK en MSX-Basic pour connaître ces valeurs) dans l'accumulateur, ce qui nous permet d'effectuer une batterie de tests ! Comme je suis parti dans une utilisation du clavier, j'ai donc la portion de code suivante :
Oui mais c'est pour quoi faire le ld b,a me demanderez-vous ? Eh bien pour tout vous dire, je pense que c'est une erreur de ma part car je croyais que l'usage d'un cp modifiait la valeur de l'accumulateur, ce qui n'est pas le cas (si je l'enlève, mon programme bugge par contre, allez comprendre ) ! Je stocke donc dans b la valeur renvoyée par GTSTCK, et j'aborde courageusement la série de tests !
En gros, je compare la valeur renvoyée par GTSTCK à chaque direction possible, et si la différence entre les deux est nulle, j'appelle la sous-routine de déplacement correspondante. Vous aurez aussi remarqué qu'avant chaque nouveau test (toujours cette erreur de ma part) je remets dans l'accumulateur la valeur initiale renvoyée par le GTSTCK !
En analysant ce que j'avais à faire, j'ai pu déterminer mon programme :
1) Préparation des attributs et formes initiaux des sprites
2) lecture du clavier : en fonction de la ou des touches enfoncées, partir vers un sous-programme qui gère le déplacement dans la direction voulue, en modifiant la forme au besoin et en testant les bords d'écran.
3) affichage du sprite ainsi modifié
4) retour au 1)
C'est bien entendu la partie 2 qui sera la plus délicate à traiter !!
Je commencerai donc par le commencement : la préparation des attributs et formes initiaux des sprites :
Code ASM :
LOOP: ld hl,SPR_NEM call CHOIX_SPRITE ld a,8 ld [SPRITES+7],a
Comme vous l'aurez remarqué, je commence par un nouveau label LOOP. Celui-ci me sert d'indicateur pour ma boucle de programme, c'est par là que je recommencerai.
Ensuite, je mets dans le registre double hl l'adresse de mes 2 premiers patrons de sprite dits normaux. J'appelle ensuite CHOIX_SPRITE, qui n'est certes pas une routine BIOS, mais une routine que j'expliciterai ci-après et qui me permet de remplir la table des patrons de sprite avec les données qui vont bien. Les deux dernières lignes serviront à mettre le deuxième sprite à la bonne couleur.
Voici à présent la fameuse routine CHOIX_SPRITE :
Code ASM :
CHOIX_SPRITE: ld de,SPRTBL ld bc,64 call LDIRVM ret
Cette routine fait appel à la routine BIOS LDIRVM, qui permet la copie d'un bloc de RAM vers une position de la VRAM. Ici, hl contiendra l'adresse de début des données de patrons de sprites (SPR_NEM, SPR_UP ou SPR_DOWN), de l'adresse de la VRAM où les informations seront envoyées et enfin bc la longueur en octets du bloc à transférer. Enfin, le ret me permet de retourner au programme principal !
Nous pouvons maintenant attaquer la partie la plus complexe !
Gestion du clavier
Pour gérer le clavier, nous avons une routine BIOS fort utile qui s'appelle GTSTCK. En fonction de la valeur de l'accumulateur a (0 pour le clavier, 1 pour le joystick du port 1, 2 pour le joystick du port 2), elle renvoie le numéro de direction (voir STICK en MSX-Basic pour connaître ces valeurs) dans l'accumulateur, ce qui nous permet d'effectuer une batterie de tests ! Comme je suis parti dans une utilisation du clavier, j'ai donc la portion de code suivante :
Code ASM :
xor a call GTSTCK ld b,a
Oui mais c'est pour quoi faire le ld b,a me demanderez-vous ? Eh bien pour tout vous dire, je pense que c'est une erreur de ma part car je croyais que l'usage d'un cp modifiait la valeur de l'accumulateur, ce qui n'est pas le cas (si je l'enlève, mon programme bugge par contre, allez comprendre ) ! Je stocke donc dans b la valeur renvoyée par GTSTCK, et j'aborde courageusement la série de tests !
Code ASM :
cp 1 CALL z,NORTH ld a,b cp 2 CALL z,NORTHEAST ld a,b cp 3 CALL z,EAST ld a,b cp 4 CALL z,SOUTHEAST ld a,b cp 5 CALL z,SOUTH ld a,b cp 6 CALL z,SOUTHWEST ld a,b cp 7 CALL z,WEST ld a,b cp 8 CALL z,NORTHWEST
En gros, je compare la valeur renvoyée par GTSTCK à chaque direction possible, et si la différence entre les deux est nulle, j'appelle la sous-routine de déplacement correspondante. Vous aurez aussi remarqué qu'avant chaque nouveau test (toujours cette erreur de ma part) je remets dans l'accumulateur la valeur initiale renvoyée par le GTSTCK !
MSX un jour, MSX toujours !
Dernière partie :
Pour ce qui est des routines de déplacement, je ne détaillerai ici que certaines d'entre elles, les autres étant calquées sur le même principe. Voici la routine qui gère le déplacement à droite :
Que fait donc cette routine ? Au départ, on va rechercher la valeur de l'abscisse dans la zone des variables et on teste si elle atteint 240 (oui parce qu'il faut compter la largeur du sprite de 16 pixels !). Le jr z,@@STOP renvoie à un sous programme qui ne fait rien si la valeur est effectivement atteinte. Si elle ne l'est pas, alors j'incrémente deux fois la valeur de l'accumulateur, puis je le remets à son emplacement d'origine. Je fais de même pour l'abscisse du deuxième sprite, et je retourne au programme principal.
Voyons à présent le déplacement vers le haut :
Le principe est comparable à la routine gérant le déplacement vers la droite, à ceci près qu'au départ, on rappelle la routine CHOIX_SPRITE en mettant cette fois dans hl l'adresse des données de patron de sprite correspondant à un déplacement vers le haut ! Ensuite on modifie la couleur du deuxième sprite (ld a,4 puis ld [SPRITES+7],a). Puis on teste le bord supérieur, etc.
On fait de même pour les autres déplacements, en notant que les déplacements en diagonale sont à la fois des déplacements dans une direction et une autre !
Il ne nous reste plus alors qu'à afficher les sprites, ce qui est fait avec la routine DUMP suivante :
Le halt du début permet de synchroniser l'affichage en fonction du balayage vertical, le reste a déjà été expliqué auparavant.
Une fois l'affichage fait, il ne reste plus qu'à boucler vers LOOP :
jp LOOP
Dans le post suivant, le listing complet, et la ROM pour voir le résultat !
Pour ce qui est des routines de déplacement, je ne détaillerai ici que certaines d'entre elles, les autres étant calquées sur le même principe. Voici la routine qui gère le déplacement à droite :
Code ASM :
EAST: ld a,[SPRITES+1] cp 240 jr z,@@STOP ld a,[SPRITES+1] inc a inc a ld [SPRITES+1],a ld a,[SPRITES+5] inc a inc a ld [SPRITES+5],a @@STOP: nop ret
Que fait donc cette routine ? Au départ, on va rechercher la valeur de l'abscisse dans la zone des variables et on teste si elle atteint 240 (oui parce qu'il faut compter la largeur du sprite de 16 pixels !). Le jr z,@@STOP renvoie à un sous programme qui ne fait rien si la valeur est effectivement atteinte. Si elle ne l'est pas, alors j'incrémente deux fois la valeur de l'accumulateur, puis je le remets à son emplacement d'origine. Je fais de même pour l'abscisse du deuxième sprite, et je retourne au programme principal.
Voyons à présent le déplacement vers le haut :
Code ASM :
NORTH: ld hl,SPR_UP call CHOIX_SPRITE ld a,4 ld [SPRITES+7],a ld a,[SPRITES] cp 0 jr z,@@STOP ld a,[SPRITES] dec a dec a ld [SPRITES],a ld a,[SPRITES+4] dec a dec a ld [SPRITES+4],a @@STOP: nop ret
Le principe est comparable à la routine gérant le déplacement vers la droite, à ceci près qu'au départ, on rappelle la routine CHOIX_SPRITE en mettant cette fois dans hl l'adresse des données de patron de sprite correspondant à un déplacement vers le haut ! Ensuite on modifie la couleur du deuxième sprite (ld a,4 puis ld [SPRITES+7],a). Puis on teste le bord supérieur, etc.
On fait de même pour les autres déplacements, en notant que les déplacements en diagonale sont à la fois des déplacements dans une direction et une autre !
Il ne nous reste plus alors qu'à afficher les sprites, ce qui est fait avec la routine DUMP suivante :
Code ASM :
DUMP: halt ld hl,SPRITES ld de,SPRATR ld bc,2*8 call LDIRVM ret
Le halt du début permet de synchroniser l'affichage en fonction du balayage vertical, le reste a déjà été expliqué auparavant.
Une fois l'affichage fait, il ne reste plus qu'à boucler vers LOOP :
jp LOOP
Dans le post suivant, le listing complet, et la ROM pour voir le résultat !
MSX un jour, MSX toujours !
Code ASM :
.bios .page 2 .rom .start DEBUT NAMTBL equ 1800h SPRTBL equ 3800h SPRATR equ 1b00h DEBUT: call DISSCR ld hl,0f3e9h ld [hl],15 inc hl ld [hl],0 inc hl ld [hl],0 call INIGRP ld bc,06201h call WRTVDP ld hl,NAMTBL ld bc,768 xor a call FILVRM ld hl,ATR_INITIAL ld de,SPRITES ld bc,8 ldir call ENASCR LOOP: ld hl,SPR_NEM call CHOIX_SPRITE ld a,8 ld [SPRITES+7],a xor a call GTSTCK ld b,a cp 1 CALL z,NORTH ld a,b cp 2 CALL z,NORTHEAST ld a,b cp 3 CALL z,EAST ld a,b cp 4 CALL z,SOUTHEAST ld a,b cp 5 CALL z,SOUTH ld a,b cp 6 CALL z,SOUTHWEST ld a,b cp 7 CALL z,WEST ld a,b cp 8 CALL z,NORTHWEST call DUMP jp LOOP EAST: ld a,[SPRITES+1] cp 240 jr z,@@STOP ld a,[SPRITES+1] inc a inc a ld [SPRITES+1],a ld a,[SPRITES+5] inc a inc a ld [SPRITES+5],a @@STOP: nop ret WEST: ld a,[SPRITES+1] cp 0 jr z,@@STOP ld a,[SPRITES+1] dec a dec a ld [SPRITES+1],a ld a,[SPRITES+5] dec a dec a ld [SPRITES+5],a @@STOP: nop ret NORTH: ld hl,SPR_UP call CHOIX_SPRITE ld a,4 ld [SPRITES+7],a ld a,[SPRITES] cp 0 jr z,@@STOP ld a,[SPRITES] dec a dec a ld [SPRITES],a ld a,[SPRITES+4] dec a dec a ld [SPRITES+4],a @@STOP: nop ret SOUTH: ld hl,SPR_DOWN call CHOIX_SPRITE ld a,[SPRITES] cp 176 jr z,@@STOP ld a,[SPRITES] inc a inc a ld [SPRITES],a ld a,[SPRITES+4] inc a inc a ld [SPRITES+4],a @@STOP: nop ret NORTHEAST: call NORTH call EAST ret NORTHWEST: call NORTH call WEST ret SOUTHEAST: call SOUTH call EAST ret SOUTHWEST: call SOUTH call WEST ret DUMP: halt ld hl,SPRITES ld de,SPRATR ld bc,2*8 call LDIRVM ret CHOIX_SPRITE: ld de,SPRTBL ld bc,64 call LDIRVM ret ATR_INITIAL: db 88,120,0,15 db 88,120,4,8 SPR_NEM: db 000h,080h,0e0h,070h,07fh,07fh,0bfh,0bfh,0bfh,078h,0e0h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,088h,0ffh,0f8h,000h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,0c0h,070h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h SPR_UP: db 000h,080h,0e0h,070h,0ffh,01fh,001h,000h,000h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,080h,0f0h,0ffh,000h,000h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,0e0h,0feh,07fh,03fh,01eh,018h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,000h,000h,0fch,0f0h,000h,000h,000h,000h,000h,000h,000h SPR_DOWN: db 000h,000h,0b0h,0fch,07fh,07fh,0bfh,0bfh,0bfh,07fh,078h,0e0h,000h,000h,000h,000h db 000h,000h,000h,000h,080h,018h,08fh,0fch,0e0h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,0e0h,070h,000h,000h,000h,000h,000h,000h,000h,000h,000h .page 3 SPRITES: ds 8
depl_sprite.zip
MSX un jour, MSX toujours !
Merci, mais je pense qu'il y a des améliorations à faire ! Comme ce problème si je vire les ld b,a et ld a,b. En principe ça ne devrait pas foirer mais ça foire, étrange...
Comme je dis, si ça peut susciter des vocations, tant mieux, après tout ça ne fait que peu de temps que je me suis mis à l'assembleur, il faut s'accrocher et faire des essais (plein ! ) et bien regarder ce qu'on peut trouver par ailleurs pour comprendre, et si possible suivre le déroulement du programme avec papier et crayon !
Mais n'hésitez pas, lancez-vous !
Comme je dis, si ça peut susciter des vocations, tant mieux, après tout ça ne fait que peu de temps que je me suis mis à l'assembleur, il faut s'accrocher et faire des essais (plein ! ) et bien regarder ce qu'on peut trouver par ailleurs pour comprendre, et si possible suivre le déroulement du programme avec papier et crayon !
Mais n'hésitez pas, lancez-vous !
MSX un jour, MSX toujours !
Argh, j'ai mis une version non corrigée !
Voici la bonne, avec le listing dans le zip (s'ouvre avec le bloc-notes ou application style Notepad++)
depl_sprite.zip
Voici la bonne, avec le listing dans le zip (s'ouvre avec le bloc-notes ou application style Notepad++)
depl_sprite.zip
MSX un jour, MSX toujours !
Voilà le genre de post qui m'intéresse
Beau boulot Granced et tu as le mérite de te lancer et de bien nous expliquer ton raisonnement pour ton programme, pour suivre il faut bien sur avoir quelques notions.
Je débute moi aussi et j'ai déjà fait un petit travail sur la base du tien, il me reste quelques trucs à voir et commenter un peu tout ça.
Il n'y a plus qu'à lancer les inscriptions pour le club des petits assemblistes en herbe!
Beau boulot Granced et tu as le mérite de te lancer et de bien nous expliquer ton raisonnement pour ton programme, pour suivre il faut bien sur avoir quelques notions.
Je débute moi aussi et j'ai déjà fait un petit travail sur la base du tien, il me reste quelques trucs à voir et commenter un peu tout ça.
Il n'y a plus qu'à lancer les inscriptions pour le club des petits assemblistes en herbe!
Le MSXien le plus à l'ouest ... ou presque
Bon, j'ai fait différemment, après reste à savoir s'il y a une méthode meilleure qu'une autre
Le programme de Granced avec mes modifs
Le fichier qui va bien et fait la même chose que celui de Granced
depspr2.zip
Le programme de Granced avec mes modifs
Code ASM :
.bios .page 2 .rom .start DEBUT NAMTBL equ 1800h SPRTBL equ 3800h SPRATR equ 1b00h DEBUT: call DISSCR ld hl,0f3e9h ld [hl],15 inc hl ld [hl],0 inc hl ld [hl],0 call INIGRP ld bc,06201h call WRTVDP ld hl,NAMTBL ld bc,768 xor a call FILVRM ld hl,ATR_INITIAL ld de,SPRITES ld bc,8 ldir ; ; Defininitions des sprites ; ld hl,SPR_DEF ;On charge en HL le début de l'endroit de la mémoire ou sont stocké les patrons des sprites ld de,SPRTBL;On indique où on veut les mettre (table des patrons des sprite à partir de 0) ld bc,6*4*8;on boucle sur 6 sprites de 4 carrés de 8 octets call LDIRVM ;on copie HL en ROM sur DE en VRAM, HL=HL+1, DE=DE+1, BC=BC-1 jusqu'à ce que BC=0. call ENASCR LOOP: xor a ; mise à 0 de l'accu (a) call CHOIX_SPRITE; mise à la valeur par défaut de l'aspect du vaisseau ; ; DEPLACEMENT ; xor a ; mise à 0 de l'accu (a) call GTSTCK; test du pad clavier (0) pushaf; Sauvegarde de a dans la pile ; ;EST ; ld HL,STK_POE; HL pointe vers les 3 positions ou le sprite va à droite (2,3,4) ld bc,3 ; BCà 3 on boucle sur 3 tours voir instruction suivante: cpir ; test de a avec l'adresse pointé par HL puis HL=HL+1 et BC=BC-1 ; jusqu'à ce que BC=0 (fin de la boucle) :-) je viens de découvrir cette instruction! JR NZ,@@WEST ; si pas d'égalité entre a et une des 3 valeurs pointé par HL on passe à la suite ; sinon on déplace le sprite ld a,[SPRITES+1] ; a=position en x du sprite 1 cp 240 ; a=240? jr z,@@NORTH ; oui donc on passe direct au déplacement vertical inc a ; sinon a=a+2 inc a ld [SPRITES+1],a ; remise de a+2 dans x du sprite1 ld [SPRITES+5],a ; puis dans x du sprite2 car il sesuivent en permanence ;-) jp @@NORTH ; puis on passe direct au déplacement vertical ; ;OUEST ; @@WEST: ld HL,STK_POW ld bc,3 cpir JR NZ,@@NORTH ld a,[SPRITES+1] cp 0 jr z,@@NORTH dec a dec a ld [SPRITES+1],a ld [SPRITES+5],a ; ;NORD ; @@NORTH: popAF ld HL,STK_PON ld bc,3 cpir JR NZ,@@SOUTH ld a,8 call CHOIX_SPRITE ld a,[SPRITES] cp 0 jr z,@@END dec a dec a ld [SPRITES],a ld [SPRITES+4],a JP @@END ; ;SUD ; @@SOUTH: ld HL,STK_POS ld bc,3 cpir JR NZ,@@END ld a,16 call CHOIX_SPRITE ld a,[SPRITES] cp 176 jr z,@@END ld a,[SPRITES] inc a inc a ld [SPRITES],a ld [SPRITES+4],a @@END: call DUMP jp LOOP nop ret DUMP: halt ld hl,SPRITES ld de,SPRATR ld bc,8 call LDIRVM ret CHOIX_SPRITE: ld [SPRITES+2],a add a,4 ld [SPRITES+6],a cp 12 jr nz,@@ROUGE ld a,4 jp @@COULEUR @@ROUGE: ld a,8 @@COULEUR: ld [SPRITES+7],a ret ATR_INITIAL: db 88,120,0,15 db 88,120,4,8 STK_POE: db 2,3,4 STK_POW: db 6,7,8 STK_PON: db 8,1,2 STK_POS: db 4,5,6 SPR_DEF: ;SPR_NEM1=sprite0 db 000h,080h,0e0h,070h,07fh,07fh,0bfh,0bfh,0bfh,078h,0e0h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,088h,0ffh,0f8h,000h,000h,000h,000h,000h,000h,000h,000h ;SPR_NEM2=sprite1*4= 4 db 000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,0c0h,070h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h ;SPR_UP1=sprite2*4= 8 db 000h,080h,0e0h,070h,0ffh,01fh,001h,000h,000h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,080h,0f0h,0ffh,000h,000h,000h,000h,000h,000h,000h,000h,000h ;SPR_UP2=sprite3*4= 12 db 000h,000h,000h,000h,000h,0e0h,0feh,07fh,03fh,01eh,018h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,000h,000h,0fch,0f0h,000h,000h,000h,000h,000h,000h,000h ;SPR_DOWN1=sprite4*4= 16 db 000h,000h,0b0h,0fch,07fh,07fh,0bfh,0bfh,0bfh,07fh,078h,0e0h,000h,000h,000h,000h db 000h,000h,000h,000h,080h,018h,08fh,0fch,0e0h,000h,000h,000h,000h,000h,000h,000h ;SPR_DOWN1=sprite5*4= 20 db 000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h,000h db 000h,000h,000h,000h,000h,0e0h,070h,000h,000h,000h,000h,000h,000h,000h,000h,000h .page 3 SPRITES: ds 8
Le fichier qui va bien et fait la même chose que celui de Granced
depspr2.zip
Le MSXien le plus à l'ouest ... ou presque
Répondre
Vous n'êtes pas autorisé à écrire dans cette catégorie