Etoiles inactivesEtoiles inactivesEtoiles inactivesEtoiles inactivesEtoiles inactives
 

4.6 - Flipping

C'est bien tout ça mais pas tip top. C'est chiant à manipuler et quand on va ajouter plusieurs sprites qui bougent à l'écran on va vite se retrouver bloqué... Heureusement pour nous on a une superbe solution qui va cette fois régler le problème de façon définitive !!! Cette solution on la nomme "le double buffering"
4.6.1 - Double Buffering théorie.
Concrètement, pour ne pas se prendre le balayage et ne pas avoir à synchroniser nos affichages à différents endroit de l'écran, nous allons travailler sur une page mémoire et ne la mettre à l'écran que lorsque tout sera affiché. Il nous faudra donc travailler sur 2 pages écran de 16Ko. 
Pendant que l'on affiche sur la première, on met la deuxième à l'écran.
flipping0
 Quand on a fini d'afficher sur la première, on la met à l'écran et on travaille sur la deuxième.
flipping1
 Petite rappel :
RAM
Chacune de ces banks fait donc 16Ko (16385 Octets). L'écran de base étant en #C000 et se terminant en #FFFF. Pour travailler sur deux pages écran il va donc falloir voir un peu comment connecter tout cela. On va voir comment changer la page affichée à l'écran.
4.6.2 - Changer l'adresse de l'écran:
Pour changer l'adresse de l'écran nous allons passer par un autre composant: le CRTC !!! Ce composant très important du CPC est composé de plusieurs registres (on parle de registre mais ceci n'a rien à voir avec AF HL DE BC et autres !!!) dont les registres 12 et 13 qui permettent de changer l'adresse de l'écran.
Offset
Pour le CRTC il faut dans un premier temps choisir le registre. Pour cela nous utiliserons le port #BC qui justement correspond à la selection de registre.
LD BC,#BC0C     ;(#0C=12 en décimal)
OUT (C),C       ;R12 selectionné
Pour envoyer une valeur à ce registre il nous faudra donc séléctionner le port d'envois: #BD
LD BC,#BD00+%00010000
OUT (C),C      ;Ecran en #4000
Voila vous savez tout !!! Ou presque...
Reste à préciser que l'adresse de départ de l'écran est appelée OFFSET et que celui ci est pris en compte en fin d'écran (vous pouvez donc le donner quand vous voulez entre le début de l'écran et avant la fin de celui-ci)
Mais surtout : le CRTC ne prend en compte que la RAM centrale dans sa config standard. Vous aurez beau modifier l'ordre des banks, pour le CRTC c'est comme si vous n'aviez rien fait, les banks sont pour lui toujours linéaires avec les banks 0 - 1 - 2 et pour finir 3.
Attention tout de même, sur CRTC 1 l'offset est aussi pris en compte à la fin du premier bloc de ligne... Donc si vous voulez rester compatible avec ce CRTC, essayez de donner le nouvel offset après le premier bloc de ligne au minimum.
Pour le principe c'est assez simple. Mais c'est assez contraignant. En effet puisqu'on affiche à deux endroits différents il nous faut deux routines d'affichage différentes:
-Une routine pour afficher entre #4000/#7FFF
-Une routine pour afficher entre #C000/#FFFF
C'est en fait assez pénible et pas franchement facile à gérer.
Heureusement pour nous une astuce va nous permettre de passer outre cette contrainte en affichant toujours au même endroit.
Aller on change de chapitre pour voir cela un peu plus en détail.
C'est simple : Avant de commencer l'offset doit être en #C000. Comme votre jeu est à écran fixe, commencez donc par afficher tout le décors en #C000 et copiez le en #4000 (A coup de LDIR ou encore mieux: à la pile !!! Hein quoi ? J'vous ai pas expliqué ça ??? Ah merde :D )

4.7 - Double Buffering (astuces)

 

