Revenir au plan du site

Détail du tracé de colonnes sur le scrolling multi-directionnel

Les routines à appeler pour scroller à droite ou à gauche sont complètement flexibles

On utilise les définitions du moteur ENGINE_*

- La hauteur des tiles peut être 8, 16 ou 32
- La largeur des tiles peut être 2, 4, 8
- La largeur et la hauteur de la map sont libres (il faut quand même qu'elle soit assez grande pour afficher un écran)
;------------------------------------------------------------------------------------------------------------------------------------
ScrollGauche
;------------------------------------------------------------------------------------------------------------------------------------
ld a,(ix+multi.colonne) : or a : jr nz,.goForScroll
ld a,(ix+multi.mapOffset) : and ENGINE_MAP_WIDTH-1 : ret z ; si on est sur la colonne zéro de la tile la plus à gauche... Bye!
.goForScroll
ld a,(ix+multi.HSSR) : bit 2,a : jr nz,.willDraw : add 4 : ld (ix+multi.HSSR),a : ret ; on ne change que le décalage et bye!
.willDraw
add 4 : and 15 : jr nz,.stillSSR
ld hl,(ix+multi.crtcHL) : dec hl : ld (ix+multi.crtcHL),hl ; each word
.stillSSR ld (ix+multi.HSSR),a
ld hl,(ix+multi.adresseDebut) :
ld a,h : and 7 : or l : jr nz,.skip : ld a,h : add 8 : ld h,a : .skip dec hl ; gérer le rebouclage à la décrémentation
ld (ix+multi.adresseDebut),hl
ld a,(ix+multi.colonne) : dec a : and ENGINE_TILE_WIDTH-1 : ld (ix+multi.colonne),a : cp ENGINE_TILE_WIDTH-1 : jr nz,.stillPosx : dec (ix+multi.mapOffset) : .stillPosx
ld hl,(ix+multi.mapOffset) : ld bc,ENGINE_MAP_WIDTH : exx ; HL'=adresse de la tile BC'=saut de ligne dans la tileMap
ld de,(ix+multi.adresseDebut) ; DE=haut de la colonne à rafraichir
call afficheColonne

;------------------------------------------------------------------------------------------------------------------------------------
ScrollDroite
;------------------------------------------------------------------------------------------------------------------------------------
ld a,(ix+multi.mapOffset) : and ENGINE_MAP_WIDTH-1 : cp ENGINE_MAP_WIDTH-ENGINE_NBTILE_WIDTH : ret z ; gérer le bord droit
ld a,(ix+multi.HSSR) : bit 2,a : jr z,.willDraw : sub 4 : ld (ix+multi.HSSR),a : ret
.willDraw
sub 4 : jr nc,.stillHSSR : jr z,.stillHSSR
ld hl,(ix+multi.crtcHL) : inc hl : ld (ix+multi.crtcHL),hl
ld a,12
.stillHSSR ld (ix+multi.HSSR),a

ld hl,(ix+multi.mapOffset) : ld bc,ENGINE_NBTILE_WIDTH : add hl,bc : ld bc,ENGINE_MAP_WIDTH : exx ; HL'=adresse de la tile dans la tileMap BC'=saut de ligne dans la tileMap
ld hl,(ix+multi.adresseDebut) : ld bc,64 : ld a,h : add hl,bc : xor h : and %11111000 : xor h : ld h,a ; gère le rebouclage de bloc en bit merge
ex hl,de ; DE=haut de la colonne à rafraichir
call afficheColonne
ld a,(ix+multi.colonne) : inc a : and ENGINE_TILE_WIDTH-1 : ld (ix+multi.colonne),a : jr nz,.stillPosx : inc (ix+multi.mapOffset) : .stillPosx
ld hl,(ix+multi.adresseDebut) : inc hl : ld a,h : and 7 : or l : jr nz,.skip : ld a,h : sub 8 : ld h,a : .skip : ld (ix+multi.adresseDebut),hl
ret

La boucle principale des colonnes boucle au besoin selon la configuration du moteur
;----------------------------------------------------------------------------------------
afficheColonne
;----------------------------------------------------------------------------------------
ld a,(ix+multi.ligne) : or a : jp nz,.clipped
ld yh,ENGINE_NBTILE_HEIGHT
.loopTiles
calculeAdresseTuileColonne ENGINE_MAP_WIDTH
if ENGINE_TILE_HEIGHT>8
ld bc,8+((ENGINE_TILE_HEIGHT>>3)<<8) ; B=4 blocs de 8 lignes C=block walker
.insideTile
else
ld bc,#408 ; B=64 C=block walker
endif
repeat 7 : ld a,(hl) : ld (de),a : inc h : ld a,d : add c : ld d,a : rend
ld a,(hl) : ld (de),a : inc h
if ENGINE_TILE_HEIGHT>8 : ld a,64 : else : ld a,b : endif
add e : ld e,a : ld a,#C8 : adc d : ld d,a : res 3,d
if ENGINE_TILE_HEIGHT>8
djnz .insideTile
endif
dec yh : jp nz,.loopTiles
ret
;----------
.clipped ; on arrive ici avec ligne dans A mais on ne pourra pas s'en servir... (exa ?)
calculeAdresseTuileLigneColonne ENGINE_MAP_WIDTH
ld a,ENGINE_TILE_HEIGHT : sub (ix+multi.ligne) : and ENGINE_TILE_HEIGHT-1 : ld b,a
.loopColumnC1 ld a,(hl) : ld (de),a : expandNextLineDE (void) : inc h : djnz .loopColumnC1
ld yh,ENGINE_NBTILE_HEIGHT-1
call .loopTiles
; dernière tuile clippée
calculeAdresseTuileColonne 0
ld b,(ix+multi.ligne)
.loopColumnC3 ld a,(hl) : ld (de),a : expandNextLineDE (void) : inc h : djnz .loopColumnC3
ret

Détail du tracé de lignes sur le scrolling multi-directionnel

Le fonctionnement du champ "ligne" est différent du premier moteur multi-buffer. Comme les tuiles ne sont plus stockées linéairement, ce champ correspond vraiment au nombre de lignes réel (rappel : il suffit d'ajouter sa valeur à H pour tomber sur la ligne de la tuile). Il n'y a plus besoin de tricher en y mettant un offset.

Enfin, deux fonctions font leur apparition, ça n'était pas sérieux d'appeler deux fois de suite le passage de ligne quand on peut mutualiser facilement et plus rapidement. Idéalement comme on ne les appelle que pour Haut/Bas, on en fait des macros ça évite le CALL/RET qui prend 9 nops pour rien...
MACRO NextLineHL2
ld a,h : add 16 : ld h,a
and #38 : jr nz,_+
ld a,64 : add l : ld l,a
ld a,#C0 : adc h : ld h,a
res 3,h
_
MEND

MACRO PreviousLineHL2 ; depuis une ligne de bloc paire
ld a,h : sub 16 : ld h,a
or #CF : inc a ; zéro si on vient du bloc 0
jr nz,_+
ld a,l : add #C0 : ld l,a
ld a,#47 : adc h : ld h,a
res 3,h
_
MEND

;------------------------------------------------------------------------------------------------------------------------------------
ScrollBas
;------------------------------------------------------------------------------------------------------------------------------------
ld hl,(ix+multi.mapOffset) : ld a,hi(tileMap+(ENGINE_MAP_HEIGHT-256/ENGINE_TILE_HEIGHT)*ENGINE_MAP_WIDTH) : cp h : ret z ; trop bas, on ne fait rien!
; la tuile en bas sera ENGINE_NBTILE_HEIGHT lignes de tuiles plus bas, dans la map
ld bc,ENGINE_NBTILE_HEIGHT*ENGINE_MAP_WIDTH : add hl,bc : exx ; se positionner en bas de l'écran rapport à la map + sauvegarde dans HL'
ld de,(ix+multi.adresseDebut) ; écran carré, calculs simples, le bas revient en haut et vice versa :)
call afficheTuilesHorizontalesDeuxLignes
; mettre à jour les informations courantes
ld a,(ix+multi.ligne) : add 2 : cp ENGINE_TILE_HEIGHT : jr nz,.pasDeChangementDeTuile ; on descend de 2 lignes en 2 lignes / ligne contient l'offset de début de la ligne suivante
ld hl,(ix+multi.mapOffset) : ld bc,ENGINE_MAP_WIDTH : add hl,bc : ld (ix+multi.mapOffset),hl : xor a ; passer à la tuile dessous
.pasDeChangementDeTuile
ld (ix+multi.ligne),a ; valider la ligne de tuile
ld hl,(ix+multi.adresseDebut) : NextLineHL2 (void) : ld (ix+multi.adresseDebut),hl
ld a,(ix+multi.VSSR) : add #20 : and #60 : ld (ix+multi.VSSR),a : jr nz,.pasDeChangementDeBloc
ld hl,(ix+multi.crtcHL) : ld bc,32 : add hl,bc : ld (ix+multi.crtcHL),hl ; adresse CRTC augmente de 32 mots si le bloc change
.pasDeChangementDeBloc
ret

;------------------------------------------------------------------------------------------------------------------------------------
ScrollHaut
;------------------------------------------------------------------------------------------------------------------------------------
ld hl,(ix+multi.mapOffset) : ld a,hi(tileMap) : cp h : jr nz,.okPourLeScroll ; si on est près du début on doit contrôler le "Y"
ld a,l : cp ENGINE_MAP_WIDTH-1 : jr nc,.okPourLeScroll ; si on est sur la première ligne, on doit regarder la ligne de tuile!
ld a,(ix+multi.ligne) : or a : ret z ; tout en haut, aurevoir!
.okPourLeScroll ; on peut scroller!

; mettre à jour les informations courantes
ld a,(ix+multi.ligne) : sub 2 : jr nc,.pasDeChangementDeTuile ; on remonte deux 2 lignes / ligne contient l'offset de début de la ligne suivante
ld hl,(ix+multi.mapOffset) : ld bc,-ENGINE_MAP_WIDTH : add hl,bc : ld (ix+multi.mapOffset),hl : ld a,ENGINE_TILE_HEIGHT-2 ; passer à la dernière ligne de la tuile dessous
.pasDeChangementDeTuile
ld (ix+multi.ligne),a ; valider la ligne de tuile
ld hl,(ix+multi.adresseDebut) : PreviousLineHL2 (void) : ld (ix+multi.adresseDebut),hl
ld a,(ix+multi.VSSR) : sub #20 : and #60 : ld (ix+multi.VSSR),a : cp #60 : jr nz,.pasDeChangementDeBloc
ld hl,(ix+multi.crtcHL) : ld bc,-32 : add hl,bc : ld (ix+multi.crtcHL),hl ; adresse CRTC baisse de 32 mots si le bloc change
.pasDeChangementDeBloc
; on affiche après en remontant
ld hl,(ix+multi.mapOffset) : exx
ld de,(ix+multi.adresseDebut)
call afficheTuilesHorizontalesDeuxLignes

Enfin, la routine de tracé bénéfice de différentes optimisations sur les boucles :
- pas d'incrément final qui ne sert à rien
- la gestion des colonnes est simplifiée Mais on est obligé de ralentir un peu sur le calcul de l'adresse de la ligne du dessous (le tracé de ligne reste le moins gourmand du moteur)
;-----------------------------------------------
afficheTuilesHorizontalesDeuxLignes
;-----------------------------------------------
push de
.Paires
ld a,(ix+multi.colonne) : or a : jp nz,.routineClippee
calculeAdresseTuileLigne 1 : ldi ENGINE_TILE_WIDTH-1 : ld a,(hl) : ld (de),a : inc de : res 3,d
ld a,l : sub ENGINE_TILE_WIDTH-1 : ld l,a : inc h : ld (suiteImpaire.imp0+1),hl ; adresse de la ligne suivante de la tuile à la bonne ligne
.loop
repeat ENGINE_NBTILE_WIDTH-1,x,1
calculeAdresseTuileLigne 1 : ldi ENGINE_TILE_WIDTH-1 : ld a,(hl) : ld (de),a
if x<ENGINE_NBTILE_WIDTH-1 : inc de : res 3,d : endif
ld a,l : sub ENGINE_TILE_WIDTH-1 : ld l,a : inc h : ld (suiteImpaire.imp{x}+1),hl ; adresse de la ligne suivante de la tuile à la bonne ligne
rend
jp suiteImpaire.NonClippee

;---------------
.routineClippee
calculeAdresseTuileLigneColonne 1
inc h : ld (suiteImpaire.impFirst+1),hl : dec h
ld a,ENGINE_TILE_WIDTH : sub (ix+multi.colonne) : ld c,a : ld (suiteImpaire.lenFirst+1),a
ld b,0 : ldir
repeat ENGINE_NBTILE_WIDTH-1,x
calculeAdresseTuileLigne 1 : ldi ENGINE_TILE_WIDTH : res 3,d
ld a,l : sub ENGINE_TILE_WIDTH : ld l,a : inc h : ld (suiteImpaire.imp{x}+1),hl ; adresse de la ligne suivante de la tuile à la bonne ligne
rend
calculeAdresseTuileLigne 0 : inc h : ld (suiteImpaire.impLast+1),hl : dec h
ld c,(ix+multi.colonne)
ld b,0 : ldir

suiteImpaire
.clippee
pop de : ld a,d : add 8 : ld d,a ; ligne impaire du dessous, toujours vrai
.impFirst ld hl,#1234
.lenFirst ld bc,#12 : ldir : dec de : res 3,d : inc de : set 3,d
call .loop
.impLast ld hl,#1234
ld c,(ix+multi.colonne)
ld b,0 : ldir
ret
.NonClippee
pop de : ld a,d : add 8 : ld d,a
.imp0 ld hl,#1234 : ldi ENGINE_TILE_WIDTH-1 : ld a,(hl) : ld (de),a : res 3,d : inc de : set 3,d
.loop
repeat ENGINE_NBTILE_WIDTH-1,x,1
.imp{x} ld hl,#1234 : ldi ENGINE_TILE_WIDTH-1 : ld a,(hl) : ld (de),a
res 3,d : inc de : set 3,d ; superflu lors de l'appel en non clippé... rend
ret

Allez, courage, on va maintenant aborder l'initialisation d'un écran à partir de ces nouvelles tuiles [ ICI ]