Revenir au plan du site

Scroller dans toutes les directions avec l'ASIC de la gamme Plus


Dans les pages sur le [scrolling horizontal], et le [scrolling vertical], j'ai pris le cas le plus simple en suivant le tableau, celui où on incrémente et quand on déborde du compteur, on revient à zéro et on incrémente l'autre compteur. Pour aller en "marche arrière", on ne déborde pas dans le même sens donc les comparaisons sont un peu moins intuitives et les réinitialisations de compteur ne se font pas à zéro mais aux maximums respectifs. Rien d'insurmontable ;)

Pour commencer, on va se définir un écran purement carré. Il n'a que des avantages, c'est pour ça que vous le trouvez dans beaucoup de jeux/démos :
- Largeur multiple de 256 donc facilité de calculs de position à l'écran
- Tous les octets sont visibles ce qui -en cas de scrolling- est un cadeau pour savoir où écrire les nouvelles données (la ligne ou la colonne qui disparait, réapparait de l'autre côté)

Étapes de scrolling horizontal
position X en pixel mode 1 scroll CPC scroll CPC et registre 3 scroll ASIC + SSR adresse écran
0 CRTC Adr = #0000 CRTC Adr = #0000   R3 = #8C CRTC Adr = #0000   SSR = 0 #C000
1 CRTC Adr = #0000   SSR = 2 #C000
2 CRTC Adr = #0000   SSR = 4 #C000
3 CRTC Adr = #0000   SSR = 6 #C000
4 CRTC Adr = #0000   R3 = #85 CRTC Adr = #0000   SSR = 8 #C001
5 CRTC Adr = #0000   SSR = 10 #C001
6 CRTC Adr = #0000   SSR = 12 #C001
7 CRTC Adr = #0000   SSR = 14 #C001
8 CRTC Adr = #0001 CRTC Adr = #0001   R3 = #8C CRTC Adr = #0001   SSR = 0 #C002
9 CRTC Adr = #0001   SSR = 2 #C002
10 CRTC Adr = #0001   SSR = 4 #C002
... ... ... ... ...

Étapes de scrolling vertical
position Y scroll CPC scroll ASIC + SSR adresse écran
0 CRTC Adr = #0000 CRTC Adr = #0000   SSR = #00 #C000
1 CRTC Adr = #0000   SSR = #10 #C800
2 CRTC Adr = #0000   SSR = #20#D000
3 CRTC Adr = #0000   SSR = #30#D800
3 CRTC Adr = #0000   SSR = #40#E000
5 CRTC Adr = #0000   SSR = #50#E800
6 CRTC Adr = #0000   SSR = #60#F000
7 CRTC Adr = #0000   SSR = #70#F800
8 CRTC Adr = largeur / 2 CRTC Adr = largeur / 2   SSR = #00 #C000 + largeur
9 CRTC Adr = largeur / 2   SSR = #10 #C800 + largeur
... ... ... ...


Nul besoin d'avoir un tableau pour le multi-directionnel, il suffit de réaliser chaque direction l'une après l'autre et tout se passera bien.

Pour illustrer notre programme du jour, j'ai converti quelques tuiles d'une planche en libre accès et créé une carte avec le logiciel [ Tiled ]. J'imagine que beaucoup d'entre vous le connaisse, c'est un logiciel gratuit, opensource et multi-plateforme. Il est assez simple d'importer des données, d'indiquer à l'éditeur quelle est la taille des tuiles et ensuite de construire une carte avec ces tuiles.

Bon, moi j'suis pas graphiste alors vous me pardonnerez le coup de truelle sur cette carte de démonstration ;)
 

Les tuiles sont obtenues avec la commande suivante sur ce [fichier image]
convgeneric.exe -m 0 -g -tiles -size 8x16 mapMulti.png

On récupère la palette, la petite tileMap (qui s'assemble en 4k) et le petit binaire des graphismes (3K). Seulement 7K de données pour cette map, il faut dire qu'il y avait peu de sprites sur cette planche (64) et je ne me suis servi que de 47 d'entre eux! Pour réaliser notre scrolling, nous allons avoir besoin : - d'une position X et d'une position Y (une position en tuiles ou une position absolue. On peut aussi prendre l'offset direct dans la table )
- d'une sous-position X et d'une sous position Y (à l'intérieur des tuiles, donc limité par la taille d'une tuile)
- d'une adresse écran correspondant au coin supérieur gauche
- de l'adresse du CRTC correspondant à ce coin
- des décalages verticaux et horizontaux dans des compteurs séparés qu'on recombinera avant d'envoyer à l'ASIC

On va se créer une structure pour gérer notre double buffer facilement, contenant toutes ces informations
struct multi
mapOffset defw
HSSR defb ; 0-12
VSSR defb ; #00-#70
colonne defb ; 0-3 dans la tuile
ligne defb ; 0-60 par pas de 4 dans la tuile
adresseDebut defw
crtc12 defb ; cache des bits de bank
crtcHL defw ; adresse CRTC
endstruct
; et on se déclare deux structures pour un double buffer
struct multi ecran1
struct multi ecran2

On va déjà commencer par limiter notre scrolling aux contours de la map. C'est assez simple, il faut compter les tuiles et connaitre le nombre de tuiles présentes sur un écran :

Notre écran est carré pour 128 pixels sur 256, nos tuiles font 8 pixels sur 16 lignes. Un écran contient 16 tuiles sur 16 tuiles.

Notre tileMap fait 64 tuiles sur 64 donc quand on arrive à la tuile 48, que ce soit en hauteur ou en largeur, on arrête de scroller respectivement en bas et à droite.

Pour l'autre côté, nous avons besoin d'une double comparaison, nous arrêtons de scroller en haut et à gauche quand :
- L'index de tuile vaut zéro
- Le décalage vaut zéro

Pour terminer avec les chiffres, notre tileMap correspond à 16 écrans (4 en largeur, 4 en hauteur).

On attaque les routines? On commence par une routine qui initialise l'écran et les structures avec nos tiles (déjà vue dans [l'article sur les tiles])

