New item

Livres

Communications, Modem et ...

Communications, Modem et ...

by Alexandre UNGERER, SCHULWITZ, SEVERIN

Des idées pour les CPC

Des idées pour les CPC

by Bernd KOWAL

3D et Vrai Relief

3D et Vrai Relief

by J.J.MEYER

Le Language Machine pour ...

Le Language Machine pour ...

by Hardy STRASSENBURG, Holger DULLIN


Le Live du CP/M

Le Live du CP/M

by Lothar Schüssler

Apprendre à programmer e...

Apprendre à programmer e...

by Claude DELANNOY

La Bible du CPC 6128

La Bible du CPC 6128

by Bruckmann, English, Gerits, Steigers



Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; JCommentsACL has a deprecated constructor in /home/c1154036c/public_html/components/com_jcomments/classes/acl.php on line 17

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; JCommentsPagination has a deprecated constructor in /home/c1154036c/public_html/components/com_jcomments/helpers/pagination.php on line 18
Etoiles inactivesEtoiles inactivesEtoiles inactivesEtoiles inactivesEtoiles inactives
 

Nous savons maintenant afficher des sprites. C'est bien mais bon... A partir de maintenant on va essayer d'appliquer un minimum à travers des exercices. Seulement les exercices c'est chiant... Alors pourquoi ne pas simplement faire un jeu ??? Pour ce jeu nous allons simplement faire quelque chose d'assez simple : un jeu de plateformes sans scrolling.

Avant de commencer un projet il faut déjà le définir. Voici donc ce que nous voulons faire :

-C'est un jeu de plateforme écran par écran.
-Nous dirigeons un personnage qui peu marcher; sauter et tomber.
-Si on touche un bord de l'écran alors on change d'écran.
-Il y aura des ennemis de plusieurs types au parcours définis.
La première chose à faire avant de commencer à déplacer un sprite dans notre jeu c'est déjà d'apprendre à afficher le décors. Pour notre jeu ce sera assez simple puisqu'on travaille écran par écran sans scrolling. En gros, dès qu'on sort de l'écran on en affiche un autre. Le principe va être très simple. En mémoire nous allons stocker des sprites de notre décors. Comme il s'agit de décors, nous appellerons ces sprites des TILES. Retenez bien le mot car j'vais bien en abuser. Ces tiles vont donc se suivre en mémoire. En gros ca ressemble à un truc comme ça: (La ce sont ceux de Navy Seals mais bon on s'en fout c'est pour l'exemple)
tiles
Admettons que l'on charge nos images en #4000 (ce qui n'est pas totalement con non plus). Le premier tile sera donc en #4000; le suivant en #4000+(largeur*hauteur); le suivant en #4000+(largeur*hauteur)+(largeur*hauteur)...
Ce qui revient à la même chose que ce que vous avez déjà fait pour l'affichage de texte (voir 2.4 - Lire un texte et trouver la lettre dans la fonte) Jusque la ça doit aller normalement. Rien de bien compliqué. Et pour le coup la suite est évidente. Ce que l'on veut faire c'est lire une table avec les numéros des tiles pour ensuite afficher à l'écran dans le même ordre. Notre table peut être de cet ordre :
00,01,02,03,04,01,06,08,07,05,03,01,02,05,09,16,25,14,20,29
21,22,23,24,00,06,09,07,10,16,02,18,30,20,16,18,04,16,06,18
00,01,02,03,04,01,06,08,07,05,03,01,02,05,09,16,25,14,20,29
21,22,23,24,00,06,09,07,10,16,02,18,30,20,16,18,04,16,06,18
00,01,02,03,04,01,06,08,07,05,03,01,02,05,09,16,25,14,20,29
21,22,23,24,00,06,09,07,10,16,02,18,30,20,16,18,04,16,06,18
00,01,02,03,04,01,06,08,07,05,03,01,02,05,09,16,25,14,20,29
21,22,23,24,00,06,09,07,10,16,02,18,30,20,16,18,04,16,06,18
00,01,02,03,04,01,06,08,07,05,03,01,02,05,09,16,25,14,20,29
21,22,23,24,00,06,09,07,10,16,02,18,30,20,16,18,04,16,06,18
00,01,02,03,04,01,06,08,07,05,03,01,02,05,09,16,25,14,20,29
21,22,23,24,00,06,09,07,10,16,02,18,30,20,16,18,04,16,06,18
Voyons donc comment nous allons nous démerder. Il va falloir dans un premier temps stocker notre tableau dans la RAM (ou la ROM si vous voulez, c'est votre problème, pas le miens. Alors me prenez pas la tête avec ça hein !!!) Pour cela nous utiliserons l'instruction ASM (donc c'est une instruction pour l'assembleur mais n'est pas codé en RAM on est bien d'accord): DB (ce qui signifie Data Byte)
exemple :
Cuisine    DB      00,01,02,03,04,01,06,08,07,05,03,01,02,05,09,16,25,14,20,29 
Bien entendu vous me collez ça dans votre code à un endroit ou ça ne sera pas exécuté (le premier qui vient chialer pour un plantage alors qu'il m'a collé ça dans sa boucle principale je lui envoie en recommandé la dernière couche usagée de ma fille !!!). Pour afficher notre pièce il va falloir du coup : Afficher la première ligne de Tiles. Arrivé à la fin de celle-ci passer à la suivante à l'écran. Plusieurs techniques peuvent donner le même résultat. Au choix on peut afficher une ligne et une fois arrivé au bout calculer l'adresse de la ligne ecran suivante pour la deuxième ligne de tiles. Ou alors avoir une autre table avec les adresses de chaque ligne de tiles. Prenons un écran en #C000. Si nous affichons 20 tiles de 8*16 octets, nous aurons une ligne complète (en mode 0).
ORG #adresse_que_vous_voulez
  LD    HL,table_cuisine
  LD    DE,#C000
  PUSH  DE
  PUSH  HL                    ;on sauvegarde nos registres car ils vont être modifiés
  CALL  LECTURE_CUISINE       ;ici on calcul l'adresse du TILE dans la RAM
                              ;HL contient l'adresse du tile à afficher
  CALL  AFFICHE_TILE          ;on affiche le premier TILE
  POP   HL
  POP   DE                    ;on récupère nos registres
  INC   HL                    ;si notre table fait moins de 256 octet un INC L suffira
  INC   DE
  INC   DE
  INC   DE
  INC   DE                    ;4 octets de large pour notre TILE 

 

Bon jusque la ca va on calcule l'adresse du tile en RAM et on l'affiche à l'écran. En sortie l'adresse ou est affiché notre tile est INC. Si on veut afficher 20 TILE de large suffit d'ajouter un compteur. Une boucle DJNZ fera l'affaire
ORG #adresse_que_vous_voulez
  LD    HL,table_cuisine
  LD    DE,#C000
    LD    B,20                  ;notre compteur de nombre de TILES
  LIGNE
  PUSH  BC                    ;on le sauvegarde pour le cas ou les sous-routines s'en
                              ;serviraient. Si ce n'est pas le cas alors passez vous de la sauvegarde
  PUSH  DE
  PUSH  HL                    ;on sauvegarde nos registres car ils vont être modifiés
  CALL  LECTURE_CUISINE       ;ici on calcul l'adresse du TILE dans la RAM
                              ;HL contient l'adresse du tile à afficher
  CALL  AFFICHE_TILE          ;on affiche le premier TILE
  POP   HL
  POP   DE                    ;on récupère nos registres
  INC   HL                    ;si notre table fait moins de 256 octet un INC L suffira
  INC   DE
  INC   DE
  INC   DE
  INC   DE                    ;4 octets de large pour notre TILE 
  POP   BC
  DJNZ  LIGNE 

 

          
Nous avons donc affiché une ligne complète de TILES. Notre adresse écran ayant été incrémenté, nous nous retrouvons donc en #c000+80 (20*4 octets de tiles en largeur), ce qui nous met sur le deuxième bloc de ligne de l'écran. Hors nos TILES faisant 16 pixels de haut, il nous faut être 8 lignes plus bas. Soit 80 octets plus loin. Rien de plus facile, il suffit d'ajouter 80 octets à l'adresse écran... Notre adresse écran étant dans DE on la repasse dans HL (sans l'écraser vu qu'il contient l'adresse du prochain tile)
EX    HL,DE         ;on échange le contenu de HL et DE
LD    BC,80
ADD   HL,BC         ;on ajoute 80
EX    HL,DE         ;on remet les valeurs dans les bons registres

 

           
Bon, ca c'est bon. Reste plus qu'à donner le nombre de lignes de Tiles à l'écran. 12 le rempliront (il restera 8 lignes en bas). Pour cela on ajoute un autre compteur. Pourquoi pas dans A ?
ORG #adresse_que_vous_voulez
  LD    HL,table_cuisine
  LD    DE,#C000
  LD    A,12                   ;12 lignes de tiles
AFFICHE_ECRAN
 PUSH  AF                     ;on sauvegarde au cas ou (la encore si A n'est pas modifié
                               ;dans vos sous-routines passez vous de la sauvegarde)
  LD    B,20                   ;notre compteur de nombre de TILES
LIGNE
  PUSH  BC                     ;on le sauvegarde pour le cas ou les sous-routines s'en serviraient.
                               ;Si ce n'est pas le cas alors passez vous de la sauvegarde
  PUSH  DE
  PUSH  HL                     ;on sauvegarde nos registres car ils vont être modifiés
  CALL  LECTURE_CUISINE        ;ici on calcul l'adresse du TILE dans la RAM
                               ;HL contient l'adresse du tile à afficher
  CALL AFFICHE_TILE            ;on affiche le premier TILE
  POP  HL
  POP  DE                      ;on récupère nos registres
  INC  HL                      ;si notre table fait moins de 256 octet un INC L suffira
  INC  DE
  INC  DE
  INC  DE
  INC  DE                      ;4 octets de large pour notre TILE
  POP  BC
  DJNZ LIGNE
  EX   HL,DE                   ;on échange le contenu de HL et DE
  LD   BC,80
  ADD  HL,BC                   ;on ajoute 80
  EX   HL,DE                   ;on remet les valeurs dans les bons registres
  POP  AF                      ;on récupère le nombre de lignes
  DEC  A                       ;on décrémente
  OR   A                       ;équivalent d'un CP 0 mais en plus rapide (réfléchissez à
                               ;ce que fait un OR A vous comprendrez que le flag Z est modifié si A=0)
  JP   NZ,AFFICHE_ECRAN        ;si c'est pas =0 alors on boucle.
         