4.7 - Double Buffering (astuces)
Nous savons donc alterner 2 pages écrans afin d'afficher sur une non visible puis alterner entre les deux pour ne mettre à l'écran que la page entièrement affichée.
Le soucis qui se présente c'est qu'on a besoin de deux routines d'affichage: une en #4000/#7FFF et l'autre pour #C000/#FFFF.
Il faut l'admettre: ce n'est absolument pas pratique...
Heureusement pour nous le Gate Array va nous rendre la chose plus facile et nous permettre de n'utiliser qu'une seule routine d'affichage.
Avant d'expliquer la technique il faut ABSOLUMENT retenir une chose: 
QUELQUE SOIT LA CONFIGURATION DE LA RAM, LE CRTC NE CONSIDÈRE QUE LA RAM CENTRALE LINÉAIRE !!!
 
POUR LE CRTC LA RAM RESTE TOUJOURS LA MÊME !!!
4.7.1 - Le Gate Array au secours
Tout d'abord un petit schéma:
GA RAM BANKs
Comme on peut le voir ici, le GA nous permet de choisir une configuration de la RAM. (Rappel: le CRTC s'en fout, pour lui ça ne change rien).
ppp correspond à la page de 64Ko de RAM.
ppp=0 et c'est la première page, soit celle de base au démarrage.
PPP=1 et se sont les 64Ko de RAM supplémentaires sur un 6128.
Sur un cpc de base vous n'en avez pas plus.
Ainsi pour avoir la configuration "normale" on fera:
LD BC,#7F00+%11000000:OUT (C),C
en hexa: LD BC,#7FC0:OUT (C),C
Ce qui va nous intéresser particulièrement ici c'est le dernier cas: %11000011 autrement dit le mode #C3
4.7.2 - Le mode #C3
Ce qu'on nous dit et ce qui nous intéresse c'est surtout que: la Bank #C000/#FFF de la RAM centrale est connectée en #4000/#FFFF.
J'explique vite fait l'autre truc: La bank 3 de la page ppp qui est connectée en #C000/#FFFF.
Considérons chaque page de 64Ko de la façon suivante:
page64ko
Puisque ppp=0 la page sélectionnée correspond donc au 64 premiers Ko de RAM (soit la RAM centrale) et qu'on nous dit que la bank 3 de la page sera connectée en #C000/#FFFF. En gros pour le coup ça ne changera rien en #C000 puisque c'est la même bank que la normale qui sera connectée en #C000.
Bref voyons concrètement par un schéma ce que va nous faire ce mode #C3:
GAmodeC3
Voila ce que concrètement le GA mode #C3 va permettre.
L'interet de la chose c'est de voir ce qui se passe entre #4000 et #7FFF.
En mode #C0 (RAM standard), en #4000 nous aurons donc bien la bank 1. Mais dès que nous passerons en mode #C3, en #4000 c'est la bank 3 qui sera connectée !!!
Ainsi et c'est la que réside toute l'astuce, si notre routine d'affichage est faite pour fonctionner en #4000:
-En mode #C0, nous afficherons bien sur la bank 1 normalement en #4000/#7FFF
-En mode #C3, nous afficherons bien sur la bank 3 normalement en #C000/#FFFF
Et voila résolu notre problème de routine d'affichage puisqu'une seule routine qui affiche en #4000/#7FFF suffira :)
Comme déjà dit, ces changements de la RAM via le GA n'influence pas (et tant mieux) ce que le CRTC lui voit.
Aller hop petit schéma pour voir concrètement ce qu'on va pouvoir faire:
Le cas numéro 1: l'écran est en #C000 on affiche en #4000 (de toute façon maintenant on affichera toujours en #4000)
DB FRAME1
Le cas numéro 2: l'écran est en #4000 mais c'est la bank 3 qui y est connectée. On écrit donc en #4000 mais finalement dans la bank 3(#C000/#FFF).
DB FRAME2
Ce qui compte pour le moniteur c'est l'écran envoyé par le CRTC. Ici on alterne bien entre #4000 et #C000.
Pour nous et pour notre routine, passer du mode standard #C0 au mode #C3 nous permet d'afficher toujours en #4000 bien qu'en mode #C0 il s'agisse de la bank 1 et en mode #C3 de la bank 3 :)
4.7.3 - Les astuces
Changer les valeurs à envoyer au GA et au CRTC c'est facile. Cependant, cela nous oblige à savoir dans quelles configurations ils étaient pour le mettre dans l'autre...
Que nenni !!! Une petite opération logique va nous rendre la chose bien plus facile !!!
Voyons pour le GA.
Nous allons donc alterner entre deux valeurs: #C0 et #C3.
Comment pouvons nous passer de l'une à l'autre sans avoir à tester la valeur ???
Passons en binaire:
#C0=%11000000
#C3=%11000011
La différence se fait donc sur les bits 0 et 1.
Il faut donc que quand ces deux bits sont à 0 ils passent à 1 et inversement. Sans bien évidement toucher aux autres bits.
Regardons nos opérations logiques:
Operations logiques
La solution est toute trouvée: le XOR !!!
#C0 = %11000000
XOR = %00000011
         %11000011 = #C3
#C3 = %11000011
XOR = %00000011
         %11000000 = #C0
:) 
Le même principe fonctionne pour notre valeur d'offset à envoyer au registre 12 du CRTC:
#30 = %00110000 - Écran en #C000
#10 = %00010000 - Écran en #4000
#30 = %00110000
XOR = %00100000
      = %00010000 = #10
