Revenir au plan du site

Gestion de la transparence avec un sprite logiciel


Il 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]
Bit7 6 5 4 3 2 1 0
MémoireP1.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
rend


Quand 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+ecran


La 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, nous aurons 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 #100

ld 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?