Voila mon p'tit gars, t'as un super décors maintenant :D
Bon... On a notre décors. C'est cool. Après tous ces cours et exercices on arrive enfin à quelque chose. Vous savez déjà afficher un sprite masqué. Alors allez-y affichez votre perso !!! Bon on va quand même parler un peu de pratique. Parce que bon afficher un perso et le déplacer ca ne se fait pas n'importe comment.
4.3.1 - Un jeu c'est quoi ?
Un jeu c'est une boucle. Une grosse boucle dans laquelle se passe des évènements. Notre boucle consistera en plusieurs choses :
-Les éléments permanents (jouer la musique en est un ; tester le joystick peut aussi l'être (ou pas))
-Les éléments temporaires (un ennemis qu'on peut tuer)
Nous allons donc déjà réfléchir à ce qu'on va faire. Pour notre exemple, on va considérer un jeu à 50Hz (ce qui ne sera pas le cas pour tous les jeux). Notre boucle commencera par un test VBL (la encore ce n'est pas obligatoire). Notre boucle se terminera par un test de touche vers le joystick avant de ... bein.. Boucler !!! Mais avant d'entrer dans la boucle il faudra (pour notre jeu à écran fixe) afficher notre décors; notre HUD et tout le bordel qui va avec. Notre boucle elle ne s'occupe que de la gestion du jeu en lui même.
4.3.2 - Boucle principale
On commence donc par notre test VBL (si vous ne vous rappelez pas ce que c'est retournez lire ici: 2.2 - Synchronisation avec le moniteur)
BOUCLE    LD   B,#F5
VBL       IN   A,(C):RRA:JR NC,VBL
Voila on a notre début. On ajoute notre fin, a savoir notre test de touche (la vais pas me faire chier on mettra juste un call touche)
JOY 
CALL    TOUCHE            ;vous avez vu, je vous l'avais dit !!!
CP      VAL_JOY_GAUCHE
JP      Z,JOY GAUCHE
             ...          ;vous ajoutez vos tests joy ou clavier à la con ici quoi
JP      BOUCLE            ;voila la boucle est bouclée
Bon c'est pas mal mais on n'a toujours pas notre perso qui bouge... On va commencer par insérer de la place dans notre boucle... J'vous dit pas de suite pourquoi mais je vous l'explique juste après.
BOUCLE   LD B,#F5
VBL      IN A,(C):RRA:JR NC,VBL
HEROCL   DS 3,0
EVEMT2   DS 3,0
EVEMT3   DS 3,0
EVEMT4   DS 3,0
EVEMT5   DS 3,0
EVEMT6   DS 3,0
CALL     TOUCHE            ;vous avez vu, je vous l'avais dit !!!
CP       VAL_JOY_GAUCHE
JP       Z,JOY GAUCHE
             ...           ;vous ajoutez vos tests joy ou clavier à la con ici quoi
JP       BOUCLE            ;voila la boucle est bouclée 
Voila... Vous avez donc plusieurs labels EVEMT avec un numéro derrière. J'ai choisi EVEMT pour "évènement". HEROCL sera reservé au hero. Ça sera plus clair. Pour rappel, DS 3,0 met 3 instruction NOP (oppcode 0). Alors que je vous explique :
A ces évènements on va poker des CALL ADR (donc 3 octets) en fonction de ce qui se passe. Par exemple un ennemis qui se déplace sera dans une sous routine qu'on appellera par un CALL placé dans un de nos emplacement réservé. Mais surtout notre personnage sera lui aussi placé la dedans. Mais pas toujours !!! Effectivement, si notre personnage ne se déplace pas on va pas gaspiller du temps à l'afficher en continu. On ne l'affichera que si une direction du joy a été appuyée et après avoir vérifié que notre perso peut effectivement se déplacer (s'il est contre un mur on laisse tomber).Notre routine de personnage principal pourra ressemble à ça :
HERO  CALL CAPTURE_FOND    ;si votre perso ne se déplace pas sur un fond uni il faudra capturer le fond
CALL  AFFICHE_HERO         ;on affiche le sprite
XOR   A                    ;A=0 ... Plus rapide qu'un LD A,0
LD    (HEROCL),A           ;on efface le call puisque l'affichage est terminé
LD    (HEROCL+1),A
LD    (HEROCL+2),A         ;voila c'est fait, le saut est bousillé
RET                        ;fin de la routine d'affichage du perso 
Bien entendu la c'est pour un affichage du perso non animé. Pour l'animation on verra ca dans un chapitre après...
4.3.3 - Capture et restitution du fond (La parenthèse pour ZISQUIER).
CAPTURE_FOND correspond à votre routine de capture du fond. Qu'est-ce que c'est ??? Simple : Vous avez un décors de fond. Vous afficher par dessus un sprite (masqué ou non). Quand vous aller vouloir déplacer votre sprite, il faudra réafficher ce qu'il y avait dessous !!! Sinon vous aurez toujours d'affiché votre sprite sur le fond et quand vous décalerez votre sprite cela vous en fera 2... Et si vous répetez l'opération x fois vous obtenez ça (screenshot du jeu que Zisquier est en train de faire grâce aux cours. Suis fier de toi Zis !!!)
zisquier trace blinky
Avant d'afficher le moindre sprite il faudra donc recopier en RAM le décors présent à l'endroit ou vous voulez le mettre. Si votre sprite fais 8*32 octets alors il faudra recopier en RAM un carré de 8*32 octets du décors. C'est assez simple en fait puisque c'est l'inverse d'une routine d'affichage en quelque sorte... Gardons nos dimension de 8*32 octets. Notre tampon ou nous recopierons ce carré de décors on l'appelera TAMPON_CAPTURE
  LD HL,adr_sprite_a_l_ecran    ;l'adresse ou on affichera le sprite à l'écran.
                                ;la on la met pour l'exemple mais il va de soit que dans votre jeu ce sera donné.
  LD DE,TAMPON_CAPTURE          ;on enverra ça dans le tampon
  LD B,32                       ;32 lignes de haut
BOUCLE_CAPTURE
  PUSH BC
  PUSH HL                       ;il sera modifié par le LDIR
  LD   BC,8                     ;8 octets de large
  LDIR                          ;on envois la purée
  POP  HL                       ;on récupère notre adresse écran
  CALL CALCUL_LIGNE_INFERIEURE  ;ca vous savez déjà faire. Ici il faudra que ca fonctionne avec HL
  POP  BC                       ;on récupère le nombre de ligne
  DJNZ BOUCLE_CAPTURE           ;Et on boucle 
                   
C'était pas bien compliqué non ? Juste une copie de l'écran vers la RAM. La restitution elle n'est qu'un affichage de sprite de TAMPON_CAPTURE vers l'écran...
4.3.4 - Fin de la parenthèse, retournons à nos mout...Sprites.
Bon donc remettons ça en forme tout en sachant qu'avant d'entrer dans la boucle de notre jeu (et donc de la pièce en court) il faut :
-Initialiser l'affichage de notre perso pour qu'il soit à l'écran dès le début (sinon il n'y sera qu'à l'appuie d'une direction...)
-Afficher la pièce
ORG #8000
NOLIST                    ;sous winape ou maxam cette instruction de l'assembleur évitera
                                                        ; de faire défiler le source à l'assemblage
                     
  CALL AFFICHE_PIECE      ;affiche la pièce de votre château hanté
                          ;(Mais non Zisquier j'écris pas ça pour toi ;) )
  LD   A,#CD              ;l'opcode #CD correspond à l'instruction CALL
  LD   (HEROCL),A         ;on poke CALL
  LD   HL,HERO            ;adresse de la routine d'affichage du hero
  LD   (HEROCL+1),HL      ;en HEROCL on a donc maintenant un CALL HERO

BOUCLE
  LD   B,#F5
  VBL  IN A,(C):RRA:JR NC,VBL
  HEROCL DS 3,0
  EVEMT2 DS 3,0
  EVEMT3 DS 3,0
  EVEMT4 DS 3,0
  EVEMT5 DS 3,0
  EVEMT6 DS 3,0

JOY
  CALL   TOUCHE           ;vous avez vu, je vous l'avais dit !!!
  CP     VAL_JOY_GAUCHE
  JP     Z,JOY GAUCHE
             ...          ;vous ajoutez vos tests joy ou clavier à la con ici quoi
  JP     BOUCLE           ;voila la boucle est bouclée 
;**********************************************************************************
;**********************************************************************************
;**********************************************************************************
;*****************************    SOUS ROUTINES    ********************************
;*********************************             ************************************
;**********************************************************************************
HERO
  CALL  CAPTURE_FOND      ;si votre perso ne se déplace pas sur un fond uni,
                          ;il faudra capturer le fond
  CALL  AFFICHE_HERO      ;on affiche le sprite
  XOR   A                 ;A=0 ... Plus rapide qu'un LD A,0
  LD    (HEROCL),A        ;on efface le call puisque l'affichage est terminé
  LD    (HEROCL+1),A
  LD    (HEROCL+2),A      ;voila c'est fait, le saut est bousillé
  RET                     ;fin de la routine d'affichage du perso
                     
Bon c'est pas mal, notre personnage est donc affiché à la première exécution et la routine ne s’exécute qu'une fois. Mais !!! Mais me direz-vous !!! (Si si essayez vous allez voir ça marche). Il est mis ou à l'écran notre personnage ? Bein woai bande de couillons faudrait peut-être y penser !!! Pour cela on va travailler en coordonnées. On va pas se faire chier, placer le perso au pixel n'a ici aucun interet vu que de toute façon il lui faudra avoir les pieds au sol. Nous allons donc passer en bloc de ligne (8 lignes pixels). Le principe est le suivant :
-On a une coordonnée Y
-On a une table avec les adresses de début de ligne
-On additionne la coordonnée Y avec l'adresse de notre table
-On récupère l'adresse de la ligne.
Dejà on va générer notre table. Notre écran restant en #C000 (on pourrait changer son adresse mais pour notre cas on s'en tape.), la ligne suivante est donc en #C000+80octets (#50). On mettra notre table en #1000 (faut bien la mettre quelque part de toute façon. Mettez la ou vous voulez, même dans votre code si ca vous chante en reservant l'espace avec un DS)
CALC_TABLE_ADR
  LD B,20             ;nombre de ligne
  LD HL,#C000         ;adr de la première ligne écran
  LD DE,#1000         ;adr ou sera la table
BOUCLE_TABLE
  PUSH BC             ;on sauvegarde notre nombre de lignes
  LD   BC,#50         ;longueur d'une ligne
  LD   A,L:LD (DE),A  ;poids faible de l'adresse copié dans la table
  INC  DE             ;on incrémente l'adresse de la table
  LD   A,H:LD (DE),A  ;poids fort de l'adresse copié dans la table
  INC  DE             ;on incrémente l'adresse de la table
  ADD  HL,BC          ;adresse ligne=adresse ligne+80 octets
  POP  BC             ;on récupère le nombre de lignes
  DJNZ BOUCLE_TABLE 
Voila... On obtient donc en #1000 une table contenant les adresses de chaque ligne. (#C000,#C050,...) Pour un jeu écran par écran ou chaque écran est un niveau à part entière ca sera bien pratique car en plus des données de l'écran on pourra ajouter au début ou à la fin ou ailleurs la coordonnée de départ du joueur... Pour un jeu de plateforme ou on passe d'un écran à l'autre ca sera différent puisqu'une fois passé à droite on revient à gauche sans avoir réellement besoin de replacer le joueur via des coordonnées (donc pour toi Zisquier t'as pas vraiment besoin de ça en fait... Mais ca te servira quand même pour le placement des ennemis)
Et oui cette table pourra aussi pour servir pour placer les ennemis; plateformes mouvantes ou autres décors animés... Bref, notre table est crée, voyons comment la lire. Nous avons pour notre élément à afficher (hero; énemis etc) une coordonnée Y (en bloc de ligne) et une X (à l'octet ou au word ou à ce que vous voulez ca changera pas grand chose. Nous on ne fera au word (2 octets) soit :
POSY_PERSO  EQU 5
POSX_PERSO  EQU 10 
Bien entendu la c'est pour l'exemple mais dans le cas d'un jeu vous avez tout intérêt à avoir les coordonnées de départ stockées quelque part en RAM(ou ROM). Plaçons donc notre sprite :
CALC_ADR_ECRAN_SPRITE
 LD  A,POSY               ;on commencera toujours par le Y
 ADD A,A                  ;Notre table contenant des adresses sur 2 octets on multiplie par 2 notre valeur
 LD  L,A:LD H,0           ;on recopie dans HL
 LD  DE,TABLE_ADR_ECRAN   ;notre table d'adresse
 ADD HL,DE                ;on additionne l'adresse de notre table avec la coordonnée Y(*2)
                          ;HL contient l'adresse dans la table ou se trouve l'adresse de notre ligne
 LD  E,(HL):INC HL        ;on récupère le poids faible
 LD  D,(HL)               ;on récupère le poids fort
                          ;DE contient l'adresse de la ligne
 LD  A,POSX               ;on prend la coordonnée X
 ADD A,A                  ;comme on se place au word on multiplie par 2
 LD  L,A:LD H,0           ;on copie dans HL
                          ;HL contient la coordonnée X
 ADD HL,DE                ;on additionne l'adresse de la ligne avec le X
                          ;HL contient l'adresse ou afficher notre élément
 RET
                                     
Pour notre héro et dans le cas ou celui-ci est dans un jeu ou on change d'écran, mais ou un nouvel écran n'est pas un nouveau niveau (donc ton cas Zis), cette routine ne servira que pour initialiser les éléments. Ainsi l'adresse calculée pourra ensuite être pokée en RAM ou la routine d'affichage la récupèrera pour ensuite gérer l'adresse elle même. Ainsi au tout début du jeu on pourra initialiser le hero et sauvegarder l'adresse d'affichage en RAM à un label ADR_ECR_HERO. On réservera cette adresse avec un ADR_ECR_HERO DS 2,0 (on réserve 2 octets puisque c'est une adresse qu'on va y stocker). On pourra donc au tout début du code faire :
CALL CALC_ADR_ECRAN_SPRITE      ;HL contient l'adresse ecran
LD (ADR_ECR_HERO),HL
Et la routine d'affichage pour le héro ira récupérer l'adresse dans ADR_ECR_HERO :)
4.3.3 - Tu vas bouger oui connard ???
Woai oh c'est bon là !!! Z'allez pas me faire chier non !!! Suis bénévole moi m'sieur !!! BÉNÉVOLE !!! On en est ou ?
-A l'écran, la pièce composée de tiles est affichée.
-Notre perso est désormais à l'écran.
-Le test de touche est en place
Revenons donc à nos moutons: Quand nous appelons la routine d'affichage celle-ci affiche le sprite puis s'enlève des évenements en court. Premier soucis: notre routine gère des coordonnées POSX et POSY. Comme on vient de le dire on va plutôt aller lire l'adresse en ADR_ECR_HERO? Modifions donc notre routine pour le hero :
HERO
  CALL CAPTURE_FOND        ;si votre perso ne se déplace pas sur un fond uni il faudra capturer le fond
  LD   DE,(ADR_ECR_HERO)   ;Voila, on lit l'adresse
  CALL AFFICHE_HERO        ;on affiche le sprite
  XOR  A                   ;A=0 ... Plus rapide qu'un LD A,0
  LD   (HEROCL),A          ;on efface le call puisque l'affichage est terminé
  LD   (HEROCL+1),A
  LD   (HEROCL+2),A        ;voila c'est fait, le saut est bousillé
  RET                      ;fin de la routine d'affichage du perso

Bon ca c'est bien mais ca ne décale pas notre perso. Il ne se déplace toujours pas. Revenons à notre test de touche:
JOY
  CALL   TOUCHE           ;vous avez vu, je vous l'avais dit !!!
  CP     VAL_JOY_GAUCHE
  JP     Z,JOY GAUCHE
             ...          ;vous ajoutez vos tests joy ou clavier à la con ici quoi
  JP     BOUCLE           ;voila la boucle est bouclée

On va donc créer notre label JOY_GAUCHE en dehors de la boucle principale et travailler la dedans. Le but va être d'une part :
-Vérifier si on peut bouger => si non alors on abandonne et on retourne d'ou on vient (la aussi il y aura des choses à dire en fonction de ce qu'on veut)
-Restituer le fond (si besoin)
-Modifier l'adresse d'affichage
-lancer la routine d'affichage
Pour la vérification de collision on verra ça plus tard. Car ça va nous demander du temps et on n'en a pas besoin tout de suite. Restituer le fond c'est simple, vous l'avez capturé avec votre CALL CAPTURE_FOND, donc logiquement vous avez stocké quelque part en RAM ce qu'il y avait à l'endroit ou vous avez affiché votre perso avant de l'afficher. Donc RESTITUE_FOND c'est juste un affichage de sprite à l'endroit ou est encore votre perso (on verra peut être plus tard qu'on peut gagner du temps et optimiser la capture et l'affichage en affichant des colonnes... Si j'oublie vous me le rappellerez ;)) Aller hop on y va :
JOY_GAUCHE
 CALL COLLISION_GAUCHE  ;test de collision avec le décors à gauche on verra ça plus tard
 JP   Z,BOUCLE       ;dans le cas d'une collision et dans le cas présent on ne testera
                 
                ;aucune touche et on revient au début de la boucle.
             ;a noter qu'on pourrait très bien revenir après le test de la touche
         ;gauche pour tester d'autres touches (par exemple)
      ;Si on arrive la alors c'est qu'il n'y a pas de collision et qu'on peut se déplacer.
   ;on va donc modifier l'adresse d'affichage
;on va vers la gauche donc il faut décrémenter l'adresse
 LD   HL,(ADR_ECR_HERO)       ;on récupère l'adresse actuelle du hero
 DEC  HL                      ;on décrémente d'un octet
 LD   (ADR_ECR_HERO),HL       ;on modifie l'adresse
                              ;Ca c'est fait, on peut donc ajouter l'évènement
 LD   A,#CD                   ;#CD est l'oppcode de CALL
 LD   HL,HERO                 ;adresse de la routine d'affichage du perso
 LD   (HEROCL),A              ;on poke le CALL
 LD   (HEROCL+1),HL           ;on poke l'adr de la routine
                              ;voila c'est fini :)
 JP    BOUCLE                 ;la encore, si on veut tester autre chose on fera notre jump ailleurs. 
Notre perso va maintenant à gauche :) Pour la droite c'est la même chose mais en incrémentant l'adresse. On verra plus tard pour le saut.
Votre sprite clignote... Triste triste mon p'tit scarabée !!! Normal, celui ci se bouffe le balayage !!! Mais pas d'inquiétude il y a une solution. Et pour cela on va utiliser les interruptions :) Bon, on va commencer par de la théorie... Nous allons parler un peu des interruptions et ce pour plusieurs raisons :
-Elles vont permettre de nous caler pour faire des rasters à des endroits "précis" de l'écran
-Elles vont permettre de nous caler pour changer de mode à des endroits "précis" de l'écran
-Elles vont nous permettre d'éviter de se prendre le balayage en plein milieu d'un sprite.
-Elles nous permettront aussi de faire tourner la musique toute seule comme une grande par exemple.
Et plein d'autres choses mais ça c'est déjà pas mal :)
4.4.1 On se mange le balayage chef.
Pourquoi est-ce que moi la je vous parle de ça ?  Eh bien simplement parce qu'il va se poser un problème : Celui de se manger le balayage. En effet déplacer un sprite c'est bien, mais il est possible que entre le temps ou vous commencez à afficher votre hero et le temps ou votre routine termine de l'afficher, le balayage de votre moniteur soit déjà arrivé plus loin. Votre sprite apparait donc comme décalé horizontalement (vu que vous le déplacez horizontalement). C'est moche. Ou bien celui-ci n'est pas affiché à temps et entre la restitution du fond et l'affichage de sprite, ça clignote...
se manger le balayage1
se manger le balayage2
A cela plusieurs solutions. L'une d'elle dont nous parlerons plus tard consiste à travailler sur deux écrans. Pendant que l'un est à l'image, vous affichez sur l'autre. Une fois fini d'afficher, vous basculez vers l'autre écran et hop on n'y voit que du feu. C'est une excellente technique mais qui vous coute le double de RAM que l'écran fait de taille.
Ce que nous allons faire ici c'est d'essayer tant que possible de rester à 50Hz sans se manger le balayage. Pour cela, nous allons choisir quand nos différentes routines seront exécutées afin justement de finir l'affichage avant que le balayage ne commence à balayer la zone sur le moniteur. Mais cela va imposer plusieurs choses :
-Connaitre le temps pris par nos routines afin de ne pas dépasser le temps alloué.
-Maitriser les interruptions pour se caler dessus.
Comme je suis particulièrement pénible, on va voir deux façons de faire. La première consistera juste à faire des HALT pour se caler dessus. La deuxième consistera à placer nos évènements sous interruption carrément :D (Woai ça se fait pas, mais nous on va le faire) Enfin quand on aura vu ça, on verra justement comment travailler sur deux écrans ce qui nous permettra entre autre d'avoir des jeux tournants à moins de 50Hz.
4.4.2 - Les interruptions qu'est-ce que c'est ?
Nous n'allons parler pour le moment que du mode IM1 (mode par défaut du Z80A. A savoir qu'il existe aussi l'IM2 et dans une moindre mesure l'IM0. IM signifiant "Interruption Mode". En IM 1 donc le Z80A provoquera (c'est pas tout a fait le cas mais on verra ça une autre fois puisque les int sont générées par le GA) 300 fois par seconde une interruption. 300 fois par seconde cela nous fait 6 fois par écran. Soit tous les 6.5 blocs de lignes environ. Pour vous donner une idée, sur l'image suivante la couleur du border est changée à chaque interruption. Pour abréger je dirais désormais INT.
int
A chaque INT, notre Z80A saute directement en #38. Peu importe que vous soyez en train d'afficher un sprite; jouer une musique ou autre: il s'en tape complet, il saute en #38. L'adresse courante avant le saut (donc contenue dans PC) est sauvegardée dans la pile et le Z80A passe en DI (DI=Disable Interruptions=couper les interruptions).
En temps normal, en #38 on trouve un JP vers une adresse du system. Là le cpc gère tout un tas de chose comme les couleurs ou le clavier par exemple.
Nous ça va nous emmerder pour la simple raison que cela lui prend du temps et par conséquent ca nous en vole aussi... La solution c'est de dégager ce JP en le remplaçant par EI:RET
Pourquoi pas juste RET ? Parce qu'on veut quand même rétablir les INT. Même s'il ne s'y passera plus rien... Hormis un EI:RET
Ainsi quand l'INT aura lieue, le Z80 sautera en #0038 exécutera EI:RET et retournera d’où il venait. L’intérêt de garder les int actives c'est qu'on peut se caler dessus avec l'instruction HALT. Ainsi si le Z80 rencontre un HALT, il ne fera plus rien tant qu'il n'y aura pas de INT (en gros il attend). Ajoutez donc dès le début de votre code:
KILLSYS
  LD  HL,#C9FB
  LD  (#38),HL 
Comme vous le voyez sur l'écran au dessus, on peut donc se caler à 6 endroits (le premier étant tout en haut pendant la VBL). Et comme on est calé on peut aussi décider à un des HALT de changer les couleurs; changer de mode ou autre :) C'est pas le bonheur ? Aller on va s'occuper de placer nos routines d'affichage dans un premier temps.
4.4.3 - Ne pas se bouffer le balayage:
Ne pas se bouffer le balayage c'est simple. Du moins avec des routines rapides. Si vos routines sont trop lentes et bouffent tout le temps machine alors il faudra changer de technique... Trop de sprites à changer dans la même frame et hop ca sera pareil: pas assez de temps machine (TM) et paf vous vous bouffez le balayage. Vous avez donc tout intérêt à optimiser (faire des routines plus rapides) au maximum. Pour mesurer le temps de votre routine rien de tel qu'un bon raster (changement de couleur pendant le balayage... on voit cela très bientôt). Ajoutez donc en début de routine :
LD BC,#7F10:OUT (C),C:LD C,76:OUT (C),C
Et en fin de routine:
LD BC,#7F10:OUT (C),C:LD C,84:OUT (C),C
                    
Vous verrez visuellement le temps pris par votre routine qui sera rouge... Petit exemple avec le jeu de Zisquier ou j'ai un peu optimisé son affichage de sprite dans l'après midi (ne faites pas attention au décalage de début de routine qui est normal, ne vous occupez que de la longueur de la bande rouge)
optimisation
A gauche avant optimisation. A droite après... Comme quoi ca vaut le coup de se cramer les neurones !!! Depuis Zisquier a bien entendu optimisé ses routines de son coté et est arrivé à gagner beaucoup de TM. Pour commencer on va numéroter nos zones d'int (sachant que l'int est au début de chaque changement de couleur)
int numero
Petite parenthèse :
L'int ne dure pas tout le temps représenté par une couleur. Chaque couleur représente le temps entre deux int !!!
L'int en elle même dure quelques NOPs (4) puisque nous avons placé un EI:RET en #38.
Notre routine de sprite optimisée fait donc maintenant moins d'1 Halt (temps pris entre deux int: donc en gros sur le schéma une couleur entière). C'est parfait, il suffit donc de la mettre en dehors de la zone de jeu !!! Donc soit au 1er HALT; Soit au 6... Mais attention, il n'y a pas que l'affichage du sprite... Il y a aussi la restitution du fond !!! Si le sprite est affiché en 1 mais que la restitution du fond a lieue en 2 alors vous ne verrez tout simplement jamais votre sprite à partir du moment ou il bouge ... A vous de réfléchir à la chose ;) Comment faire ? En plaçant vos évènements entre les halt.
4.4.4 - La technique numéro 1.
Aller, petit schéma :
BOUCLE FRAME
       Tempo
       HALT
       HALT
       HALT
       HALT
       HALT
       Teste_touche
       JP  BOUCLE 