InitEcran
; IX = structure buffer
; A = poids fort de la page vidéo
ld hl,tileMap
ld (ix+multi.mapOffset),hl
ld hl,0
ld (ix+multi.HSSR),h
ld (ix+multi.VSSR),h
ld (ix+multi.colonne),h
ld (ix+multi.ligne),h
ld (ix+multi.crtcHL),hl
ld h,a : ld (ix+multi.adresseDebut),hl
rrca : rrca : ld (ix+multi.crtc12),a

; Afficher les tuiles sur tout l'écran

ld de,(ix+multi.adresseDebut)
ld ix,tileMap ; on n'utilise plus la structure à partir de là
ld yh,16 ; 16 tuiles en hauteur
afficheLigne
ld yl,16 ; nombre de tuiles en largeur
push de ; sauvegarder l'adresse de début de ligne de l'écran
afficheTuile
push de ; sauvegarder l'adresse du début de la tuile à l'écran
ld h,(ix+0) : inc ix : ld l,0 : srl hl : srl hl ; x 256 / 4 c'est la taille de nos tuiles (64 octets)
ld bc,tuiles : add hl,bc ; on a l'adresse de la tile
ld a,16
afficheLigneTuile
push de : ldi 4 : pop de
ld bc,#800 : ex hl,de : add hl,bc : jr nc,.novf : ld bc,64-#4000 : add hl,bc : .novf ex hl,de
dec a : jr nz,afficheLigneTuile
pop hl : ld bc,4 : add hl,bc : ex hl,de ; se placer juste à côté de la tuile précédente
dec yl : jr nz,afficheTuile
ld bc,48 : add ix,bc ; revenir à la ligne de tuile (64-16 dans notre cas)
pop hl : ld bc,128 : add hl,bc : ex hl,de ; se placer sous les tuiles à gauche
dec yh : jr nz,afficheLigne
ret

