Sprite.

Nous avons maintenant toutes les bases et presque toutes les connaissances pour afficher un sprite à l'écran.

Dans les cours précédents, nous avons vu:
- La structure de l'écran. - Comment calculer la ligne inférieure. - Comment calculer l'adresse d'une ligne en fonction d'une coordonnée.

Il est desormais temps d'afficher quelque chose à l'écran.

Ce que nous allond afficher c'est le sprite suivant:

Smile

j'ai utilisé un petit logiciel sympa et pratique que vous trouverez facilement grâce à son nom.
Celui-ci permet entre autre d'obtenir en export votre sprite sous forme de datas binaires.

                .Smile
                                defb %00000111,%11100000 ; line 0
                                defb %00011000,%00011000 ; line 1
                                defb %00100000,%00000100 ; line 2
                                defb %01000000,%00000010 ; line 3
                                defb %01000000,%00000010 ; line 4
                                defb %10000100,%00100001 ; line 5
                                defb %10000000,%00000001 ; line 6
                                defb %10000000,%00000001 ; line 7
                                defb %10000000,%00000001 ; line 8
                                defb %10000000,%00000001 ; line 9
                                defb %10001000,%00010001 ; line 10
                                defb %01000100,%00100010 ; line 11
                                defb %01000011,%11000010 ; line 12
                                defb %00100000,%00000100 ; line 13
                                defb %00011000,%00011000 ; line 14
                                defb %00000111,%11100000 ; line 15

Nous avons donc nos datas pour les gfx du sprite. Commençons donc notre programme en plaçant celui-ci à l'écran grâce à des coordonnées X et Y.

Pour cela il nous faut determiner l'adresse écran en fonction des coordonnées, exactement ce que l'on a fait dans un des précédent cours.

                                org     &8000           ;Notre code commencera en #8000 (32768 en décimal)
                .XPOS           EQU     10              ;XPOS=10
                .YPOS           EQU     36              ;YPOS=36

                                LD      A,.YPOS         ;A contient .YPOS
                                CALL    .CALCLGN        ;On appelle la routine de calcul de l'adresse de la ligne à l'écran.

                                RET

                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .CALCLGN
                                ;calcul de l'adresse d'une ligne en fonction d'une coordonnée Y
                                LD      H,A
                                ;A contient le numéro de ligne
                                ;Résultat dans HL
                                AND     %00000111
                                LD      L,A
                                LD      A,H
                                AND     %11000000
                                RRCA
                                RRCA
                                RRCA
                                OR      L
                                OR      %01000000
                                LD      D,A
                                LD      A,H
                                AND     %00111000
                                RLCA
                                RLCA
                                LD      E,A
                                RET
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .Smile
                                defb %00000111,%11100000 ; line 0
                                defb %00011000,%00011000 ; line 1
                                defb %00100000,%00000100 ; line 2
                                defb %01000000,%00000010 ; line 3
                                defb %01000000,%00000010 ; line 4
                                defb %10000100,%00100001 ; line 5
                                defb %10000000,%00000001 ; line 6
                                defb %10000000,%00000001 ; line 7
                                defb %10000000,%00000001 ; line 8
                                defb %10000000,%00000001 ; line 9
                                defb %10001000,%00010001 ; line 10
                                defb %01000100,%00100010 ; line 11
                                defb %01000011,%11000010 ; line 12
                                defb %00100000,%00000100 ; line 13
                                defb %00011000,%00011000 ; line 14
                                defb %00000111,%11100000 ; line 15

Notre code commence donc en #8000.

Comme vous pouvez le voir en début de routine, j'utilise une instruction que vous ne connaissez pas: EQU.
EQU n'est pas une instruction du Z80. C'est une instruction de l'assembleur.
Un EQU définit une variable que vous pourrez replacer dans votre code.
Par exemple imaginons que vous vouliez modifier des couleurs mais que cela vous ennuye de devoir toujours aller regarder à quelle valeur correspond le bleu.
Vous pourriez ainsi définir:
.BLEU EQU %001
Et à chaque fois ou vous voudrez entrer la valeur du bleu, vous entrerer à la place le label .BLEU.
C'est donc simplement du remplacement.
Ici, cela me permettra par exemple de modifier rapidement la coordonnée Y en début de source plutôt que de chercher ou je l'ai utilisée au milieu de mon code.