Voila c'est pas plus compliqué que ça... Quelques petites explications... J'ai mis "Tempo" juste après le test de FRAME. Pour la simple et bonne raison que la première int ayant lieue en même temps que la VBL, vous risquez de la louper... Plutôt que de prendre le risque autant en être certain et la louper à chaque fois. De toute façon vous avez la VBL pour vous caller... Donc la tempo consiste juste à ajouter après le test VBL de quoi être certain de passer la première int.
LD B,32:DJNZ $
Hop c'est bon. Notez le $. Ceci signifie "adresse actuelle". En gros ca saute sur place. Maintenant à vous d'ajouter vos routines entre les halt :) (attention si votre routine fait plus d'un halt alors vous sauterez ce halt...)
Vos routines sont trop lentes. Il faut impérativement régler le problème si vous ne voulez pas perdre du TM et avoir un jeu qui RAM à mort.
4.4.1.1 - Entrelacer les données:
Vous avez sans doute fait d'un coté une routine pour masquer et une autre pour afficher... Soit une routine qui fait un AND avec le masque sur l'écran et une autre qui fait un OR sur l'écran avec les gfx. La première optimisation consistera à mixer les deux dans la même routine. Pour cela le mieux est d'entrelacer masque et sprite. Le masque servant en premier ce sera donc le premier octet. Voila ce à quoi devra ressembler vos données entrelacées:
sprite entrelacé
Votre routine d'affichage sera donc plus simple. Si DE est la destination et HL l'adresse du sprite alors :
LD A,(DE):AND (HL):INC HL:OR (HL):INC HL:LD (DE),A Et le tour est joué pour 1 octet
4.4.1.2 - Suppression des boucles et quelques astuces:
Aussi, si votre sprite fait moins de 256 octets de long, placez le à une adresse dont le poids fort=#00 et remplacez les INC HL par des INC L (Le poids fort ne risquant pas d'être incrémenté). Un INC sur un registre 8 bit prend deux fois moins de temps que sur un registre 16 bits... Supprimez les boucles. Les boucles prennent du temps à être gérées. Rien qu'un DJNZ prend 4 NOPS. 4 NOPS*le nombre de fois ou cela boucle... Ca fait vite beaucoup. Mieux vaut recopier plusieurs fois une routine que de faire une boucle.
Preférez aussi LDI à LDIR. Par exemple si vous envoyez un sprite de 10 octets de large avec LDIR, remplacez par LDI:LDI:LDI:LDI:LDI:LDI:LDI:LDI:LDI:LDI
Un LDIR prend 6NOPS*BC(sauf quand BC arrive à 0 dans quel cas il prend 5NOPS) alors qu'un LDI en prend 5. 
Dans notre exemple, avec LDIR : (6*9)+5=59 NOPS. Avec LDI: 50 NOPS. C'est peut être pas grand chose mais multiplié par les 32 lignes de votre sprite, vous économisez 288 NOPS !!!
Passons A un truc tip top mais qui prends de la place: Le sprite généré...
4.4.1.3 - Sprites générés:
Jusque la nous avons toujours stocké les données des sprites d'un coté (avec le masque entrelacé pour le coup) que nous sommes allé chercher en RAM pour ensuite les envoyer à l'écran. Mais il y a plus rapide : Avoir les données directement dans le code. Imaginez : En temps normal pour prendre un octet de gfx d'un sprite et l'envoyer à l'écran on fait :
LD  DE,adresse_ecran        ;3 NOPS
LD  HL,adresse_gfx_sprite   ;3 NOPS
LDI                         ;5 NOPS
Eh bien moi je vous propose de faire ca:
LD HL,adresse_ecran          ;3 NOPS
LD (HL),data                 ;3 NOPS
  
Voila c'est tout... Mais "data" c'est quoi ? Eh bien tout simplement le premier octet de votre sprite :) Un sprite généré c'est ça. On ne va rien chercher en RAM, tout est déjà dans le code. En temps machine c'est extrêmement rapide. Mais pour chaque octet à envoyer il faudra cette même routine d'envois. Bien évidement on ne va pas s'emmerder à entrer à la main chaque octet de notre sprite. Nous allons donc faire du code généré. Le code généré c'est simplement un programme qui va en écrire un autre. Notre programme va lire les octets de notre sprite et écrire en RAM ailleurs le code nécessaire pour l'afficher. Vous allez comprendre...
Notre routine d'origine d'affichage de sprite masqué fait ça : (DE contient l'adresse de l'écran et HL l'adresse du sprite)
LD   A,(DE)   ;2NOPS
AND  (HL)     ;2NOPS
INC  HL       ;2NOPS
OR   (HL)     ;2NOPS
INC  HL       ;2NOPS
LD   (DE),A   ;2NOPS
INC  DE       ;2NOPS
Soit un total de 14NOPS. Nous allons la remplacer par ça :
(HL contient l'adresse de l'écran)
LD    A,(HL)    ;2NOPS
AND   data      ;2NOPS
OR    data      ;2NOPS
LD    (HL),A    ;2NOPS
INC   HL        ;2NOPS
                   
Soit un total de 10NOPS. 4 NOPS d'économisé par octet donc... Pas grand chose me direz-vous ?
Sauf que notre sprite fait 32 octets de large par 32 lignes de haut...
   Routine 1  Routine 2
 32 octets de large  32*14=448 NOPS  32*10=320 NOPS
 32 lignes de haut  448*32=14336 NOPS  320*32=10240 NOPS
 CPU en ligne pixel  14336/64 NOPS=224 Lignes écran  10240/64 NOPS=160 Lignes écran
On y gagne quand même beaucoup !!! Notre sprite fera 8*28 octets. (Tiens c'est marrant Zis c'est la taille de ton fantôme :D )
Il faudra quand même penser à modifier le calcul de la ligne inférieur de l'écran pour qu'il fonctionne avec HL. A noter que l'autre avantage est qu'on n'utilise que le registre HL... Cela nous permettra une optimisation justement dans notre routine de la ligne inférieure ou l'on donnait la valeur d'addition dans BC à chaque fois qu'on se servait de la routine de calcul... La on pourra la donner dès le début du code d'affichage et ne plus jamais la redonner. On économise ainsi quelques nops si l'écran fait plus de 16 lignes de haut.
Tant qu'on y est nous allons aussi supprimer la sauvegarde de l'adresse écran à la pile que l'on utilisait avant de faire un LDIR. En effet puisqu'on a encore un registre 16 bits de disponible, autant faire une soustraction de la largeur du sprite pour retrouver l'adresse d'origine. Et cela tombe bien on y gagne aussi puisque 1 PUSH+1 POP=3 NOPS que l'on va remplacer par un SBC HL,BC qui lui n'en fait que 4 :) Soit encore 2 NOPS d'économisés par ligne. *28 lignes ca nous fera 56 Nops de gagnés
Voyons maintenant comment générer le code.
Nous allons stocker le code généré en #4000 (au pif). Mettons déjà cela dans HL. Le masque entrelacé avec le gfx du sprite est en #1000, mettons cela dans DE. Notre sprite fait 28 lignes de haut, mettons ca dans B, on se fera une boucle avec DJNZ.
LD  HL,#4000     ;destination du code du sprite
LD  DE,sprite    ;données du sprite
LD  B,28         ;hauteur du sprite
                             
