Le cour précédent nous a permis de voir les automodifications et l'utilisation des flags pour nos tests.
Le resultat si vous y êtes parvenus est un smiley qui se balade sur tout l'écran en rebondissant sur le border.
Mais nous avons ici un problème.
Notre sprite se déplace à l'octet et donc au pixel en Y, mais à l'octet en X.
Hors 1 octet en X c'est 8 pixels d'un coup...
C'est assez violent et notre déplacement n'est donc pas identique en X et en Y.
Nous allons donc améliorer cela pour passer aussi au pixel en X
Pour passer au pixel en X il n'y a pas 50 solutions, il va nous falloir notre sprite avec autant de décalage en RAM.
Petit schéma explicatif:
Comme nous devrons décaler le sprite de 8 pixels, nous allons donc déborder de notre largeur de 2 octets.
Le mieux est donc de passer directement à une largeur de 3 octets pour avoir une largeur constante et n'avoir donc qu'une seule routine d'affichage quelque soit le cas.
Nous avons maintenant deux choix:
- Dessiner toutes les étapes comme si dessus (mais sans les traits rouges de limite d'octet).
- Faire un programme qui va nous copier le sprite en décallé automatiquement.
Dessiner toutes les étapes n'est pas franchement la chose la moins rapide. On peut même dire que ca nous évite de faire une routine et de réfléchir.
Oui mais cela prend de la place inutilement et lorsque nous devrons charger notre routine avec une cassette (oui oui), cela augmentera le temps de chargement.
Nous allons donc nous faire une routine qui va recopier le sprite en décallé 8 fois en ram.
Pour commencer, reprennons notre sprite sur desormais 3 octets.
.spr DB %00000111,%11100000,0 ; line 0 DB %00011000,%00011000,0 ; line 1 DB %00100000,%00000100,0 ; line 2 DB %01000000,%00000010,0 ; line 3 DB %01000000,%00000010,0 ; line 4 DB %10000100,%00100001,0 ; line 5 DB %10000000,%00000001,0 ; line 6 DB %10000000,%00000001,0 ; line 7 DB %10000000,%00000001,0 ; line 8 DB %10000000,%00000001,0 ; line 9 DB %10001000,%00010001,0 ; line 10 DB %01000100,%00100010,0 ; line 11 DB %01000011,%11000010,0 ; line 12 DB %00100000,%00000100,0 ; line 13 DB %00011000,%00011000,0 ; line 14 DB %00000111,%11100000,0 ; line 15
Voila qui est fait.
Commençons maintenant notre routine:
LD HL,.spr ;on pointe sur le début du gfx LD DE,.spr+(16*3) ;le sprite suivant devra être 16*3 octets plus loin LD A,(HL) ;on lit le premier octet. SRL A ;décalage vers la droite avec inclusion de 0 dans le bit 7 et bit 0 mis dans la carry LD (DE),A ;on envoi le nouvel octet INC DE ;on pointe sur l'octet suivant INC HL ;on passe à l'octet suivant LD A,(HL) ;on lit le deuxième octet RR A ;on decale vers la droite en incluant en bit 7 la carry et en mettant le bit 0 ensuite dans celle-ci. LD (DE),A INC DE INC HL LD A,(HL) ;dernier octet RR A LD (DE),A INC DE INC HL
Maintenant que nous avons fait cela pour la première ligne, nous pouvons le faire pour les autres en incluant le tout dans une boucle.
Comme nous avons fait 1 ligne il faut faire 16 fois la boucle. Mais comme en plus nous avons 8 décalages a faire à partir du sprite de départ, il nous faut 16*8=128 répétitions
LD HL,.spr ;on pointe sur le début du gfx LD DE,.spr+(16*3) ;le sprite suivant devra être 16*3 octets plus loin LD B,112 ;nombre de répétitions .DecaleSpr LD A,(HL) ;on lit le premier octet. SRL A ;décalage vers la droite avec inclusion de 0 dans le bit 7 et bit 0 mis dans la carry LD (DE),A ;on envoi le nouvel octet INC DE ;on pointe sur l'octet suivant INC HL ;on passe à l'octet suivant LD A,(HL) ;on lit le deuxième octet RR A ;on decale vers la droite en incluant en bit 7 la carry et en mettant le bit 0 ensuite dans celle-ci. LD (DE),A INC DE INC HL LD A,(HL) ;dernier octet RR A LD (DE),A INC DE INC HL DJNZ .DecaleSpr
Pour que vous compreniez mieux l'utilisation des instructions de décalage que j'ai utilisé, voici un schéma global pour une ligne (3 octets) de notre sprite:
Faites attention à bien placer vos grpahismes du sprite à la toute fin du code si vous ne voulez pas écraser celui-ci avec la génération des sprites décalés
Notez aussi qu'il est toujours préférable de générer les données plutôt que de le faire à la main, pour la simple raison qu'un changement vous obligerait à tout refaire.
Ici simplement en changeant le sprite de base, tout se recréera tout seul.
Passons à la routine d'affichage maintenant car nous avons quelques changements à y apporter.
Pour commencer, remettons ici notre routine d'affichage telle que nous l'avions faite:
.POSY LD A,.YPOS CALL .CALCLGN ;DE contient l'adresse de la ligne .POSX LD A,.XPOS ADD A,E LD E,A LD HL,.Smile LD B,16 .SPRITE PUSH BC LDI LDI LDI ;Je rajoute ici un LDI DEC E DEC E DEC E ;Et un DEC E pour passer à 3 octets de large CALL .LGNINF POP BC DJNZ .SPRITE
Le calcul de X était assez simple.
Nous lisions une valeur et on l'ajoutait à l'adresse écran.
Cependant, notre routine va devoir changer.
En effet, prenons par exemple les valeur de 0 à 10...
0: on prendra le 1ier sprite et on sera en position x=0.
1: on prendra le 2ème sprite et on sera en position x=0.
2: on prendra le 3ème sprite et on sera en position x=0.
3: on prendra le 4ème sprite et on sera en position x=0.
4: on prendra le 5ème sprite et on sera en position x=0.
5: on prendra le 6ème sprite et on sera en position x=0.
6: on prendra le 7ème sprite et on sera en position x=0.
7: on prendra le 8ème sprite et on sera en position x=0.
8: on prendra le 1er sprite et on sera en position x=1.
9: on prendra le 2ème sprite et on sera en position x=1.
10: on prendra le 3ème sprite et on sera en position x=1.
Nous avons donc 8 valaurs pour lesquelles la position en X ne bouge pas et seul le sprite décalé change.
Ceci revient à dire que les valeur 0 à 7 correspondent au numéro du sprite tandis que les valeurs supérieures à 7 correspondent à la coordonnée X à ajouter à l'adresse de la ligne.
Aussi, nous allons dans un premier temps pouvoir ajouter la coordonnée X en supprimant les valeurs 0 à 7, soit les bits 0 à 2.
.POSY LD A,.YPOS CALL .CALCLGN ;DE contient l'adresse de la ligne .POSX LD A,.XPOS LD C,A ;on sauvegarde la valeur SRL A SRL A SRL A ;on enlève les 8 possibilités de sprites ADD A,E ;on ajoute X à l'adresse LD E,A
Maintenant que notre décalage en X est fait, il nous reste à choisir lequel des 8 sprites nous allons afficher.
Comme chaque valeur correspond à un sprite, il suffit donc de prendre l'adresse du premier des sprites et de lui ajouter n fois la longueur de celui-ci.
Comme notre sprite fait 16*3 octets = 48, le premier sprite est donc en 0, le deuxième en 48; le troisième en 96 etc etc.
Soit: n le numéro du sprite et son adresse est égal à:
Adresse du sprite 0+(48*n)
Refléchissais bien, faites vous un schéma si nécessaire, vous verrez que c'est logique et simple.
Nous devons donc multiplier la valeur de nos trois premier bits de la coordonnée X par la longueur d'un sprite et ajouter à l'adresse du premier de ceux-ci.
Mais comment multiplier ?
Nous avons de la chance je vous le dis directement, notre multiplication est une puissance de 2 (comme 1,2,4,8,16,32,64,128,256...) et ca va bien nous arranger.
Revoyons brièvement notre cour de CE1 (ou CE2 je ne sais plus trop à notre époque quand nous avions vu cela) sur la multiplication.
Une multiplication n'est en soit qu'une addition d'un nombre par lui même.
Ex: 2*4 donne 4+4=8 ; 64*4 c'est 64+64+64+64=256
C'est exactement ce que nous allons exploiter.
Nous devons faire une multiplication par 48.
Prenons le chiffre 1:
1+1=2 : Addition 1
2+2=4 : Addition 2
4+4=8 : Addition 3
8+8=16 : Addition 4 ... Ici nous avons donc multiplié par 16 notre valeur de départ.
Changeons notre valeur de départ et prenons par exemple 4:
4+4=8 : Addition 1
8+8=16 : Addition 2
16+16=32 : Addition 3
32+32=64 : Addition 4 ... Nous avons fait le même nombre d'addition... Hors 64/4 donne ? Eh bien 16 !!! Notre multiplication reste la même pour le même nombre d'additions.
Aussi pour multiplier par 48:
1+1=2 : *2
2+2=4 : *4
4+4=8 : *8
8+8=16 :*16
16+16=32 : *32
32+32... Zut, on dépasse notre valeur.
Restons donc à 32 qui est notre multiplication maximale avant de dépasser 48. Posons-nous la question de savoir combien il manque à 32 pour atteindre 48: 16.
Une multiplication par 48 est donc une multiplication par 32 additionnée à une multiplication par 16 !
Reprennons:
1+1 : *2
2+2 : *4
4+4 : *8
8+8 : *16 ... On stocke cette valeur puis on continue
16+16 : *32
On additionne avec le resultat de la multiplication par 16 et hop, nous avons une multiplication par 48.
Notre Z80 possède plusieurs instructions d'addition qui pour les registres 8bits s'effectuent avec A pour resultat et pour les registres 16bits avec HL.
Comme multiplier 48*8 possibilité de sprite donne 384, nous avons donc besoin d'un registre 16 bits pour calculer.
L'instruction que nous allons utiliser est: LD HL,r16 : Celle-ci additionne à HL un registre 16bits et met le resultat dans HL.
Aussi pour multiplier un nombre par lui même et faire notre multiplication, nous pourrons simplement mettre ce chiffre dans HL et faire des LD HL,HL.
Reprenons notre routine et ajoutons notre calcul. Je rappelle avant tout que nous devrons ajouter le resultat de la multiplication à l'adresse ou se trouvent nos sprites
.POSY LD A,.YPOS CALL .CALCLGN ;DE contient l'adresse de la ligne .POSX LD A,.XPOS LD C,A ;on sauvegarde la valeur SRL A SRL A SRL A ;on enlève les 8 possibilités de sprites ADD A,E ;on ajoute X à l'adresse LD E,A LD A,C ;on récupère la valeur de X AND %00000111 ;on garde les 8 possibilités de sprites LD L,A ;on copie A dans HL LD H,0 ;pensez à mettre H=0 ADD HL,HL ;*2 ADD HL,HL ;*4 ADD HL,HL ;*8 ADD HL,HL ;*16 LD C,L ;on sauvegarde le resultat de la multiplication par 16 LD B,H ;dans un registre 16bits bien evidement ADD HL,HL ;*32 ADD HL,BC ;*48 par addition de *32+*16 LD BC,.SPR ;adresse des sprites en mémoire ADD HL,BC ;addition de l'adresse des sprites avec notre valeur calculée LD B,16 .SPRITE PUSH BC LDI LDI LDI DEC E DEC E DEC E CALL .LGNINF POP BC DJNZ .SPRITE
Voilà nous avons terminé et notre sprite se déplace joyeusement.
Vous devriez obtenir quelque chose de ce style.