#10 = %00010000
XOR = %00100000
      = %00110000 = #30
On peut donc facilement passer d'une valeur à l'autre sans tester quoi que se soit !!!
4.7.4 - Algorythme pour un jeu écran par écran
On y arrive, voyons maintenant comment nous y prendre pour le cas d'un jeu se déroulant écran par écran sans scrolling.
Comme nous utilisons deux écrans il faudra à chaque nouvel écran afficher à deux endroits, donc en double notre décors.
Soit en Bank 1 et en bank 3.
Le plus rapide est d'afficher sur un écran et de le recopier tel quel dans l'autre.
La notre base est posée.
INIT    - Affichage du décors en bank 1
        - Copie de la bank 1 en bank 3 
Une fois cela fait, on va initialiser le CRTC pour la première "frame" ainsi que le mode du GA. On attendra une frame histoire d'être certain que le CRTC a bien changé l'offset
INIT    - Affichage du décors en bank 1
          - Copie de la bank 1 en bank 3
          - Écran en #C000
          - GA mode #C0
          - Frame 
On peut alors initialiser le placement du personnage ainsi que de ennemis et les afficher à l'écran.
INIT    - Affichage du décors en bank 1
          - Copie de la bank 1 en bank 3
          - Écran en #C000 => on stocke la valeur quelque part en RAM
          - GA mode #C0 => on stocke la valeur quelque part en RAM
          - Affichage perso écran #C000
          - Affichage ennemis écran #C000
          - Frame
 