Notre sprite fait 8 octets de large, on va pas s'emmerde non plus, on va aussi faire une boucle DJNZ pour ça. Donc il nous faut sauvegarder le premier B de la hauteur.
PUSH BC
LD   B,8   ;largeur
                             
Voila c'est bien, on va commencer le code pour une ligne ici. Le principe est simple: on va utiliser les opcodes des instructions pour aller les écrire petit à petit à la destination en incorporant dedans nos données de sprite. Dors et déjà je vous donne les opcodes des instructions qu'on va utiliser :
 Instruction  Opcode
 LD A,(HL)  #7E
 AND data  #E6,data
 OR data  #F6,data
 LD (HL),A  #77
 INC HL  #23
 SBC HL,BC  #ED,#42
Reprenons notre code et commençons la génération :

  LD    HL,#4000             ;destination du code du sprite
  LD    DE,sprite            ;données du sprite
  LD    B,28                 ;hauteur du sprite
  PUSH  BC
  LD    B,8                  ;largeur
Boucle_largeur
  LD   (HL),#7E:INC HL       ;LD A,(HL)
  LD    A,(DE):INC DE        ;on lit l'octet de masque
  LD   (HL),#E6:INC HL
  LD   (HL),A:INC HL         ;AND mask
  LD    A,(DE):INC DE        ;on lit l'octet gfx
  LD   (HL),#F6:INC HL
  LD   (HL),A:INC HL         ;OR gfx
  LD   (HL),#77:INC HL       ;LD (HL),A
  LD   (HL),#23:INC HL       ;INC HL
  DJNZ Boucle_largeur