Qu'on se déplace horizontalement ou verticalement, on va avoir besoin à chaque fois d'aller récupérer l'adresse de la tuile courante, d'incrémenter son déplacement dans la tileMap. En gérant tous les cas de clipping, on va partir parfois d'une tile entière, d'une ligne de tuile, d'une colonne de tuile et même en plein milieu d'une tuile.

C'est le bon moment pour préparer quelques macros qui vont nous 'éclaircir' le code d'affichage sans qu'on passe notre temps à relire et relire des calculs adresse qui se ressemblent comme deux gouttes d'eau. Une des sources d'erreur en programmation est de recopier un peu trop rapidement des morceaux de code pour les utiliser ailleurs.

D'ailleurs, devinez qui s'est planté en écrivant cet article et aurait dû les écrire de suite? ^_^

La routine qui calcule l'index primaire peut ensuite avancer (ou pas) dans la table, avec un incrément simple ou une valeur plus grande pour parcourir la table en Y.
MACRO calculeAdresseTuile increment
exx : ld a,(hl) ; index de la tile dans A
if {increment}==0 : elseif {increment}==1 : inc hl : else : add hl,bc : endif
exx ; index de la tile dans A / HL'=tileMap
ld hl,tuiles : ld b,a : ld c,0 : srl bc : srl bc : add hl,bc ; HL=adresse du début de la tuile
MEND
MACRO calculeAdresseTuileLigne increment
calculeAdresseTuile {increment}
ld a,(ix+multi.ligne) : add l : ld l,a ; HL=adresse du début de la tuile à la bonne ligne
MEND
MACRO calculeAdresseTuileColonne increment
calculeAdresseTuile {increment}
ld a,(ix+multi.colonne) : add l : ld l,a ; HL=adresse du début de la tuile à la bonne colonne
MEND
MACRO calculeAdresseTuileLigneColonne increment
calculeAdresseTuile {increment}
ld a,(ix+multi.ligne) : add (ix+multi.colonne) : add l : ld l,a ; HL=adresse du début de la tuile à la bonne ligne ET bonne colonne
MEND

J'allais oublier, on modifie l'écran, on modifie nos compteurs, mais on n'a toujours pas envoyé au hardware!
UpdateHardware
RMR2 ASICON
ld a,(ix+multi.HSSR) : or (ix+multi.VSSR) : or #80 : ld (#6804),a
ld hl,(ix+multi.crtcHL) : ld a,h : and 3 : ld h,a : ld a,(ix+multi.crtc12) : or h : ld bc,#BC00+12 : out (c),c : inc b : out (c),a
inc c : dec b : out (c),c : inc b : out (c),l
RMR2 ASICOFF
ret

On va écrire un premier programme qui n'affiche QUE notre écran (besoin des fichiers mapMulti.bin et mapMulti.tilemap)
BUILDSNA : BANKSET 0
SNASET CPC_TYPE,4 ; modèle 6128+ conseillé
ORG #100 : RUN #100
;*** RMR2 tags + macro ***
ASICOFF equ 0 : ROM0000 equ 0 : ROM4000 equ %01000 : ROM8000 equ %10000 : ASICON equ %11000
ROM0 equ 0 : ROM1 equ 1 : ROM2 equ 2 : ROM3 equ 3 : ROM4 equ 4 : ROM5 equ 5 : ROM6 equ 6 : ROM7 equ 7
macro RMR2 tags
ld a,{tags}+%10100000
ld b,#7F
out (c),a
mend

struct multi
mapOffset defw
HSSR defb ; 0-12
VSSR defb ; #00-#70
colonne defb ; 0-3 dans la tuile
ligne defb ; 0-60 par pas de 4 dans la tuile
adresseDebut defw
crtc12 defb ; cache des bits de bank
crtcHL defw ; adresse CRTC
endstruct

ld bc,#7F00+%10001100+%00 : out (c),c ; MODE 0
call UnlockAsic : RMR2 ASICON
ld hl,palettePlus : ld de,#6400 : ld bc,32 : ldir : ld hl,#000 : ld (#6420),hl ; +border noir

