Gestion de la transparence avec un sprite logicielIl existe de nombreuses méthodes pour gérer la transparence, toutes plus ou moins inélégantes. Comment ça, inélégantes? En effet, les pixels transparents n'ayant pas à être affichés, ils ne devraient pas être traités non plus!Pourquoi perdre du temps à gérer quelque chose d'inexistant? Évidemment, la réalité est plus complexe que ça, c'est souvent parce qu'on pense économiser de la mémoire qu'on va simplifier au maximum la routine d'affichage. Et comme nous sommes dans une série d'apprentissage, nous allons faire nos gammes avec des routines peu performantes afin d'apprendre chaque principe dans le détail. Nous allons modifier nos deux fichiers car l'encre rose est la 8è couleur et cela simplifierait nos calculs d'avoir la couleur zéro transparente. Nous allons utiliser une table pour prémâcher un peu nos calculs. L'idée de base est de ne PAS afficher les pixels de l'encre 0, ok? Pour cela, l'outil [ConvGeneric] permet de réorganiser les couleurs d'une image. Petit rappel de la page [structure Écran]
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | Mémoire | P1.0 | P0.0 | P1.2 | P0.2 | P1.1 | P0.1 | P1.3 | P0.3 |
Nous allons parcourir toutes les valeurs possibles d'un octet, il y en a 256. Puis, nous allons regarder si le pixel de droite ou celui de gauche correspond à notre couleur 8. Enfin nous définirons un masque d'affichage. align 256 tableTransparence repeat 256,x px=x-1 ; car le compteur va par défaut de 1 à 256 et non 0 à 255 masque=0 if (oc & (128|32|8|2))==0 masque|=128|32|8|2 ; pour cet octet on conservera les données écran endif if (oc & (64|16|4|1))==0 masque|=64|16|4|1 ; pour cet octet on conservera les données écran endif defb masque rendQuand nous lirons un octet dans notre sprite, à partir de cet octet, nous irons récupérer le masque à appliquer sur les données de l'écran. Enfin nous ferons un OR des données de notre sprite. Comme la couleur transparente choisie est l'encre zéro, un OR de zéro ne modifiera pas les données écran. La boucle principale pour un octet devient : ; DE=source BC=destination HL=table de masque alignée sur 256 octets ld a,(de) ; lire le sprite ld l,a ; octet du sprite dans L ld a,(bc) ; on récupère l'octet de l'écran and (hl) ; on applique le masque or l ; on fusionne avec la donnée du sprite ld (bc),a ; on remet dans l'écran inc bc inc de ; et on incrémente data+ecranLa routine est plus complexe, il va nous falloir un autre registre pour compter les pixels en X, ou alors dérouler cette routine bien assez lente comme ça.On modifie notre source pour appliquer les modifications et nous utilisons des sprites réorganisés pour l'encre transparente en index zéro[hibouZéro] et [forêtZéro] Note : Pour l'exemple, nous utiliserons aussi l'affichage masqué pour restituer le fond, c'est une hérésie au niveau des performances car notre fond ne contiendra jamais cette encre. Dans un projet sérieux, nousaurons plusieurs routines : Celle de sauvegarde, celle d'affichage du sprite et celle de restitution, plus rapide que l'affichage masqué. BUILDSNA : BANKSET 0 ORG #38 : EI : RET ORG #100 : RUN #100ld sp,#100 : ei ld bc,#7F00+%10001100+%00 : out (c),c ; MODE 0 ld hl,palette : ld bc,#7F00 setPalette out (c),c : inc c : inc b : outi : ld a,(hl) : or a : jr nz,setPalette ; définir notre coordonnée X et son incrément pour faire bouger le sprite tout seul ld hl,40 ; milieu écran en nombre d'octets ld (positionx),hl ld hl,1 ld (incrementx),hl ;**************** BouclePrincipale ;**************** call waitVBL ; on attend d'être sûr que l'écran précédent soit complètement affiché ld bc,#BC00+12 : out (c),c : ld a,#30 : inc b : out (c),a ; écran visible en #C000 ; si restorex8 vaut 255 c'est le premier affichage, il ne faut pas 'restituer' ld b,#80 : ld a,(restorex8) : cp 255 : jr z,.first8 : ld c,a : ld de,backup8 : call RestituerTampon : .first8 ld b,#80 : ld a,(positionx) : ld c,a : ld de,backup8 : call SauvegarderPortionEcran ld b,#80 : ld a,(positionx) : ld (restorex8),a : ld c,a : call AfficherHibou call DeplacerHibou ; call waitVBL ; on attend d'être sûr que l'écran précédent soit complètement affiché ld bc,#BC00+12 : out (c),c : ld a,#20 : inc b : out (c),a ; écran visible en #8000 ; si restorexC vaut 255 c'est le premier affichage, il ne faut pas 'restituer' ld b,#C0 : ld a,(restorexC) : cp 255 : jr z,.firstC : ld c,a : ld de,backupC : call RestituerTampon : .firstC ld b,#C0 : ld a,(positionx) : ld c,a : ld de,backupC : call SauvegarderPortionEcran ld b,#C0 : ld a,(positionx) : ld (restorexC),a : ld c,a : call AfficherHibou call DeplacerHibou jr BouclePrincipale DeplacerHibou ; déplacer notre sprite ld hl,(positionx) : ld bc,(incrementx) : add hl,bc : ld (positionx),hl ; gérer le "rebond" aux bords ld a,h : or l : jr nz,.pasGauche : ld hl,1 : ld (incrementx),hl : .pasGauche ld a,l : cp 79-9 : jr nz,.pasDroite : ld hl,-1 : ld (incrementx),hl : .pasDroite ret AfficherHibou ld de,donnees_sprite RestituerTampon ; nécessite DE pré-positionné ld hl,80 ; Y=80 pour être à peu prêt centré ld xl,9 : ld xh,36 ; dimensions du sprite largeur/lignes call AfficheSprite ret waitVBL ld b,#F5 .loop in a,(c) : rra : ret c : jr .loop SauvegarderPortionEcran ld hl,80 ; Y=80 pour être à peu prêt centré ld xl,9 : ld xh,36 ; dimensions du sprite largeur/lignes ; BC=page + coordonnée X (0-79) / HL=coordonnée Y (0-199) / DE=adresse du tampon de sauvegarde ; XL=largeur du sprite en octets / XH=hauteur du sprite en nombre de lignes call CalculeAdressePixel ; HL=source écran ; DE=tampon de sauvegarde ld b,0 .sauvegardeLignes ld c,xl ; chargeur la largeur d'une ligne push hl ; on met l'adresse de début de la ligne de côté ldir pop hl ; on récupère l'adresse du début de ligne ; et on calcule le passage à la ligne suivante ld a,h : add 8 : ld h,a ; ajouter #800 à HL and #38 ; on teste si on déborde de la bank (passage de page+#3800 à page+#0000) jr nz,.nextLine ; pas zéro, on est toujours dans le même bloc de lignes ld a,80 : add l : ld l,a ; on ajoute 80 (largeur d'une ligne en octets) pour passer au bloc suivant ld a,#C0 : adc h : ld h,a ; et on enlève #4000 (additionner #C000 c'est comme enlever #4000) .nextLine dec xh ; notre compteur de lignes jr nz,.sauvegardeLignes ret ; ------------------------------- AfficheSprite ; BC=page + coordonnée X (0-79) / HL=coordonnée Y (0-199) / DE=adresse des données du sprite ; XL=largeur du sprite en octets / XH=hauteur du sprite en nombre de lignes call CalculeAdressePixel ; HL=destination écran ; DE=toujours l'adresse source des données ld bc,hl ; on utilise BC comme destination écran ld h,hi(tableTransparence) .afficheLignes ld a,xl : ld yl,a ; chargeur la largeur d'une ligne dans YL push bc ; on met l'adresse de début de la ligne de côté .pixelMasque ld a,(de) ; lire le sprite ld l,a ; octet du sprite dans L ld a,(bc) ; on récupère l'octet de l'écran and (hl) ; on applique le masque or l ; on fusionne avec la donnée du sprite ld (bc),a ; on remet dans l'écran inc bc inc de ; et on incrémente data+ecran dec yl jr nz,.pixelMasque pop bc ; on récupère l'adresse du début de ligne ; et on calcule le passage à la ligne suivante ; notre routine de passage à la ligne suivante, adaptée pour BC ld a,b : add 8 : ld b,a ; ajouter #800 à DE and #38 ; on teste si on déborde de la bank (passage de page+#3800 à page+#0000) jr nz,.nextLine ; pas zéro, on est toujours dans le même bloc de lignes ld a,80 : add c : ld c,a ; on ajoute 80 (largeur d'une ligne en octets) pour passer au bloc suivant ld a,#C0 : adc b : ld b,a ; et on enlève #4000 (additionner #C000 c'est comme enlever #4000) .nextLine dec xh ; notre compteur de lignes jr nz,.afficheLignes ret ;------------------- CalculeAdressePixel ; B=page vidéo #00, #40, #80 ou #C0 ; C=coordonnée X (0-79) ; HL=coordonnée Y (0-199) ; adresse de la ligne dans HL en résultat add hl,hl ; adresses 16 bits, il faut indexer de 2 en 2 ld a,lo(tableau) : add l : ld l,a : ld a,h : adc hi(tableau): ld h,a ld a,(hl) : inc hl ld h,(hl) : ld l,a add hl,bc ; ajouter la position X en octets et la page! ret ;------------------- adresse_ecran=#0000 largeur_ecran=80 tableau repeat 25 repeat 8 defw adresse_ecran adresse_ecran+=#800 rend adresse_ecran+=largeur_ecran adresse_ecran-=#4000 rend align 256 tableTransparence repeat 256,x px=x-1 ; car le compteur va par défaut de 1 à 256 et non 0 à 255 masque=0 if (px & (128|32|8|2))==0 masque|=128|32|8|2 ; pour cet octet on conservera les données écran endif if (px & (64|16|4|1))==0 masque|=64|16|4|1 ; pour cet octet on conservera les données écran endif defb masque rend palette defb #4D,#54,#56,#5C,#46,#5E,#40,#47,#43,#4E,#4B,#4C,0 ; Rose en zéro donnees_sprite incbin 'hibouZero.bin' restorex8 defb 255 ; il ne faut pas restituer le fond la première fois restorexC defb 255 ; il ne faut pas restituer le fond la première fois positionx defw 0 incrementx defw 0 backup8 defs 9*36 ; on réserve la place de sauvegarde pour notre sprite backupC defs 9*36 ; on réserve la place de sauvegarde pour notre sprite ; initialiser notre écran sur les deux pages! org #8000 : incbin 'foretZero.bin' org #C000 : incbin 'foretZero.bin' Ça commence à être sympa non? Roudoudou CPCrulez[Content Management System] v8.732-desktop Page créée en 070 millisecondes et consultée 30 foisL'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko. |
|