;Une fois cela exécuté nous aurons donc à partir de #4000 notre code généré:
LD A,(HL):AND mask:OR gfx:LD (HL),A:INC HL
LD A,(HL):AND mask:OR gfx:LD (HL),A:INC HL
LD A,(HL):AND mask:OR gfx:LD (HL),A:INC HL
LD A,(HL):AND mask:OR gfx:LD (HL),A:INC HL
LD A,(HL):AND mask:OR gfx:LD (HL),A:INC HL
LD A,(HL):AND mask:OR gfx:LD (HL),A:INC HL
LD A,(HL):AND mask:OR gfx:LD (HL),A:INC HL
LD A,(HL):AND mask:OR gfx:LD (HL),A:INC HL
C'est pas merveilleux ? Nous avons donc généré le code pour une ligne de notre sprite. Déjà on remarque que le dernier INC HL ne sert à rien. Autant le dégager. Pour cela on va juste reculer notre pointeur. De cette façon l'instruction sera écrasée par la suivante.
DEC   HL        ;le dernier inc hl ne sert à rien, on l'écrasera.
Maintenant il nous faut calculer la ligne inférieure. Nous allons donc refaire cette routine pour qu'elle fonctionne avec HL (vu que c'est ce reg qu'on utilise dans notre code de sprite)
LIGNEINF_hero
 LD    A,H
 ADD   A,#08
 LD    H,A
 RET   NC
 ADD   HL,DE
 RET 