Vous noterez justement que juste après je fais un LD A,.YPOS où .YPOS sera automatiquement remplacé à l'assemblage par 36...

L'instruction suivante est un CALL

CALL adr : appel d'une sous routine.
Lorsque le Z80 rencontrera un CALL adr, il sauvegardera l'adresse de l'instruction suivante (ici le RET) et sautera à l'adresse adr.
A cette adresse vous aurez bien entendu placé une routine qui devra OBLIGATOIREMENT se terminer par un RET.
Quand le Z80 rencontrera le RET,il récupèrera l'adresse de retour qu'il avait stocké et y sautera pour continuer à partir de celle-ci.

Dans le cas présent pour le moment, on retrounera sur l'instruction RET qui mettra fin à notre programme.

Maintenant que je vous ai parlé du CALL c'est l'occasion révée pour faire un petit écart pour vous expliquer les instructions de saut et leur fonctionnement.

LES INSTRUCTIONS DE SAUT:

Les instructions de saut vous permettront de sortir de votre routine, soit pour aller vers un autre endroit puis revenir ensuite; soit pour sortir d'une routine de façon définitive.

Les instructions de saut définitif:

Elles sont de deux sortes: les JP et les JR:

JP : JumP : Correspond à un saut définitif à une adresse. Vous n'en reviendrez pas et un RET ne vous ramènera pas là d'où vous venez.
Il n'y a pas de limite de saut, vous sautez où vous voulez, même en ROM si vous le souhaitez.

JR : Jump Relative : JR est un saut Relatif. Ceci signifie que vous sautez à une adresse en donnant une distance en octet par rapport à l'endroit ou est l'instruction.
Comme l'octet de saut est codé sur 8bits, vous pourrez sauter de -128 à 127 relativement à la position de l'instruction donc.

Toutes ces instructions possèdent des possibilités de saut conditionnels (donc avec condition).
Ainsi certains flags pourront influencer le saut.

Voici une liste de ces instructions de sauts définitifs:

JP adr : Saut définitif vers l'adresse.
JP cc,adr : Saut définitif vers l'adresse selon condition cc (Z, NZ ,C ,NC, P, PO, PE, M).
JP (HL) : Saut définitif vers l'adresse contenue dans HL.
JP (IX) : Saut définitif vers l'adresse contenue dans IX.
JP (IY) : Saut définitif vers l'adresse contenue dans IY.

JR dist : Saut définitif et relatif de la distance désignée.
JR cc,dist : Saut définitif et relatif de la distance désignée selon condition cc (seulement avec les flags C et Z).

Les instructions de saut avec retour:

La seule instruction de saut avec retour est le CALL.
Lors d'un CALL, l'adresse de l'instruction qui le suit est sauvegardée dans la Pile, puis le saut est effectué.
Lorsqu'un RET sera rencontré, le Z80 récupèrera l'adresse sauvegardée dans la Pile puis y sautera.
Attention donc à ce que vous faites avec la pile si vous ne voulez pas détruire le retour du CALL.

Voici une liste des différentes instructions CALL:

CALL adr : Saut avec retour vers l'adresse.
CALL cc,adr : Saut avec retour vers l'adresse selon condition cc (toutes les conditions sont possibles).

Maintenant que nous avons vu les instructions de saut, j'ai encore quelques petites choses à vous apprendre avant de pouvoir faire une routine d'affichage de sprite.

Cependant, nous pouvons déjà ajouter le X à notre adresse de ligne.