ld bc,#BC00+1 : out (c),c : ld bc,#BD00+32 : out (c),c ; Notre map fait 128 pixels de large, adaptez selon VOS besoins
ld bc,#BC00+2 : out (c),c : ld bc,#BD00+42 : out (c),c ; Centrer l'écran en X
ld bc,#BC00+6 : out (c),c : ld bc,#BD00+32 : out (c),c ; Hauteur de l'écran visible en lignes de caractères
ld bc,#BC00+7 : out (c),c : ld bc,#BD00+34 : out (c),c ; Centrer l'écran en Y
ld bc,#BC00+12 : out (c),c : ld bc,#BD30 : out (c),c ; adresse par défaut
ld bc,#BC00+13 : out (c),c : ld bc,#BD00 : out (c),c

ld ix,ecran1 : ld a,#C0 : call InitEcran
jr $

;---------------------------------------------------------------------
InitEcran
; IX = structure buffer
; A = poids fort de la page vidéo
ld hl,tileMap
ld (ix+multi.mapOffset),hl
ld hl,0
ld (ix+multi.HSSR),h
ld (ix+multi.VSSR),h
ld (ix+multi.colonne),h
ld (ix+multi.ligne),h
ld (ix+multi.crtcHL),hl
ld h,a : ld (ix+multi.adresseDebut),hl
rrca : rrca : ld (ix+multi.crtc12),a

; Afficher les tuiles sur tout l'écran

ld de,(ix+multi.adresseDebut)
ld ix,tileMap ; on n'utilise plus la structure à partir de là
ld yh,16 ; 16 tuiles en hauteur
afficheLigne
ld yl,16 ; nombre de tuiles en largeur
push de ; sauvegarder l'adresse de début de ligne de l'écran
afficheTuile
push de ; sauvegarder l'adresse du début de la tuile à l'écran
ld h,(ix+0) : inc ix : ld l,0 : srl hl : srl hl ; x 256 / 4 c'est la taille de nos tuiles (64 octets)
ld bc,tuiles : add hl,bc ; on a l'adresse de la tile
ld a,16
afficheLigneTuile
push de : ldi 4 : pop de
ld bc,#800 : ex hl,de : add hl,bc : jr nc,.novf : ld bc,64-#4000 : add hl,bc : .novf ex hl,de
dec a : jr nz,afficheLigneTuile
pop hl : ld bc,4 : add hl,bc : ex hl,de ; se placer juste à côté de la tuile précédente
dec yl : jr nz,afficheTuile
ld bc,48 : add ix,bc ; revenir à la ligne de tuile (64-16 dans notre cas)
pop hl : ld bc,128 : add hl,bc : ex hl,de ; se placer sous les tuiles à gauche
dec yh : jr nz,afficheLigne
ret
;---------------------------------------------------------------------
NextLineHL ld a,h : add 8 : ld h,a : and #38 : ret nz ; tester le changement de bloc sur n'importe quelle page
ld a,64 : add l : ld l,a : ld a,#C0 : adc h : ld h,a : res 3,h : ret
;---------------------------------------------------------------------
UnlockAsic
ld bc,#BCFF
out (c),c
out (c),0
ld hl,%1001000011101010
.loop
out (c),c
ld a,h:rlca:ld h,l:ld l,a
srl c:res 3,c
and #88
or c
ld c,a
cp #4D
jr nz,.loop
ld a,#CD
out (c),a : out (c),a
ret

WaitVBL
ld b,#F5 : noVBL in a,(c) : rra : jr c,noVBL
VBL in a,(c) : rra : jr nc,VBL : ret

paletteplus: defw #323,#646,#5B3,#967,#A57,#B95,#6E4,#9B7,#C89,#ABA,#CE2,#DB8,#DB9,#EC6,#EC9,#EED
tuiles incbin 'mapMulti.bin'
align 2 : tileMap include 'mapMulti.tilemap'
; et on se déclare deux structures pour un double buffer
struct multi ecran1
struct multi ecran2

Nos tuiles s'affichent, c'est un bon début, et il est essentiel!

La préparation étant faite, on va pouvoir ajouter nos directions dans la [ deuxième partie ]