A noter que je ne donne pas DE=#C050 puisque le registre n'est pas utilisé dans notre routine, on le donnera avant. Voila pour ça. notre routine de calcul de ligne inf sera mise plus loin dans le code mais je l'ai mise car on va en avoir besoin pour faire un CALL. Et il nous faut donc une adresse. A noter qu'on aurait la encore pu améliorer le truc en n'appelant pas cette routine par un CALL mais en l'incluant à chaque fin de ligne dans notre code moyennant quelques modifs telle que dégager le RET NC et le remplacer par un JR... Bref, nous on fera un CALL. Reprenons notre code :
  LD    HL,#4000             ;destination du code du sprite
  LD    DE,sprite            ;données du sprite
  LD    B,28                 ;hauteur du sprite
GEN_SPR 
  PUSH  BC
  LD    B,8                  ;largeur
Boucle_largeur
  LD   (HL),#7E:INC HL       ;LD A,(HL)
  LD    A,(DE):INC DE        ;on lit l'octet de masque
  LD   (HL),#E6:INC HL
  LD   (HL),A:INC HL         ;AND mask
  LD    A,(DE):INC DE        ;on lit l'octet gfx
  LD   (HL),#F6:INC HL
  LD   (HL),A:INC HL         ;OR gfx
  LD   (HL),#77:INC HL       ;LD (HL),A
  LD   (HL),#23:INC HL       ;INC HL
  DJNZ Boucle_largeur
                             ;fin d'une ligne
  DEC   HL                   ;le dernier inc hl ne sert à rien, on l'écrasera
  LD   (HL),#ED:INC HL
  LD   (HL),#42:INC HL       ;SBC HL,BC ceci afin de supprimer le push pop
  LD   (HL),#CD:INC HL       ;CALL adr
  LD   BC,LIGNEINF_hero      ;on recupere l'adr
  LD   A,C:LD (HL),A:INC HL  ;poke poids faible
  LD   A,B:LD (HL),A:INC HL  ;poke poids fort
  POP  BC                    ;on récupère la hauteur de notre sprite
  DJNZ GEN_SPR               ;et on boucle pour générer les nouvelles lignes
                  