Comme notre coordonnée X peut être simplement ajoutée à l'adresse de début de notre ligne, nous pouvons donc simplement additionner cette valeur à l'adresse de la ligne.

                                org     &8000           ;Notre code commencera en #8000 (32768 en décimal)
                .XPOS           EQU     10              ;XPOS=10
                .YPOS           EQU     36              ;YPOS=36

                                LD      A,.YPOS         ;A contient .YPOS
                                CALL    .CALCLGN        ;On appelle la routine de calcul de l'adresse de la ligne à l'écran.
                                LD      A,.XPOS
                                ADD     A,E             ;on ajoute X à l'adresse
                                LD      E,A             ;on remet dans E



                                RET

                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .CALCLGN
                                ;calcul de l'adresse d'une ligne en fonction d'une coordonnée Y
                                LD      H,A
                                ;A contient le numéro de ligne
                                ;Résultat dans HL
                                AND     %00000111
                                LD      L,A
                                LD      A,H
                                AND     %11000000
                                RRCA
                                RRCA
                                RRCA
                                OR      L
                                OR      %01000000
                                LD      D,A
                                LD      A,H
                                AND     %00111000
                                RLCA
                                RLCA
                                LD      E,A
                                RET
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .Smile
                                defb %00000111,%11100000 ; line 0
                                defb %00011000,%00011000 ; line 1
                                defb %00100000,%00000100 ; line 2
                                defb %01000000,%00000010 ; line 3
                                defb %01000000,%00000010 ; line 4
                                defb %10000100,%00100001 ; line 5
                                defb %10000000,%00000001 ; line 6
                                defb %10000000,%00000001 ; line 7
                                defb %10000000,%00000001 ; line 8
                                defb %10000000,%00000001 ; line 9
                                defb %10001000,%00010001 ; line 10
                                defb %01000100,%00100010 ; line 11
                                defb %01000011,%11000010 ; line 12
                                defb %00100000,%00000100 ; line 13
                                defb %00011000,%00011000 ; line 14
                                defb %00000111,%11100000 ; line 15

J'ai surtout besoin de vous apprendre l'instruction LDI et LDIR (c'est presque la même chose.).

Les instructions de copie:

LDI : LDI est une instruction de copie.
Cette instruction vous permettra de copier un octet d'une case mémoire à une autre.
LDI copie l'octet à l'adresse (HL) à l'adresse (DE), puis incrémente HL et DE et décrémente BC.
Moyen mémotechnique pour retenir, pensez DE comme DEstination ;).

LDIR : LDIR est une instruction de copie de chaine d'octet.
Cette instruction permet de copier plusieurs octets d'une adresse vers une autre.
LDIR copie de BC octets de l'adresse (HL) à l'adresse (DE).
BC contient donc le nombre d'octets à copier; HL l'origine et DE la Destination.

Voilà, il me reste à vous expliquer encore la pile mais on va déjà afficher notre première ligne avant d'aller plus loin.

Puisque nous avons calculé l'adresse ou afficher notre sprite, que nous avons les données graphiques de celui-ci, nous allons pouvoir utiliser les instruction de copie pour envoyer les gfx dans la RAM écran.

Notre superbe smile fait 16 pixels de large par 16 de haut.
Comme il faut compter en largeur 1 octet pour 8 pixels, nous avons donc 2 octets de large pour 16 lignes de haut (une ligne faisant 1 pixel de haut bien entendu).
Pour afficher la première ligne de notre gfx nous pourrons donc faire deux LDI.
LDI étant plus rapide que LDIR, il est préférable de l'utiliser quand il n'y a pas 50 octets à bouger.

Reprenons notre programme pour afficher notre première ligne:

                                org     &8000           ;Notre code commencera en #8000 (32768 en décimal)
                .XPOS           EQU     10              ;XPOS=10
                .YPOS           EQU     36              ;YPOS=36

                                LD      A,.YPOS         ;A contient .YPOS
                                CALL    .CALCLGN        ;On appelle la routine de calcul de l'adresse de la ligne à l'écran.
                                LD      A,.XPOS
                                ADD     A,E             ;on ajoute X à l'adresse
                                LD      E,A             ;on remet dans E

                                ;DE contient la destination

                                LD      HL,.Smile       ;HL contient l'adresse du gfx
                                LDI                     ;on envoie le premier octet
                                LDI                     ;on envoie le second

                                RET

                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .CALCLGN
                                ;calcul de l'adresse d'une ligne en fonction d'une coordonnée Y
                                LD      H,A
                                ;A contient le numéro de ligne
                                ;Résultat dans HL
                                AND     %00000111
                                LD      L,A
                                LD      A,H
                                AND     %11000000
                                RRCA
                                RRCA
                                RRCA
                                OR      L
                                OR      %01000000
                                LD      D,A
                                LD      A,H
                                AND     %00111000
                                RLCA
                                RLCA
                                LD      E,A
                                RET
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .Smile
                                defb %00000111,%11100000 ; line 0
                                defb %00011000,%00011000 ; line 1
                                defb %00100000,%00000100 ; line 2
                                defb %01000000,%00000010 ; line 3
                                defb %01000000,%00000010 ; line 4
                                defb %10000100,%00100001 ; line 5
                                defb %10000000,%00000001 ; line 6
                                defb %10000000,%00000001 ; line 7
                                defb %10000000,%00000001 ; line 8
                                defb %10000000,%00000001 ; line 9
                                defb %10001000,%00010001 ; line 10
                                defb %01000100,%00100010 ; line 11
                                defb %01000011,%11000010 ; line 12
                                defb %00100000,%00000100 ; line 13
                                defb %00011000,%00011000 ; line 14
                                defb %00000111,%11100000 ; line 15