Voila on est pret :)
A partir de ce moment on entre dans la boucle principale. Dans notre boucle principale il y aura deux choses:
-La gestion des évènements (énemis qui se déplacent; élements de décors qui se déplacent ou s'animent etc etc)
-Un test de touche pour notre perso
-Pas de test de frame !!!
Pourquoi pas de test de frame ? Parce que si rien ne se déplace ou n'est ré-affiché on ne change rien.
INIT    - Affichage du décors en bank 1
          - Copie de la bank 1 en bank 3
          - Écran en #C000
          - GA mode #C0
          - Affichage perso écran #C000
          - Affichage ennemis écran #C000
          - Frame
Boucle   -Test de touche
            -gestion des événements
            -JP Boucle
 
Dans le cas ou rien ne bouge, aucun soucis, notre boucle se produit et rien n'est affiché. Aucun événement n'a lieu, aucun problème.
Maintenant il faut réfléchir à un truc...
Un problème qui risque de se poser c'est lorsque plusieurs événements se produisent. A savoir soit un ennemis qui bouge; un décors qui s'anime; le perso est déplacé.
Il ne faudrait pas que l'on échange les écrans tant que tout n'est pas fait et donc surtout pas faire: 
-déplacement ennemis; changement écran
-déplacement plateforme; changement écran
-déplacement perso; changement écran
Sinon ca va nous prendre plusieurs frame...
Il faut donc que dès que quelque chose se produise, on attende que tout ait été affiché avant d'échanger les écrans. On va donc ajouter un "flag" d'événement qui sera mis à 0 quand tout est affiché et à une autre valeur si c'est pas le cas. Le flag sera testé en fin de boucle et si il n'est pas à 0, cela signifiera que quelque chose a été affiché et qu'il faut donc échanger les écrans. Puis on remettra le flag à 0 pour la prochaine fois.
Nos sous routines d'affichage n'auront donc qu'à mettre le flag >0
 
INIT    - Affichage du décors en bank 1
          - Copie de la bank 1 en bank 3
          - Écran en #C000
          - GA mode #C0
          - Affichage perso écran #C000
          - Affichage ennemis écran #C000
          - Flag événement=0 (ne pas oublier de l'initialiser)
          - Frame
Boucle   -Test de touche
         -gestion des événements
         -Test du flag événement.
         -Si >0 alors on bascule les écrans et le mode du GA pour la prochaine fois et on remet le flag=0
         -JP Boucle
Aller on détail un peu:
 
INIT     - Affichage du décors en bank 1
         - Copie de la bank 1 en bank 3
;Écran en #C000 et on poke en ram pour relire la config plus tard
LD BC,#BC0C:OUT (C),C:INC B:LD A,#30:OUT (C),A:LD (R12+1),A
         
;GA mode #C0
LD B,#7F:LD A,#C0:OUT (C),A:LD (GA_MODE+1),A
        - Affichage perso écran #C000
        - Affichage ennemis écran #C000
        - Flag événement=0 (ne pas oublier de l'initialiser)
         
XOR A:LD (FLAG_EVMT+1),A
Boucle    -Test de touche
          -gestion des événements
          ;Test du flag événement.
FLAG_EVMT    LD A,0               
;cette valeur est automodifiée
;Si >0 alors on bascule les écrans et le mode du GA
;pour la prochaine fois et on remet le flag=0
OR A                ;on teste si c'est égal à 0
JP Z,boucle         ;si c'est égal on boucle
                    ;sinon on arrive ici
LD BC,#BC0C:OUT (C),C
R12 LD A,0          ;cette valeur est automodifiée
XOR %00100000
LD (R12+1),A        ;on automodifie pour la prochaine fois
INC B:OUT (C),A     ;on envoie au CRTC
LD  B,#7F
GA_MODE 
LD A,0              ;cette valeur est automodifiée
XOR %00000011
LD  (GA_MODE+1),A   ;on automodifie pour la prochaine fois
OUT (C),A           ;on envoie au GA
                    
                    ;tout a donc été affiché; on attend la frame
LD  B,#F5
FRAME  IN A,(C):RRA:JR NC,FRAME
JP Boucle
Voila concrètement rien de plus compliqué.
Chaque routine d'affichage que se soit du perso; des énemis ou d'éléments de décors animés mettront le FLAG_EVMT à une valeur >0. Elles écriront TOUJOURS en #4000/#7FFF.
Reste un petit détail: la restitution du fond. La je vous laisse réfléchir un peu, mais concrètement celui-ci sera décalé vis à vis de l'affichage.

4.8 - Changement de pièce

Ca y est nous avons désormais une pièce de notre superbe château. On déplace notre personnage dessus, c'est magnifique.

Sauf que notre château il a plusieurs pièces !!! Pas de problème, on va régler ca tout de suite.
4.8.1 - Algorythme pour un jeu écran par écran
Afin de savoir si on change de pièce, on va simplement tester si notre perso sort de l'écran.
Pour cela on va utiliser ses coordonnées.
Partons des coordonnées de départ de celui-ci.
A l'initialisation de notre première pièce, il faut initialiser des coordonnées du perso principal (mais aussi des ennemis; plateformes mouvantes etc etc on verra cela plus loin).
Ces coordonnées vous permettront par calcul à afficher votre personnage, mais aussi justement à tester les sorties d'écran.
Prenons des coordonnées X,Y ou X est à l'octet et Y par exemple au caractère (8 lignes).
Pour savoir ou placer notre perso à l'écran il suffira de faire un Y*80(nombre d'octet par ligne)+X et on pourra afficher le perso.
En revanche pour ses coordonnées que se soit pour les collisions (on verra plus tard) ou pour les changement de pièce, on a besoin qu'elles soient dans l'unité la plus petite de son déplacement.
Si notre perso se décale à l'octet en X, il faudra donc une coordonnée X en octet.
Si notre perso saute ou tombe au pixel en Y il faudra donc être à l'octet (ce qui revient à être à la ligne) en Y.
Pour notre X c'est bon, c'est déjà à l'octet. Mais pour notre Y on est au caractère. Il faudra donc multiplier notre X par 8.
Nous obtenons donc des coordonnées qui vont nous permettre de faire nos tests.
4.8.2- Débordement

 

A chaque déplacement de notre personnage, nous allons modifier ses coordonnées.
Si on va à droite ou à gauche on fera respectivement un INC ou un DEC sur la coordonnée X.
Si on saute/monte ou tombe/descend, on fera autant de INC ou de DEC nécessaires sur la coordonnée Y.
A partir de la tout est simple.
-Si on va à gauche, on testera avant tout déplacement si X=0. Si c'est le cas, cela signifie qu'on est déjà au bord de l'écran et que par conséquent en allant à gauche, on sort de celui-ci.
-Si on va à droite, on testera avant tout déplacement si Y=80-largeur du sprite. Si c'est le cas on est déjà tout à droite et on change d'écran
-Si on va en haut et que Y=0 alors on change d'écran.
-Si on va en bas et que Y=hauteur de l'écran de jeu-hauteur du perso, alors on change d'écran.
Dès lors, on sort de la boucle et on initialise une nouvelle pièce.
-Si on sort de l'écran par la droite, on ne change pas la coordonnée Y; on met le X à 0 pour arriver à gauche. On initialise la pièce; on l'affiche et on lance la boucle.
-Si on sort de l'écran par la gauche, on ne change pas la coordonnée Y; on met le X à "largeur d'écran-largeur du sprite" pour arriver à droite. On initialise la pièce; on l'affiche et on lance la boucle.
-Si on sort de l'écran par le haut, on ne change pas le X; on met Y à "hauteur d'écran-hauteur du sprite" pour arriver en bas (c'est bourrin quand même: à améliorer)...
-Si on sort de l'écran par le bas, on ne change pas le X et on met le Y à 0 pour faire ensuite comme d'habitude.
Voila, on sait maintenant quand on change d'écran et on gère un minimum de coordonnées.
Passons à la suite en changeant de pièce.
4.8.3 - Map organisée
Avant de pouvoir changer de pièce, il serait judicieux d'en avoir plusieurs.
Comme nous l'avons vu, pour une pièce nous avons en mémoire un tableau contenant les numéros de tiles.
Si notre écran fait 20*12 tiles, cela nous donne un tableau de 20*12 octets. Jusque la c'est tout bon on garde.
Faire une nouvelle pièce revient donc a ajouter un nouveau tableau.
Mais comment passer de l'un à l'autre ?
La première solution qui vient à l'esprit c'est d'avoir un énorme tableau "carré" comportant toutes les pièces les unes à la suite des autres.
Pour un univers de 10*10 pièces cela nous donne un truc du type:
UNIVERS        PIECE00,PIECE01,PIECE02,PIECE03,PIECE04,PIECE05,PIECE06,PIECE07,PIECE08,PIECE09
                   PIECE10,PIECE11,PIECE12,PIECE13,PIECE14,PIECE15,PIECE16,PIECE17,PIECE18,PIECE19
                   PIECE20,PIECE21,PIECE22,PIECE23,PIECE24,PIECE25,PIECE26,PIECE27,PIECE28,PIECE29
                   ....
Admettons que notre jeu commence en pièce 14. Que chaque pièce fait 20*12 tiles.
-si on va à gauche on fera: adr dans le tableau-(20*12)
-si on va à droite on fera: adr dans le tableau+(20*12)
-si on monte, on fera: adr dans le tableau-(10*(20*12))
-si on descend, on fera: adr dans le tableau+(10*(20*12)
Plutôt simple, mais trop contraignant...
Effectivement cela nous oblige à avoir un univers "carré". Ce n'est vraiment pas pratique.
Voici donc une autre solution qui non seulement nous permet de passer outre le monde "carré", mais nous permet en plus par exemple d'arriver dans la même pièce en passant par plusieurs endroits.
Pour chaque pièce nous allons faire en plus d'une "map tile", une autre table avec des adresses vers ces "map tile".
Exemple:
Nous avons pour la pièce13 une "map tile" de 20*12 octets.
Si nous sortons par la gauche on va en "map tile"12; vers la droite en "map tile"14; vers le haut en "map tile"5;vers le bas en "map tile"22...
Appelons l'adr de notre "map tile"13: MAPTILE13
Donc à MAPTILE13 nous avons la map pour la pièce 13.
Maintenant nous allons faire une autre table "UNIVERS" qui elle contiendra d'une part: l'adresse de la map tile en court; l'adresse ou sauter dans "UNIVERS" si on va en haut; en bas; à droite et à gauche.
UNIVERS        ;          adr maptile ,haut     ,bas      ,gauche ,droit
PIECE13        DW        MAPTILE13,PIECE05,PIECE22,PIECE12,PIECE14 
Il suffira d'avoir à l'initialisation du premier écran (donc au début du jeu), un pointeur sur l'adresse dans la table UNIVERS de la première pièce.
Si on commence par la pièce 13 alors on fera un LD HL,PIECE13:LD (POINTEUR_PIECE),HL
On lira la première valeur MAPTILE et on affichera en conséquence la pièce.
Au changement d'écran, on pourra récupérer l'adresse pour la pièce suivante facilement en allant lire dans cette table la bonne adresse.
Voila, ce n'était pas plus compliqué.
4.8.4 - Amélioration de l'univers
Notre table UNIVERS est déjà bien pratique, mais on pourra ajouter des choses dedans. Par exemple initialiser les ennemis en donnant les coordonnées et leur "type"
Admettons que nous ayons un ennemis qu'on appellera TROLL. Cet ennemis quelque soit le tableau fait toujours la même chose: il se déplace de gauche à droite à partir de sa coordonnée de départ et e fait demi-tour que s'il rencontre un mur ou un trou (ça on le gèrera dans l'évènement en lui même).
Tous nos ennemis peut importe ce qu'ils sont se limiteront à cette initialisation.
Pour initialiser cet ennemis à l'arrivée dans la pièce nous avons donc besoin de plusieurs infos:
-Type: 1 octet, on n'aura pas non plus 256 choses différentes.
-X de départ: 1 octet
-Y de départ: 1 octet
-Direction (gauche ou droite; haut ou bas). Un octet suffira sur 4 bits par exemple: bit=0: on s'en fout; Bit=1 on va dans cette direction. H=Haut;B=Bas;G=Gauche;D=Droite
              notre octet sera de la forme %0000HBGD
Il faudra définir un nombre maximum d'événements. Disons 10 maxi par pièce ce qui devrait suffire sachant qu'on compte dans les événements tout ce qui bouge ou se modifie hormis le perso.
On terminera notre table par un octet à 0 afin de tester sa fin et la fin des initialisation d'événements
Reprenons notre table UNIVERS avec notre unique pièce pour le moment et ajoutons 3 événements:
UNIVERS        ;          adr maptile ,haut     ,bas      ,gauche ,droit
PIECE13        DW        MAPTILE13,PIECE05,PIECE22,PIECE12,PIECE14
                   DB        TROLL,X,Y,DIRECTION
                   DB        PIAF,X,Y,DIRECTION
                   DB        PLATEFORME_MOUVANTE,X,Y,DIRECTION
                   DB        0
 
Vous voyez l'idée ?
Même des changements de palette pour chaque pièce pourraient être ajoutés ou tout ce que vous voulez...
Notre univers commence à vivre petit à petit :)