Et voila c'est fini :) il reste juste à terminer la routine avec un ret. Mais avant on va dégager le dernier SBC HL,BC:CALL lugneinf_hero qui ne sert à rien vu qu'on a terminé... Pour ça bien entendu on decremente HL et on place notre RET
 LD    HL,#4000             ;destination du code du sprite
  LD    DE,sprite            ;données du sprite
  LD    B,28                 ;hauteur du sprite
GEN_SPR 
  PUSH  BC
  LD    B,8                  ;largeur
Boucle_largeur
  LD   (HL),#7E:INC HL       ;LD A,(HL)
  LD    A,(DE):INC DE        ;on lit l'octet de masque
  LD   (HL),#E6:INC HL
  LD   (HL),A:INC HL         ;AND mask
  LD    A,(DE):INC DE        ;on lit l'octet gfx
  LD   (HL),#F6:INC HL
  LD   (HL),A:INC HL         ;OR gfx
  LD   (HL),#77:INC HL       ;LD (HL),A
  LD   (HL),#23:INC HL       ;INC HL
  DJNZ Boucle_largeur
                             ;fin d'une ligne
  DEC   HL                   ;le dernier inc hl ne sert à rien, on l'écrasera
  LD   (HL),#ED:INC HL
  LD   (HL),#42:INC HL       ;SBC HL,BC ceci afin de supprimer le push pop
  LD   (HL),#CD:INC HL       ;CALL adr
  LD   BC,LIGNEINF_hero      ;on recupere l'adr
  LD   A,C:LD (HL),A:INC HL  ;poke poids faible
  LD   A,B:LD (HL),A:INC HL  ;poke poids fort
  POP  BC                    ;on récupère la hauteur de notre sprite
  DJNZ GEN_SPR               ;et on boucle pour générer les nouvelles lignes
  DEC  HL
  DEC  HL
  DEC  HL
  DEC  HL
  DEC  HL
  LD   (HL),#C9              ;RET
                             
A noter qu'on pourrait encore optimiser en supprimant le masquage des octet plein. C'est très simple puisqu'il suffit de lire l'octet du masque et passer le AND et OR si c'est=0, on y gagnerait beaucoup !!!
4.4.1.4 - Affichage à la pile:
J'ai presque faillit oublier cette technique bien pratique et bien rapide.
Vous savez maintenant placer vos routines entre les interruptions. Mais pourquoi ne pas placer vos routines SOUS interruption ??? De ce fait si vos routines sont placées dans la bonne interruption alors elle s’exécuteront toutes seuls quand l'int arrivera. Le principe est simple :
-On sait qu'à chaque int, le Z80 saute en #38
-On sait qu'il y a 6 int par frame
Il suffit donc de savoir dans quelle int nous sommes (1 à 5) et en fonction de sauter à une sous routine (ou pas). Pour commencer on va couper les int pour ne pas qu'elles nous emmerde et on va initialiser notre compteur d'int ainsi que le saut vers notre routine détournée.
ORG #8000
 DI                            ;on coupe les int il n'y en aura plus
 LD  B,#F5
FRM  IN A,(C):RRA:JR NC,FRM    ;on teste la VBL car la première int a lieue en même temps
                    