Il nous faut maintenant trouver une solution pour afficher la ligne suivante.

Déjà, nous savons calculer la ligne inférieure puisque nous l'avons fait dans les cours précédents.
Intégrons donc cette sous routine (par la suite je ne recopierai pas les sous routines et garderai juste le source principal pour gagner en lisibilité.)

Attention cependant: les deux LDI ont incrémenté 2 fois DE. Si nous calculons maintenant la ligne inférieure, nous obtiendrons l'adresse de la ligne en dessous de la fin de celle que nous venons d'afficher !!!
La solution simple est de décrémenter deux fois E après les LDI.

                                org     &8000           ;Notre code commencera en #8000 (32768 en décimal)
                .XPOS           EQU     10              ;XPOS=10
                .YPOS           EQU     36              ;YPOS=36

                                LD      A,.YPOS         ;A contient .YPOS
                                CALL    .CALCLGN        ;On appelle la routine de calcul de l'adresse de la ligne à l'écran.
                                LD      A,.XPOS
                                ADD     A,E             ;on ajoute X à l'adresse
                                LD      E,A             ;on remet dans E

                                ;DE contient la destination

                                LD      HL,.Smile       ;HL contient l'adresse du gfx
                                LDI                     ;on envoie le premier octet
                                LDI                     ;on envoie le second
                                DEC     E               ;on décrémente E
                                DEC     E               ;une deuxième fois pour se trouver en début de sprite
                                CALL    .LGNINF         ;on saute vers la routine de calcul de la ligne inférieure

                                RET

                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .CALCLGN
                                ;calcul de l'adresse d'une ligne en fonction d'une coordonnée Y
                                LD      H,A
                                ;A contient le numéro de ligne
                                ;Résultat dans HL
                                AND     %00000111
                                LD      L,A
                                LD      A,H
                                AND     %11000000
                                RRCA
                                RRCA
                                RRCA
                                OR      L
                                OR      %01000000
                                LD      D,A
                                LD      A,H
                                AND     %00111000
                                RLCA
                                RLCA
                                LD      E,A
                                RET
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .Smile
                                defb %00000111,%11100000 ; line 0
                                defb %00011000,%00011000 ; line 1
                                defb %00100000,%00000100 ; line 2
                                defb %01000000,%00000010 ; line 3
                                defb %01000000,%00000010 ; line 4
                                defb %10000100,%00100001 ; line 5
                                defb %10000000,%00000001 ; line 6
                                defb %10000000,%00000001 ; line 7
                                defb %10000000,%00000001 ; line 8
                                defb %10000000,%00000001 ; line 9
                                defb %10001000,%00010001 ; line 10
                                defb %01000100,%00100010 ; line 11
                                defb %01000011,%11000010 ; line 12
                                defb %00100000,%00000100 ; line 13
                                defb %00011000,%00011000 ; line 14
                                defb %00000111,%11100000 ; line 15
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                ;**********************************************************************
                .LGNINF
                ;DE contient l'adresse de la ligne
                                INC D           
                                LD  A,D         
                                AND %00000111   
                                RET NZ          
                                LD  A,E         
                                ADD A,32        
                                LD  E,A         
                                RET C           
                                LD  A,D         
                                SUB 8           
                                LD  D,A         
                                RET

On a donc ajouté le calcul de l'adresse de la ligne inférieure...

Je rappelle au passage que HL a été incrémenté avec chaque LDI.
Après les deux LDI, nous sommes donc déjà sur les gfx de la deuxième ligne des gfx.

Il nous reste à faire une boucle pour afficher les 16 lignes de notre gfx.

Pour cela, nous allons simplement utiliser DJNZ adr qui comme nous l'avons vu précédement est bien pratique lorsqu'il s'agit de faire une boucle.
DJNZ utilisant B comme boucle, nous allons devoir sauvegarder celui-ci avant les LDI qui eux l'incrémentent.
C'est donc le moment tant attendu de vous expliquer le fonctionnement de la légendaire Pile !!!

La Pile:

Imaginez prendre des assiettes.
Vous en posez une. C'est la première.
Puis vous en posez une autre. C'est la deuxième.
Vous en posez encore une: la troisième.

Maintenant récupérez les assiettes les unes après les autres.

La première que vous récuperez est la dernière que vous avez posé. C'était la troisième.
La deuxième était aussi la deuxième, mais la dernière que vous récupérerez était la première posée.

Le fonctionnement de la pile c'est exactement cela.
La première valeur que vous y sauvegarderez sera la dernière que vous récupèrerez.
Gardez donc toujours à l'esprit cette histoire de pile d'assiette qui décrit parfaitement le fonctionnement de la pile.

La pile permet donc de sauvegarder le contenu des registres 16bits et uniquement 16 bits. Il vous est impossible de sauvegarder un registre 8 bit sans son associé.
Vous pourrez donc sauvegarder AF ; HL; DE; BC; IX et IY.

Les instructions de gestion de la pile sont les suivantes:

PUSH r16 : Sauvegarde la valeur d'un registre 16 bits dans la pile.
POP r16 : Récupère la valeur d'un registre 16 bits dans la pile.

Lorsque vous sauvegardez une valeur avec PUSH r16, vous donnez le registre 16bits à sauvegarder.
En revanche et c'est important, quand vous faites un POP r16, r16 indique dans quel registre 16bits vous voulez récupérer la dernière valeur sauvegardée.

Ce que cela implique c'est que si vous faites par exemple un PUSH HL et que vous faites ensuite un POP DE, c'est bien dans DE que vous récupèrerez la dernière valeur même si c'était la valeur de HL quand ca a été sauvegardé

Quelques petites précisions:

La pile est logée en RAM. Cela signifie que ce n'est pas une hypothétique sauvegarde interne au Z80.
Quand vous sauvegardez une valeur celle-ci est écrite en ram.
Vous vous demandez certainement où et je vous réponds: à l'adresse pointée par SP.

SP est un registre 16bits dédié à la pile (hors de question de l'utiliser donc pour autre chose).
Ce registre donne donc l'adresse de la pile.

Quand vous sauvegardez une valeur, SP est décrémenté. Le poids faible est lû, SP est a nouveau décrémenté et le poids fort est lû.
Quand vous récupérez une valeur dans la pile, le poids fort est récupéré puis SP est incrémenté, puis le poids faible est récupéré et SP est incrémenté.

Attention, les instructions de sauts avec retour (CALL) sauvegardent l'adresse de retour dans la pile.

Nous avons enfin un moyen de sauvegarder nos valeurs.

Intégrons maintenant notre boucle DJNZ. Nous allons donc sauvegarder BC avant les LDI et le récupérer avant le DJNZ adr.

Attention à l'érreur du débutant: on initialise le compteur de boucle B AVANT la boucle ;)

                                org     &8000           ;Notre code commencera en #8000 (32768 en décimal)
                .XPOS           EQU     10              ;XPOS=10
                .YPOS           EQU     36              ;YPOS=36

                                LD      A,.YPOS         ;A contient .YPOS
                                CALL    .CALCLGN        ;On appelle la routine de calcul de l'adresse de la ligne à l'écran.
                                LD      A,.XPOS
                                ADD     A,E             ;on ajoute X à l'adresse
                                LD      E,A             ;on remet dans E

                                ;DE contient la destination

                                LD      HL,.Smile       ;HL contient l'adresse du gfx
                                LD      B,16            ;on initialise le compteur de boucle à 16 pour 16 lignes
                .SPRITE
                                PUSH    BC              ;on sauvegarde B car il sera modifié par les LDI
                                LDI                     ;on envoie le premier octet
                                LDI                     ;on envoie le second
                                DEC     E               ;on décrémente E
                                DEC     E               ;une deuxième fois pour se trouver en début de sprite
                                CALL    .LGNINF         ;on saute vers la routine de calcul de la ligne inférieure
                                POP     BC              ;on récupère notre compteur de boucle
                                DJNZ    .SPRITE         ;on boucle sur .SPRITE

                                RET

Et voilà notre routine est terminée. Ce n'était franchement pas compliqué.

Notez que c'est améliorable. S'il existe les instructions LDI et LDIR il existe aussi les instructions LDD et LDDR qui permettent de faire des copies "vers la gauche"

A vous de tester. Cela vous permettra de supprimer les deux DEC E après les LDI.

X

CONNEXION




Inscription