Le problème... Quand on teste le signal VBL rien ne nous dis qu'il n'était pas déjà en court. Dans quel cas, on est peut être pas à son début mais un peu plus loin. C'est un soucis car la première int est déclenchée au début de la VBL. Du coup selon le moment ou on teste le signal, on choppera soit la première int; soit la deuxième. La solution est simple : il suffit de passer le premier signal; d'attendre un peu et de re-tester.  (on pourrait aussi tester l'absence de signal VBL...). La deuxième fois on sera forcément au début !!!
ORG #8000
 DI                                  ;on coupe les int il n'y en aura plus
 LD    B,#F5
FRM    IN A,(C):RRA:JR NC,FRM        ;on teste la VBL car la première int a lieue en même temps
 HALT
 HALT
 HALT                                ;la on est certain d'avoir passé la vbl
FRM2   IN A,(C):RRA:JR NC,FRM2       ;deuxième test... La c'est certain nous sommes au début de la VBL
 LD    A,6:LD (INT_N),A
                                     ;notre compteur est initialise a 6
                                    ;On va maintenant mettre en #38 un saut vers notre routine
                                     ;de gestion des int
 LD   A,#C3:LD (#38),A               ;#C3=JP
 LD   HL,INT:LD (#39),HL             ;en #38 nous avons donc JP INT
                                     ;une fois fait on remet les interruptions
 EI
                       
Reste à créer notre routine ... On va donc à chaque int regarder la valeur de notre compteur. Comme j'ai choisi de décrémenter ce compteur à chaque int, la première int est donc la 6.
INT LD A,(INT_N)
    CP 1:JP Z,INT1
    CP 2:JP Z,INT2
    CP 3:JP Z,INT3
    CP 4:JP Z,INT4
    CP 5:JP Z,INT5
    JP INT6        ;il n'en reste qu'une c'est forcement la 6
      ;voila... En fonction de la valeur on saute plus loin. A noter qu'on aurai
      ;t aussi faire des JP NZ et mettre notre routine juste à la suite... 
      ;personnellement je trouve cela plus clair comme ça.
      ;Tant qu'on y est on va créer notre routine de décrémentation du compteur
      ;il faudra l’exécuter à chaque fin de int.
INT_END
    LD A,(INT_N)
    DEC A
    OR A
    JP NZ,INT_FIN        ;On va maintenant decrementer le numero d'int
                         ;si c'est =0 alors on boucle a 6
    LD A,6

INT_FIN
    LD (INT_N),A
    EI
    RET                  ;ici on poke le numero pour la prochaine int

INT_N  NOP               ;ca c'est notre tampon d'1 octet pour le numero de l'int
                 ;Ensuite on n'a plus qu'à créer nos sous routines pour chaque int.
                 ;attention il est impératif que vous routine prennes moins d'un HALT
                 ;en longueur sans quoi ca deviendra vite le bordel
                 ;nous on va juste faire des rasters
INT6
    LD BC,#7f10:OUT(C),C
    LD A,76:OUT (C),A
    JP INT_END            ;on saute systématiquemen en fin à INT_END
                          ;rouge
INT5
    LD BC,#7F10:OUT(C),C
    LD A,78:OUT (C),A
    JP INT_END
                          ;orange
INT4
    LD BC,#7F10:OUT(C),C
    LD A,74:OUT (C),A
    JP INT_END
                          ;jaune
INT3
    LD BC,#7F10:OUT(C),C
    LD A,75:OUT (C),A
    JP INT_END
                          ;blanc
INT2
    LD BC,#7F10:OUT(C),C
    LD A,95:OUT (C),A
    JP INT_END
                          ;bleu pâle
INT1
    LD BC,#7F10:OUT(C),C
    LD A,85:OUT (C),A
    JP INT_END
                          ;bleu vif 
Voila c'est presque bon... Reste un gros détail qui a une importance cruciale : Quand l'int a lieu autre chose sera peut-être déjà en cours. Aussi puisque vous utilisez des registres pendant les int, vous allez modifier les contenus des registres de la routine interrompue... Il faut donc IMPÉRATIVEMENT SAUVEGARDER LES REGISTRES !!! Bien entendu si vous savez quels registres vous utilisez inutile d'en sauvegarder plus. Aller on refais ça au propre. 
Comme nous sommes un peu barbare, on va aussi ajouter une boucle infinie histoire que toute cette merde ne plante pas après le RET.
ORG #8000
 DI                               ;on coupe les int il n'y en aura plus
 LD    B,#F5
FRM    IN A,(C):RRA:JR NC,FRM     ;on teste la VBL car la première int a lieue en même temps
 HALT
 HALT
 HALT                             ;la on est certain d'avoir passé la vbl
FRM2   IN A,(C):RRA:JR NC,FRM2    ;deuxième test... La c'est certain nous sommes au début de la VBL
                                  ;Puisqu'on est dans la VBL nous sommes donc a la premiere INT.
                                  ;Comme nous on decremente, ca sera celle notee 6
 LD    A,6:LD (INT_N),A
                                  ;notre compteur est initialise a 6
                                  ;On va maintenant mettre en #38 un saut vers notre routine
                                  ;de gestion des int
 LD   A,#C3:LD (#38),A            ;#C3=JP
 LD   HL,INT:LD (#39),HL          ;en #38 nous avons donc JP INT
                                  ;une fois fait on remet les interruptions
 EI                               ;fin de l'initialisation des int
;******************************************************************************
;******************************************************************************
;******************************************************************************
;***********************GESTION DES INTERRUPTIONS******************************
;******************************************************************************
;******************************************************************************
;******************************************************************************
Boucle
    jp boucle     ;bon bein c'est une boucle infinie quoi...
    RET    
                  ;lors d'une interruption le Z80 sautera donc en #38 qui le renverras ici
                  ;puisqu'on a mis en #38 un JP INT
                  ;La routine regarde le numéro de l'interruption en cours
                  ;et y saute
INT
    PUSH BC
    PUSH HL
    PUSH DE
    PUSH AF         ;on sauve nos registres
INT LD A,(INT_N)
    CP 1:JP Z,INT1
    CP 2:JP Z,INT2
    CP 3:JP Z,INT3
    CP 4:JP Z,INT4
    CP 5:JP Z,INT5
    JP INT6        ;il n'en reste qu'une c'est forcement la 6
INT_END
    LD A,(INT_N)
    DEC A
    OR A
    JP NZ,INT_FIN        ;On va maintenant decrementer le numero d'int
                         ;si c'est =0 alors on boucle a 6
    LD A,6

INT_FIN
    LD (INT_N),A
    EI
    RET                  ;ici on poke le numero pour la prochaine int

INT_N  NOP               ;ca c'est notre tampon d'1 octet pour le numero de l'int
                 ;Ensuite on n'a plus qu'à créer nos sous routines pour chaque int.
                 ;attention il est impératif que vous routine prennes moins d'un HALT
                 ;en longueur sans quoi ca deviendra vite le bordel
                 ;nous on va juste faire des rasters
INT6
    LD BC,#7f10:OUT(C),C
    LD A,76:OUT (C),A
    JP INT_END            ;on saute systématiquemen en fin à INT_END
                          ;rouge
INT5
    LD BC,#7F10:OUT(C),C
    LD A,78:OUT (C),A
    JP INT_END
                          ;orange
INT4
    LD BC,#7F10:OUT(C),C
    LD A,74:OUT (C),A
    JP INT_END
                          ;jaune
INT3
    LD BC,#7F10:OUT(C),C
    LD A,75:OUT (C),A
    JP INT_END
                          ;blanc
INT2
    LD BC,#7F10:OUT(C),C
    LD A,95:OUT (C),A
    JP INT_END
                          ;bleu pâle
INT1
    LD BC,#7F10:OUT(C),C
    LD A,85:OUT (C),A
    JP INT_END
                          ;bleu vif 

Voila voila. Ce principe vous permettra surtout de faire des rasters au niveau d'une int ou par exemple de changer de MODE. (Zisquier, l'int 6 et 2 devrait t’intéresser pour mettre ton écran de jeu en mode 0 et le HUD en mode 1) Au passage pour un changement de mode :
LD  BC,#7f00+%100011XX
OUT (C),C
XX sont bien évidement les 2 bits de mode (00/01/10/... et le mode 3 tout pourri 11)

 

Commentaires   

0 #1 ZISQUIER 09-06-2019 11:03
Rahhhhh j'en suis au Flipping ! J'ai posté un topic de la map de mon code afin de repérer ce qui ne va pas

You have no rights to post comments